Seperti yang gw bilang pada post gw yang sebelumnya, kali ini gw akan membahas bagaimana cara thread pada scheduler from executor bekerja dan cara menghitungnya. Hal ini penting karena seperti yang gw bilang, apabila cuma pakai saja tapi tidak mengerti fundamentalnya maka tentu akan berbahaya. Kenapa berbahaya? Karena bisa mengarahkan aplikasi kepada out of memory, client timeout dan rejection terhadap request yang datang. Oleh karena itu, mari kita bahas cara kerjanya satu per satu.
Untuk executor sendiri, yang akan gw gunakan adalah ThreadPoolTaskExecutor. Buat kalian yang menggunakan ThreadPoolExecutor sebenarnya cara kerjanya sama saja, cuma cara pakainya gw lebih suka yang taskexecutor. Disini yang wajib diingat adalah properties-properties yang bernama corePoolSize, maxPoolSize, MaxQueue, keepAliveSeconds dan allowCoreThreadTimeout. Ada rumus yang berlaku dalam properties-properties yang akan gw sebutkan diatas.
Rumusnya adalah sebagai berikut :
- Jika jumlah thread yang ada <= corePoolSize maka buat thread baru untuk menjalankan task yang baru.
- Jika jumlah thread yang ada > corePoolSize maka taro task yang ada ke dalam queue.
- Jika jumlah queue sudah penuh dan jumlah thread yang sedang berjalan <= maxPoolSize maka buat thread baru.
- Jika jumlah queue sudah penuh dan jumlah thread > maxPoolSize maka reject task.
Nah, jika dilihat dari rumus tersebut maka kita disini bisa mulai berhitung untuk menghitung berapa lama sebuah request berjalan dan juga berapa lama request tersebut bisa kita tolerir. Sebagai contoh :
Jika sebuah endpoint membutuhkan waktu 50 ms untuk menyelesaikan responsenya, maka 1 thread mampu menghandle 20 rps (50 ms * 20 request). Oleh karena itu, jika kita memperkirakan bahwa endpoint tersebut akan dihit sekurang-kurangnya 500 rps, maka kita akan membutuhkan 25 thread. Lalu bagaimana untuk menghitung maxQueue, maxPoolSize dan keepAliveSecond?
Untuk menghitung maxQueue maka kita harus menghitung berapa perkiraan waktu yang bisa ditolerir oleh client ketika call endpoint tersebut. Jika client mentolerir sampai 6s, maka kita bisa kalikan jumlah corePoolSize * 6. Selanjutnya adalah maxPoolSize, kita bisa berasumsi bahwa request yang datang ternyata lebih tinggi dari dugaan, maka kita harus menghitung berapa toleransi dari jumlah traffic yang akan meledak tersebut. Sebagai contoh jika ternyata traffic yang datang mungkin akan 4x traffic harian, maka kita akan menggunakan menghitung 4 * corePoolSize. Yang terakhir adalah keepAliveSecond. Ini adalah waktu lama hidupnya thread untuk maxPoolSize jika kita tidak melakukan setup allowCoreThreadTimeOut. Biasanya untuk extremenya, kita akan mengkalikannya dengan 10.
Untuk source codenya sendiri akan seperti berikut.
1 2 3 4 5 6 7 8 |
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(25); executor.setMaxPoolSize(100)); executor.setKeepAliveSeconds(10); executor.setQueueCapacity(150); executor.setAllowCoreThreadTimeOut(true); executor.setThreadNamePrefix("threadPoolTask-"); executor.initialize(); |
Disana ada 1 properties bernama allowCoreThreadTimeout. Penggunaan properties ini harus berhati-hati karena coreThread bisa ditimeout sesuai dengan keepAliveSeconds. Ini cukup berbahaya karena akan mengurangi reliability system. Sebagai contoh jika sebuah thread membutuhkan waktu proses melebihi lama waktu normalnya, bisa diterminate. Memang itu bagus untuk menjaga stabilitas sistem ketika traffic besar secara diluar dugaan namun akan membawa masalah karena bisa saja task yang sedang berjalan merupakan proses penting yang harus selesai. Jadi pemakaiannya harus sebijak mungkin.
Lalu bagaimana dengan anggapan yang gw tulis diatas mengenai terjadinya out of memory, rejected task dan client timeout? Oke, mungkin yang gw bahas adalah untuk yang client timeout terlebih dahulu karena ilustrasinya uda gw jelasin diatas juga. Jadi seperti yang gw tulis diatas, jika kita salah melakukan setup maxQueue, maka akan ada kemungkinan terjadi timeout disisi client karena kita tidak berekspektasi sesuai dengan client expectation.
Gampangnya, kalian setup client kalian 4s, lebih dari itu kalian anggap server ga bisa response. Padahal dibelakang server sedang mengantrikan task kalian, namun di depan client mengganggap sudah timeout dan berakhir kalian memproses sesuatu yang di depannya mengganggap bahwa kalian sudah gagal memproses. Kalau masi bingung, coba dibaca ulang aja kata-kata gw atau buat kodingannya sendiri. Gw yakin nanti kalian bakal ngerti hehe.
Untuk part reject task dan out of memory akan gw bahas di post terpisah ya hehe. Demikian sedikit tulisan gw mengenai executor.
Sumbernya bisa dicek disini :