Bagikan melalui


Antipattern Front End yang Sibuk

Melakukan pekerjaan asinkron pada sejumlah besar thread latar belakang dapat kekurangan tugas latar depan bersamaan sumber daya lainnya, mengurangi waktu respon ke tingkat yang tidak dapat diterima.

Deskripsi masalah

Tugas intensif sumber daya dapat meningkatkan waktu respons untuk permintaan pengguna dan menyebabkan latensi tinggi. Salah satu cara untuk meningkatkan waktu respons adalah dengan membongkar tugas intensif sumber daya ke thread terpisah. Pendekatan ini memungkinkan aplikasi tetap responsif saat pemrosesan terjadi di latar belakang. Namun, tugas yang berjalan pada thread latar belakang masih menggunakan sumber daya. Jika ada terlalu banyak, tugas dapat kekurangan thread yang menangani permintaan.

Catatan

Istilah sumber daya dapat mencakup banyak hal, seperti pemanfaatan CPU, hunian memori, dan jaringan atau disk I/O.

Masalah ini biasanya terjadi saat aplikasi dikembangkan sebagai bagian kode monolitik, dengan semua logika bisnis digabungkan menjadi satu tingkat yang dibagikan dengan lapisan presentasi.

Berikut adalah pseudocode yang menunjukkan masalah.

public class WorkInFrontEndController : ApiController
{
    [HttpPost]
    [Route("api/workinfrontend")]
    public HttpResponseMessage Post()
    {
        new Thread(() =>
        {
            //Simulate processing
            Thread.SpinWait(Int32.MaxValue / 100);
        }).Start();

        return Request.CreateResponse(HttpStatusCode.Accepted);
    }
}

public class UserProfileController : ApiController
{
    [HttpGet]
    [Route("api/userprofile/{id}")]
    public UserProfile Get(int id)
    {
        //Simulate processing
        return new UserProfile() { FirstName = "Alton", LastName = "Hudgens" };
    }
}
  • Metode Post dalam pengontrol WorkInFrontEnd mengimplementasikan operasi HTTP POST. Operasi ini menyimulasikan tugas yang berjalan lama dan intensif CPU. Pekerjaan dilakukan pada thread terpisah, dalam upaya untuk memungkinkan operasi POST menyelesaikan dengan cepat.

  • Metode Get dalam pengontrol UserProfile menerapkan operasi HTTP GET. Metode ini jauh lebih tidak intensif CPU.

Perhatian utama adalah persyaratan sumber daya dari metode Post. Meskipun menempatkan pekerjaan ke thread latar belakang, pekerjaan masih dapat menggunakan sumber daya CPU yang cukup besar. Sumber daya ini dibagi dengan operasi lain yang dilakukan oleh pengguna bersamaan lainnya. Jika sejumlah pengguna moderat mengirim permintaan ini pada saat yang sama, performa keseluruhan kemungkinan akan kesulitan, memperlambat semua operasi. Pengguna mungkin mengalami latensi yang signifikan dalam metode Get, misalnya.

Cara memperbaiki masalah ini

Pindahkan proses yang menggunakan sumber daya yang signifikan ke backend yang terpisah.

Dengan pendekatan ini, frontend menempatkan tugas yang intensif sumber daya ke antrean pesan. Backend mengambil tugas untuk pemrosesan asinkron. Antrean juga bertindak sebagai load leveler, melakukan buffer pada permintaan untuk backend. Jika panjang antrean menjadi terlalu panjang, Anda dapat mengonfigurasi penskalaan otomatis untuk menskalakan backend.

Berikut adalah versi revisi dari kode sebelumnya. Dalam versi ini, metode Post menempatkan pesan pada antrean Bus Layanan.

public class WorkInBackgroundController : ApiController
{
    private static readonly QueueClient QueueClient;
    private static readonly string QueueName;
    private static readonly ServiceBusQueueHandler ServiceBusQueueHandler;

    public WorkInBackgroundController()
    {
        string serviceBusNamespace = ...;
        QueueName = ...;
        ServiceBusQueueHandler = new ServiceBusQueueHandler(serviceBusNamespace);
        QueueClient = ServiceBusQueueHandler.GetQueueClientAsync(QueueName).Result;
    }

    [HttpPost]
    [Route("api/workinbackground")]
    public async Task<long> Post()
    {
        return await ServiceBusQueueHandler.AddWorkLoadToQueueAsync(QueueClient, QueueName, 0);
    }
}

Backend menarik pesan dari antrean Bus Layanan dan melakukan pemrosesan.

public async Task RunAsync(CancellationToken cancellationToken)
{
    this._queueClient.OnMessageAsync(
        // This lambda is invoked for each message received.
        async (receivedMessage) =>
        {
            try
            {
                // Simulate processing of message
                Thread.SpinWait(Int32.MaxValue / 1000);

                await receivedMessage.CompleteAsync();
            }
            catch
            {
                receivedMessage.Abandon();
            }
        });
}

Pertimbangan

  • Pendekatan ini menambahkan beberapa kompleksitas tambahan pada aplikasi. Anda harus menangani antrean dan pembatalan antrean dengan aman untuk menghindari kehilangan permintaan jika terjadi kegagalan.
  • Aplikasi mengambil dependensi pada layanan tambahan untuk antrean pesan.
  • Lingkungan pemrosesan harus cukup terukur untuk menangani beban kerja yang diharapkan dan memenuhi target throughput yang diperlukan.
  • Meskipun pendekatan ini harus meningkatkan responsivitas secara keseluruhan, tugas yang dipindahkan ke backend mungkin membutuhkan waktu lebih lama untuk diselesaikan.

Cara mendeteksi masalah

Gejala frontend yang sibuk termasuk latensi tinggi saat tugas intensif sumber daya sedang dilakukan. Pengguna akhir cenderung melaporkan waktu respons yang diperpanjang atau kegagalan yang disebabkan oleh waktu layanan. Kegagalan ini juga dapat menampilkan kesalahan HTTP 500 (Internal Server) atau kesalahan HTTP 503 (Service Unavailable). Periksa log peristiwa untuk server web, yang cenderung berisi informasi terperinci tentang penyebab dan keadaan kesalahan.

Anda dapat melakukan langkah-langkah berikut untuk membantu mengidentifikasi masalah ini:

  1. Lakukan pemantauan proses sistem produksi, untuk mengidentifikasi titik saat waktu respons melambat.
  2. Periksa data telemetri yang ditangkap pada titik ini untuk menentukan campuran operasi yang dilakukan dan sumber daya yang digunakan.
  3. Temukan korelasi antara waktu respons yang buruk dan volume serta kombinasi operasi yang terjadi pada saat itu.
  4. Uji beban setiap operasi yang dicurigai untuk mengidentifikasi operasi mana yang menggunakan sumber daya dan membuat operasi lainnya kekurangan.
  5. Tinjau kode sumber untuk operasi tersebut untuk menentukan alasannya dapat menyebabkan konsumsi sumber daya yang berlebihan.

Contoh diagnosis

Bagian berikut menerapkan langkah-langkah ini ke aplikasi contoh yang dijelaskan sebelumnya.

Mengidentifikasi titik perlambatan

Instrumentasikan setiap metode untuk melacak durasi dan sumber daya yang digunakan oleh setiap permintaan. Kemudian, pantau aplikasi dalam produksi. Ini dapat memberikan pandangan keseluruhan tentang cara permintaan bersaing satu sama lain. Selama periode stres, permintaan yang memerlukan sumber daya yang berjalan lambat kemungkinan akan memengaruhi operasi lain, dan perilaku ini dapat diamati dengan memantau sistem dan mencatat penurunan performa.

Gambar berikut menunjukkan dasbor pemantauan. (Kami menggunakan AppDynamics untuk pengujian kami.) Awalnya, sistem memiliki beban ringan. Kemudian, pengguna mulai meminta metode GET UserProfile. Performa cukup baik sampai pengguna lain mulai mengeluarkan permintaan ke metode POST WorkInFrontEnd. Pada saat itu, waktu respons meningkat secara dramatis (panah pertama). Waktu respons hanya meningkat setelah volume permintaan ke pengontrol WorkInFrontEnd berkurang (panah kedua).

Panel Transaksi Bisnis AppDynamics yang menampilkan efek waktu respons semua permintaan saat pengontrol WorkInFrontEnd digunakan

Memeriksa data telemetri dan menemukan korelasinya

Gambar berikutnya menunjukkan beberapa metrik yang dikumpulkan untuk memantau pemanfaatan sumber daya selama interval yang sama. Pada awalnya, hanya sedikit pengguna yang mengakses sistem. Karena semakin banyak pengguna yang terhubung, pemanfaatan CPU menjadi sangat tinggi (100%). Perhatikan juga bahwa tingkat I/O jaringan awalnya naik karena penggunaan CPU meningkat. Tetapi begitu penggunaan CPU memuncak, I/O jaringan benar-benar turun. Itu karena sistem hanya dapat menangani sejumlah kecil permintaan setelah CPU pada kapasitas. Saat pengguna terputus, tail beban CPU nonaktif.

Metrik AppDynamics yang memperlihatkan pemanfaatan CPU dan jaringan

Pada titik ini, tampaknya metode Post dalam pengontrol WorkInFrontEnd adalah kandidat utama untuk pemeriksaan lebih dekat. Pekerjaan lebih lanjut dalam lingkungan yang terkontrol diperlukan untuk mengonfirmasi hipotesis.

Melakukan pengujian beban

Langkah selanjutnya adalah melakukan pengujian di lingkungan yang terkontrol. Misalnya, jalankan serangkaian pengujian beban yang mencakup dan kemudian menghilangkan setiap permintaan secara bergantian untuk melihat efeknya.

Grafik di bawah ini menunjukkan hasil pengujian beban yang dilakukan terhadap penyebaran layanan cloud yang identik yang digunakan dalam pengujian sebelumnya. Pengujian ini menggunakan beban konstan 500 pengguna yang melakukan operasi Get di pengontrol UserProfile, bersama dengan beban langkah pengguna yang melakukan operasi Post di pengontrol WorkInFrontEnd.

Hasil pengujian beban awal untuk pengontrol WorkInFrontEnd

Awalnya, beban langkah adalah 0, jadi satu-satunya pengguna aktif yang melakukan permintaan UserProfile. Sistem ini mampu menanggapi sekitar 500 permintaan per detik. Setelah 60 detik, beban 100 pengguna tambahan mulai mengirim permintaan POST ke pengontrol WorkInFrontEnd. Segera, beban kerja yang dikirim ke pengontrol UserProfile turun menjadi sekitar 150 permintaan per detik. Hal ini disebabkan oleh cara runner pengujian beban berfungsi. Ini menunggu respons sebelum mengirim permintaan berikutnya, jadi semakin lama waktu yang dibutuhkan untuk menerima respons, semakin rendah tingkat permintaan.

Karena semakin banyak pengguna mengirim permintaan POST ke pengontrol WorkInFrontEnd, tingkat respons pengontrol UserProfile terus menurun. Tetapi perhatikan bahwa volume permintaan yang ditangani oleh pengontrol WorkInFrontEnd tetap relatif konstan. Saturasi sistem menjadi jelas karena tingkat keseluruhan kedua permintaan cenderung menuju batas yang stabil namun rendah.

Meninjau kode sumber

Langkah terakhir adalah melihat kode sumber. Tim pengembangan menyadari bahwa metode Post bisa memakan banyak waktu, itulah sebabnya implementasi asli menggunakan thread terpisah. Hal tersebut memecahkan masalah langsung, karena metode Posttidak menghalangi menunggu tugas yang sudah berjalan lama untuk diselesaikan.

Namun, pekerjaan yang dilakukan dengan metode ini masih menggunakan CPU, memori, dan sumber daya lainnya. Mengaktifkan proses ini untuk berjalan secara asinkron sebenarnya dapat merusak performa, karena pengguna dapat memicu sejumlah besar operasi ini secara bersamaan, dengan cara yang tidak terkendali. Ada batasan jumlah thread yang dapat dijalankan server. Melewati batas ini, aplikasi kemungkinan akan mendapatkan pengecualian saat mencoba memulai thread baru.

Catatan

Ini tidak berarti Anda harus menghindari operasi asinkron. Melakukan menunggu asinkron pada panggilan jaringan adalah praktik yang disarankan. (Lihat Antipattern I/O sinkron.) Masalahnya di sini adalah bahwa pekerjaan intensif CPU diluapkan di utas lain.

Menerapkan solusi dan memverifikasi hasilnya

Gambar berikut menunjukkan pemantauan performa setelah solusi diterapkan. Bebannya mirip dengan yang ditunjukkan sebelumnya, tetapi waktu respons untuk pengontrol UserProfile sekarang jauh lebih cepat. Volume permintaan meningkat selama durasi yang sama, dari 2.759 menjadi 23.565.

Panel Transaksi Bisnis AppDynamics yang menampilkan efek waktu respons semua permintaan saat pengontrol WorkInBackground digunakan

Perhatikan bahwa pengontrol WorkInBackground juga menangani volume permintaan yang jauh lebih besar. Namun, Anda tidak dapat membuat perbandingan langsung dalam hal ini, karena pekerjaan yang dilakukan di pengontrol ini sangat berbeda dari kode aslinya. Versi baru hanya mengantrekan permintaan, daripada melakukan perhitungan yang memakan waktu. Poin utamanya adalah bahwa metode ini tidak lagi menyeret seluruh sistem di bawah beban.

Pemanfaatan CPU dan jaringan juga menunjukkan peningkatan performa. Penggunaan CPU tidak pernah mencapai 100%, dan volume permintaan jaringan yang ditangani jauh lebih besar dari sebelumnya, dan tidak bertahan sampai beban kerja turun.

Metrik AppDynamics yang memperlihatkan CPU dan pemanfaatan jaringan untuk pengontrol WorkInBackground

Grafik berikut menunjukkan hasil pengujian beban. Volume keseluruhan permintaan yang dilayani sangat ditingkatkan dibandingkan dengan pengujian sebelumnya.

Hasil pengujian beban untuk pengontrol BackgroundImageProcessing