Di Asana, kami telah membangun sistem pemuatan data bernama LunaDb yang berfungsi sebagai tulang punggung aplikasi web kami. Terlepas dari namanya, ini bukan database. Sebaliknya, ini adalah sistem seperti GraphQL untuk mengambil data secara deklaratif—pada dasarnya ini adalah cara untuk memuat versi terbaru data dan semua pembaruan mendatang.
Awalnya kami meluncurkan LunaDb pada 2015 sebagai penulisan ulang radikal dari infrastruktur backend kami¹. Komponen utama dari sistem baru ini adalah server sinkronisasi, monolit yang melakukan segalanya mulai dari sinkronisasi klien hingga pemuatan data dan kontrol akses. Tanpa perubahan signifikan, arsitektur awal ini berkembang jauh melampaui tingkat yang diperkirakan sebelumnya, hingga mencapai jutaan pengguna aktif mingguan dan miliaran kueri harian.
Meskipun kinerjanya tetap kuat, seiring dengan pertumbuhan lalu lintas dan kompleksitas fitur, semakin sulit untuk mengoperasikan dan meningkatkan LunaDb karena kendala server sinkronisasi.
ikhtisar infrastruktur pemuatan data kami
Mengapa sulit dioperasikan?
Pergeseran lalu lintas itu mahal
Server sinkronisasi secara langsung mengelola koneksi websocket persisten ke klien. Setiap websocket didukung oleh sesi klien stateful. Ketika koneksi terputus, semua status ini dibuang dan klien akan berlangganan ulang ke semua data yang diperlukan. Ketika ini terjadi pada banyak sesi, biayanya akan cepat menjadi mahal. Jadi, kami harus berhati-hati saat mengalihkan koneksi ini, mengingat lonjakan besar dalam pekerjaan akibat penyambungan ulang.
pergeseran lalu lintas
Menyebarkan server sinkronisasi berarti mengalihkan lalu lintas
Tentu saja, Anda tidak dapat mencegah perpindahan lalu lintas secara terus-menerus. Setiap kali Anda ingin mendorong kode baru atau meningkatkan/menurunkan skala, Anda perlu menonaktifkan server sinkronisasi—dan itu memerlukan pemindahan semua lalu lintas dari instans yang dihentikan.
pembaruan berkelanjutan
Server sinkronisasi tidak berkinerja baik saat memulai
Pada saat yang sama, server sinkronisasi hanya akan berkinerja baik setelah proses pemanasan yang cukup besar. Mengelola kedua isu ini merupakan keseimbangan yang cukup rapuh dan sebelumnya memerlukan pekerjaan teknik yang ekstensif.
Server sinkronisasi itu kompleks dan sulit dipantau
Terakhir, karena server sinkronisasi menjalankan banyak bit kode produk yang sewenang-wenang (melalui fungsi sisi server khusus), server tersebut sangat rentan terhadap regresi kinerja berbasis noisy neighbor yang sulit untuk dikaitkan².
masalah tetangga berisik
Pertanyaan yang masuk akal untuk diajukan adalah, “Mengapa server sinkronisasi kompleks dan tidak berkinerja saat memulai?”
Penyebab umum dari isu-isu ini adalah kode produk sisi server kami. Bagian utama kode server sinkronisasi ditulis dalam Scala. Meskipun ada beberapa kompleksitas terkait manajemen status sesi dan berbagai aspek kerangka kerja Luna, kode kerangka kerja/platform ini sebagian besar berperilaku seperti yang kita harapkan (isu operasional dan kinerja relatif sedikit).
Di sisi lain, server-computed-value produk ini (kami menyebutnya SCV, tetapi anggap sebagai resolver kustom) ditulis dalam Typescript. Kedua rangkaian kode berjalan bersama di GraalVM, VM poliglot yang memungkinkan penggunaan beberapa bahasa melalui Kerangka Kerja Truffle-nya. Karena ditulis dalam Typescript, SCV pada dasarnya diinterpretasikan saat memulai—yang secara prediktif menghasilkan kinerja dan penggunaan CPU yang tidak dapat diterima. GraalVM akan mencoba melakukan kompilasi just-in-time pada SCV yang dipanggil. Ini bagus! GraalVM/Truffle dapat sangat mengoptimalkan kinerjanya—tetapi melakukannya tidak gratis. Kompilasi SCV bisa cukup mahal (dalam hal cpu, cache kode, dll.).
penyiapan VM poliglot kami
Mengapa dua bahasa?
Desain pertama kami untuk SCV sepenuhnya dalam Scala. Di sisi lain, sistem mutasi dan pekerjaan asinkron kami ditulis dalam Javascript/Typescript. Meskipun SCV berbasis Scala berfungsi, duplikasi logika bisnis antara sistem mutasi dan pekerjaan asinkron kami dan LunaDb, serta ketidakpahaman insinyur produk terhadap Scala, menjadi penghambat yang cukup besar bagi kecepatan produk.
Mengapa GraalVM?
Kami menyimpan banyak data dalam cache saat proses berlangsung untuk mempercepat komputasi (dan komputasi ulang) hasil langganan. Menggunakan GraalVM memberi kami cara mudah untuk membagikan cache ini di berbagai bahasa tanpa kekhawatiran terkait ketepatan atau kinerja yang mungkin timbul dari pemisahan bagian Scala dan TypeScript ke dalam kontainer terpisah.
Mengapa sulit untuk diperbaiki?
Karena server melakukan begitu banyak hal dan relatif rentan untuk dioperasikan, kami cenderung menghindari perubahan yang lebih besar. Bukan hanya karena kompleksitas kode, tetapi juga karena biaya overhead yang tinggi untuk meluncurkan perubahan baru dengan aman.
Ya, saya suka mengajukan pertanyaan
Mengingat kesulitan dalam mengoperasikan dan memperbaiki server sinkronisasi, kami membuat keputusan sulit untuk mengubah arsitektur kami. Pada dasarnya, kami memutuskan untuk mengganti server sinkronisasi monolitik dengan dua jenis komponen yang lebih kecil:
broker sesi yang mengelola koneksi klien dan resolusi status
pemuat yang dapat disinkronkan yang bertanggung jawab atas pemuatan data, yaitu memenuhi kueri dari broker sesi
session-broker dan syncable-loader
Mengapa ini membantu?
Arsitektur baru ini segera memisahkan pergeseran lalu lintas websocket (yaitu menyebarkan broker sesi) dari pemanasan proses baru (yaitu menyebarkan loader yang dapat disinkronkan). Hasilnya, kami dapat meminimalkan gangguan dengan menyebarkan loader yang dapat disinkronkan secara terpisah dari broker sesi.
Setiap komponen baru ini lebih sederhana.
Broker sesi jauh lebih ringan, tidak memerlukan pemanasan proses, dan tidak menjalankan kode produk. Akibatnya, kita tidak perlu menyebarkannya sesering itu—dan ketika kita melakukannya, prosesnya cukup mudah.
Syncable loader memiliki Antarmuka yang lebih sederhana (permintaan langganan stateless) yang lebih mudah disesuaikan dengan autoscaling pod horizontal Kubernetes standar. Ini juga membuat pemanasan lebih cepat dan lebih mudah—kami cukup menggunakan pencerminan lalu lintas pada permintaan ambient yang mengalir antara broker sesi dan loader yang dapat disinkronkan
Arsitektur baru memungkinkan kami untuk sangat menyederhanakan proses pengembangan produk. Sejak awal, jadwal penyebaran independen kode produk sisi server dan pemanggilan kode produk sisi klien telah menjadi penghambat kecepatan dan sumber beban operasional (karena ketidakcocokan versi). Karena sekarang loader yang dapat disinkronkan adalah satu-satunya proses yang tersisa yang menjalankan kode produk dan penyebarannya tidak lagi mengganggu, kami dapat menyebarkannya kembali setiap kali kami mendorong kode produk baru.
Arsitektur baru ini memungkinkan kami untuk menskalakan fitur baru dengan lebih baik dengan menyebarkan kumpulan pemuat yang dapat disinkronkan yang berbeda untuk jenis beban kerja yang berbeda (misalnya, fitur berbeda yang berbeda seperti Kotak Masuk, Tugas, Gol, dll.). Broker sesi berfungsi sebagai gateway layanan yang dapat secara langsung mengontrol cara kueri data diarahkan ke berbagai pemuat yang dapat disinkronkan di upstream.
Bagus! Arsitektur baru ini terdengar jauh lebih baik, tetapi bagaimana kami mewujudkannya? Server sinkronisasi pada dasarnya adalah monolit karena mencakup beberapa fungsi—dan memisahkan monolit hampir selalu sulit. Dalam kasus kami, kami harus mengatasi beberapa hambatan desain utama.
Memisahkan PubSub
PubSub, sistem kami untuk menerapkan reaktivitas, dirancang berdasarkan satu proses (server sinkronisasi) yang bertanggung jawab untuk memuat data baru dan mengirimkannya ke klien. Kami harus mendesain ulang PubSub dengan cara yang menjamin kebenaran di kedua jenis proses yang sekarang independen ini (pemuatan yang dapat disinkronkan dan broker sesi).
Mari kita pelajari secara singkat cara implementasinya di server sinkronisasi. Catatan: Mungkin akan bermanfaat jika Anda membaca postingan kami sebelumnya tentang pipeline invalidasi, tetapi kami akan memberikan tampilan sistem tanpa prasyarat.
Pada server sinkronisasi, kami melacak langganan per sesi. Kami menggunakan pipeline invalidasi untuk terus memonitor langganan untuk pembaruan. Pada setiap pesan pembatalan baru, server sinkronisasi akan memuat ulang semua langganan yang terdampak.
Server sinkronisasi secara intensif menyimpan hasil objek/kueri db, hasil resolver kustom, dan hasil langganan sebelumnya dalam cache untuk mengoptimalkan pemuatan ulang langganan (yaitu menggunakan pola read-through). Artefak yang di-cache secara pasif dibatalkan oleh pipeline pembatalan yang sama yang digunakan untuk langganan. Setiap kali kami mencoba menggunakan data yang di-cache, kami akan memeriksa validitasnya dan kembali ke perhitungan ulang jika perlu.
Kita dapat mengamati dependensi yang jelas antara memuat ulang langganan dan membatalkan validitas data cache. Setelah menerima pembatalan, jika kami memuat ulang langganan sebelum data cache dibatalkan, kami berpotensi menghitung hasil yang sudah usang. Ketika pemuatan data dan manajemen langganan terjadi pada proses yang sama, sangat mudah untuk menjamin dependensi ini—cukup batalkan cache sebelum memuat ulang.
Kondisi race dapat menyebabkan data usang saat pembaruan
Dalam arsitektur baru yang kami usulkan, broker sesi dan pemuat yang dapat disinkronkan adalah konsumen independen dari pipeline pembatalan. Lalu, bagaimana kita dapat memastikan bahwa cache dinyatakan tidak valid sebelum langganan dimuat ulang?
Pembuatan versi permintaan dan respons
Kami bisa menyelesaikan ini dengan membuat pipeline pembatalan mengirimkan pesan secara serempak. Atau kami bisa saja membuat mekanisme untuk memberlakukan jaminan urutan bahwa tidak ada pesan pipeline pembatalan yang tiba di broker sesi sebelum loader yang dapat disinkronkan. Namun, kedua solusi ini memiliki kompromi yang kurang ideal—yang terpenting, keduanya meningkatkan penggabungan broker sesi dan loader yang dapat disinkronkan.
Sebaliknya, kami memecahkan masalah ini dengan menambah protokol pemuatan data kami dengan versi permintaan dan respons berdasarkan kemajuan relatifnya dalam stream invalidasi. Karena stream mewakili urutan total pembaruan pada basis data, progres stream kami dapat digunakan seperti penghitung versi global.
versi permintaan dan tanggapan
Pemuatan ulang invalidasi
Server sinkronisasi memuat banyak data—sebagian besar dari semua pembacaan untuk situs. Arsitektur baru kami mengharuskan session-broker dan syncable-loader untuk bertukar banyak data melalui jaringan. Untuk langganan baru, overhead jaringan ini relatif dapat diabaikan. Namun, ini sangat tidak efisien untuk pemuatan ulang invalidasi, karena kita sering kali tidak perlu mengembalikan respons lengkap—hanya data yang diperbarui³. Beberapa kasus tertentu sangat buruk di sini. Bayangkan kasus di mana satu pengguna telah memuat 10.000 tugas dalam satu proyek, dan pengguna lain terus-menerus mengubah deskripsi tugas dalam proyek ini: semua tugas perlu dikirim setiap kali terjadi pembatalan validasi! Jelas, yang ideal adalah hanya mengirim kembali data yang diperbarui, tetapi bagaimana cara menerapkannya secara efisien?
pemuatan ulang pembatalan
Fingerprinting
Agar loader yang dapat disinkronkan dapat menghitung delta data yang diperbarui, ia harus tahu data mana yang sudah dimiliki oleh pemohon. Namun, mengirimkan data terkini dengan permintaan akan sama mahalnya dengan mengembalikan hasil lengkap. Kita perlu merepresentasikan data dengan cara yang lebih hemat ruang.
Nah, hashing adalah cara terbaik untuk menghemat ruang. Setiap bit data granular yang kita perhatikan disebut syncable. Kita dapat menghitung murmur hash 128-bit dari setiap syncable yang diserialisasi untuk digunakan sebagai sidik jari⁴. Secara khusus, sidik jari ini adalah pengidentifikasi untuk versi syncable tersebut.
Di mana pun kita melacak data syncable, kita dapat menggunakan sidik jarinya sebagai gantinya. Sekarang, ketika kita ingin melacak respons langganan lengkap, kita cukup menggunakan satu set sidik jari tanpa harus menyampaikan data lengkap!
Catatan samping: Apa itu syncable dan bagaimana kaitannya dengan langganan?
Syncable adalah konten dari hasil langganan. Saat kita memuat langganan, hasilnya dikembalikan sebagai satu set syncable. Lebih khusus lagi, syncable dapat berupa objek, kueri, atau hasil SCV.
dapat disinkronkan ke pemetaan langganan
Jelas, setiap langganan dipetakan ke beberapa syncable. Namun, syncable dapat dibagikan oleh beberapa langganan (ketika langganan tersebut memuat data yang tumpang tindih). Oleh karena itu, sebenarnya ada pemetaan banyak-ke-banyak antara langganan dan syncable.
Kami meneruskan kumpulan sidik jari ini dengan setiap permintaan dari broker sesi. Pada pemuat yang dapat disinkronkan, kami menghitung respons lengkap, menghitung kumpulan sidik jarinya, mengecualikan data apa pun yang tumpang tindih dengan permintaan, dan mengembalikan delta.
Mengingat ukuran dan tingkat kekritisan perubahan, kami membagi peluncuran menjadi sekitar 4 tahap.
Tahap 1 - Melakukan refactoring pada monolit
Memisahkan kode pengelolaan sesi dan pemuatan data kami yang sangat terintegrasi menjadi komponen-komponen independen
Tahap 2 - Syncable-loader lokal
Gunakan komponen pemuatan data baru untuk membuat server gRPC lokal dan memigrasikan pemuatan data
Tahap 3 - Syncable-loader jarak jauh
Buat biner dan penyebaran syncable-loader baru
Migrasikan semua pemuatan data sync-server ke penyebaran syncable-loader baru kami
Tahap 4 - Biner session-broker terpisah
Buat biner session-broker dan penyebaran baru
Migrasikan semua traffic dari sync-server ke session-broker
Begitu banyak. Harus mulai dari mana?
Respons besar
Salah satu masalah yang segera kami hadapi adalah ukuran respons yang besar. Karena pemuatan data di server sinkronisasi semuanya berada dalam proses yang sama, ini tidak terbukti menjadi isu besar hingga saat ini⁵. Namun, setelah kami mulai memuat data melintasi batas gRPC lokal, kami mulai menghadapi banyak isu.
Kami menduga bahwa beberapa respons mungkin besar selama ini, tetapi ketika kami mulai menyelidiki hal ini, kami menemukan hasil yang benar-benar mengejutkan. Kami memiliki ribuan pemuatan per hari yang secara rutin melebihi 100MiB! Secara fisik, kami tidak dapat mengembalikan respons sebesar itu melalui metode gRPC unary (Anda mulai mencapai ukuran frame maks http2). Apa yang harus dilakukan?
Kami mempertimbangkan beberapa cara untuk memperbaiki ini secara sistematis, tetapi pada akhirnya menyimpulkan bahwa kami perlu mengatasi akar penyebabnya. Kami bisa saja menerapkan streaming sisi server gRPC—tetapi biaya serialisasi yang tinggi dan peningkatan persaingan soket yang ditimbulkan akan cukup merugikan latensi dan throughput. Kami bisa saja menolak respons ini—tetapi tingkat kejadiannya terlalu tinggi untuk dapat diterima.
Kami memutuskan untuk menggunakan pendekatan tiga fase, yaitu menandai semua tanggapan besar, menganalisis dan menghilangkan setiap kasus yang bermasalah, dan kemudian memberlakukan batas atas yang ketat pada ukuran tanggapan.
Kami menandai semua muatan yang lebih besar dari 1 MB dan mencatat peristiwa terperinci tentang sumber, penggunaan, dan perincian data. Ada beberapa kasus penggunaan mahal yang berbeda, tetapi yang paling terkenal mungkin adalah blob thumbnail lampiran. Ternyata mereka dikodekan sebagai string base64 dan disertakan dalam respons yang diserialkan. Jumlahnya yang sedikit masih dapat ditoleransi, tetapi dengan cepat menjadi sangat besar ketika dimuat secara massal—seperti saat memuat tampilan berbasis kisi yang merender thumbnail lampiran untuk setiap Tugas.
Kami dapat secara progresif memperbaiki isu-isu seperti ini dengan membatasi thumbnail untuk respons yang sangat besar, menggunakan thumbnail yang lebih kecil, dan akhirnya memindahkan data biner keluar dari respons. Setelah mitigasi sederhana seperti ini, jumlah respons besar menghilang, dan selanjutnya kami dapat menerapkan batasan ukuran respons tingkat kerangka kerja⁶.
Tabrakan topik
Masalah aneh lainnya yang kami temui adalah tabrakan topik pubsub. Ternyata kami memiliki penggunaan Kerangka Kerja yang tidak sesuai yang menghasilkan topik langganan yang sama terlepas dari domain. Ketika pubsub hanya terjadi pada satu jenis proses, efeknya relatif tidak berbahaya. Biasanya, satu topik pubsub sesuai dengan data dari satu domain. Namun, dengan pubsub yang kini terpisah di antara session-broker dan syncable-loader, ada kemungkinan kedua jenis proses tersebut tidak sesuai pada domain topik tertentu. Ketika ini terjadi, kami akan melihat tingkat muat ulang invalidasi yang meningkat secara stabil karena “ketidakcocokan domain” ini. Untungnya, perbaikannya cukup mudah—tetapi menarik untuk diketahui bagaimana bug ini bertahan dalam Kerangka Kerja kami begitu lama tanpa terdeteksi.
Menyesuaikan kembali beban kerja
Session broker dan syncable loader memiliki beban kerja yang sangat berbeda dengan beban kerja server sinkronisasi asli. Broker sesi hanya bertanggung jawab atas manajemen sesi dan loader yang dapat disinkronkan hanya bertanggung jawab atas pemuatan data.
Kami tidak yakin bagaimana hal ini akan memengaruhi kebutuhan sumber daya mereka, jadi kami memulai keduanya dengan permintaan sumber daya (cpu/mem) dan pengaturan horizontal pod autoscaler (HPA) yang serupa.
session-broker
Seperti yang kami amati, menjadi jelas bahwa broker sesi jauh lebih ringan. Mereka beroperasi dengan andal pada CPU yang sangat rendah (jika ada, mereka jauh lebih terikat pada memori⁷). Beberapa replika tampaknya cukup untuk melayani lalu lintas seluruh sel infrastruktur. Namun, ketika kami benar-benar mengurangi minReplicas pada HPA, kami mengamati bahwa pemuatan ulang data dan latensi pemuatan ulang melonjak. Apa yang terjadi?
Singkatnya, kami lalai mempertimbangkan semua pengaturan bersama kami seputar ukuran cache dan throttler. Dengan hanya beberapa replika, setiap broker sesi menangani jauh lebih banyak sesi per pod (sekitar 3,5x) dibandingkan server sinkronisasi biasa. Karena masing-masing broker melihat jauh lebih banyak data, mereka benar-benar mengisi cache topik ⇔ langganan pubsub mereka dan setiap pengeluaran memicu pemuatan ulang (karena alasan keamanan). Meningkatkan ambang batas ini secara tepat hingga ~6x memperbaiki peningkatan tingkat pengeluaran cache dan pemuatan ulang. Demikian pula, kami menemukan bahwa throttler muat ulang hierarkis kami salah dikonfigurasi untuk tingkat lalu lintas baru yang masuk. Mengatur ukuran pengatur ini dengan benar juga menyebabkan penurunan dramatis dalam latensi muat ulang dan lag reaktivitas ujung ke ujung (yaitu berapa lama waktu yang dibutuhkan bagi suatu app web untuk melihat penulisannya sendiri) sekitar 5x - 10x.
syncable-loaders
Di sisi lain, loader yang dapat disinkronkan jauh lebih berat dari yang diharapkan. Setiap server akan memuat lebih banyak langganan per detik (sekitar 1,5x) dibandingkan server sinkronisasi dengan sumber daya yang setara. Tidak seperti broker sesi, mereka jauh lebih terikat pada CPU ⁸.
Menariknya, sebagian besar CPU diatribusikan ke peningkatan deoptimisasi Truffle yang terkait dengan kode TS SCV kami. Kemungkinan besar, ini disebabkan oleh setiap loader yang dapat disinkronkan yang mengakses lebih banyak kode SCV kami. Terlepas dari itu, hal ini memerlukan peningkatan kecil dalam ukuran cache kode kami⁹.
Pemanasan proses
Pemanasan proses untuk pemuatan data secara historis merupakan tantangan. Untungnya, dalam arsitektur baru kami, ini sedikit lebih mudah. Antarmuka utama syncable-loader kami adalah kueri data stateless—jadi kami dapat memanaskannya hanya dengan memutar ulang atau mencerminkan lalu lintas yang ada antara session-broker dan syncable-loader.
Di sisi lain, kami masih menghadapi banyak tantangan yang sama seperti saat memanaskan server sinkronisasi. Intinya, dibutuhkan banyak CPU untuk memanaskan proses, dan ini menyebabkan berbagai macam masalah noisy neighbor (bagi kami, metrik yang relevan di sini adalah penghentian parsial karena kami tidak mencapai batas throttling k8s) saat memulai. Peningkatan bagus yang kami lakukan di sini adalah menggunakan pengubahan ukuran pod di tempat untuk membatasi sumber daya syncable-loader saat memulai, tetapi memungkinkannya untuk melonjak setelah memulai.
Meskipun demikian, pemanasan setiap pod masih memerlukan waktu beberapa menit. Dari melihat profil JFR, kami yakin kemacetan utamanya adalah kompilasi kode TS SCV yang tidak memadai selama pemanasan, dan kami yakin ada lebih banyak ruang untuk peningkatan di sana. Kami secara aktif mencari cara untuk memanaskan jalur yang relevan dengan lebih tepat menggunakan metode yang lebih bertarget dan membentuk ulang Antarmuka TS kami¹⁰ untuk kompilasi yang lebih baik.
Broker sesi kami tidak bertanggung jawab atas pemuatan data apa pun dan dalam praktiknya tidak pernah memerlukan pemanasan apa pun.
Arsitektur baru kami secara signifikan mengurangi kompleksitas operasional kami, mempercepat penyebaran dan kecepatan kode, serta membuka peluang kecepatan dan penskalaan di masa depan.
Arsitektur baru kami secara otomatis menskalakan perubahan dalam total lalu lintas baca tanpa memerlukan manajemen lalu lintas yang kompleks. Setiap jenis operasi pergeseran lalu lintas ditangani dengan rapi hanya dengan memulai ulang pod. Perlu mem-boot semua sesi? Buat siklus semua broker sesi. Perlu menghapus cache kami? Buat siklus semua syncable-loader. Kedua operasi tersebut aman dalam hal waktu online.
Sebelumnya, kecepatan pengembangan produk terutama mengalami kemacetan karena penyebaran server sinkronisasi. Di dunia baru kami, kami hanya perlu menyebarkan syncable-loader untuk menyebarkan kode produk. Kami telah melakukan ini dengan memindahkan syncable-loader ke sel deployable mereka sendiri yang kami upayakan untuk dideploy lebih sering (dan pada akhirnya bersama kode produk). Penyebaran syncable-loader sudah ~40% lebih cepat (~20 menit lebih cepat) daripada sync-server (kami dapat dengan aman meningkatkan jauh lebih tinggi), dan kami menargetkan peningkatan yang lebih besar di masa mendatang.
Perlu dicatat, peningkatan kinerja bukanlah gol dari pekerjaan ini, tetapi layak untuk dibahas mengingat seberapa besar desain dan implementasi yang melibatkannya. Latensi hasil kueri komputasi sebenarnya meningkat di sistem baru kami (kemungkinan karena penyeimbangan beban/penyetelan/penskalaan otomatis yang lebih baik). Sejalan dengan itu, latensi langganan awal jauh lebih baik. Di sisi lain, latensi refleksi mutasi ujung-ke-ujung (yaitu, berapa lama waktu yang dibutuhkan agar perubahan didistribusikan ke sesi lain) kurang lebih sama. Jejak menunjukkan bahwa ini kemungkinan disebabkan oleh ketidakseimbangan dalam memanfaatkan stream PubSub antara session-broker dan syncable-loader (syncable-loader tidak dapat melayani permintaan sampai mereka up-to-date dengan versi permintaan).
Sistem kami saat ini jauh lebih baik, tetapi masih membatasi kecepatan produk dengan mengharuskan pertimbangan seputar kompatibilitas mundur/maju pada setiap rilis. Namun, dengan semua peningkatan kami dalam kecepatan penyebaran, sekarang memungkinkan untuk menyebarkan semua perubahan model data kami secara bersamaan, sehingga menghilangkan kebutuhan untuk mempertimbangkan kompatibilitas mundur/maju. Kami secara aktif berupaya membangun ini dalam waktu dekat.
Salah satu motivasi utama kami untuk pekerjaan ini adalah regresi kinerja yang sulit dikaitkan. Perlu dicatat, ini adalah salah satu area yang belum kami tingkatkan secara signifikan sebagai hasil dari pekerjaan ini. Sisi positifnya, ini sekarang menjadi salah satu masalah utama yang kami atasi untuk ke depannya. Tidak seperti sebagian besar pekerjaan yang dibahas di sini, ini adalah masalah yang jauh lebih lintas fungsi yang melibatkan domain produk, model data, Kerangka Kerja, dan pertimbangan infrastruktur.
pool pekerja pemuat yang dapat disinkronkan menurut fitur
Kami sangat bersemangat untuk mengeksplorasi solusi di seluruh infrastruktur platform (misalnya, kumpulan pekerja berdasarkan fitur), Kerangka Kerja platform, dan alat platform (misalnya, pengujian black box/white box).
Arvind Vijayakumar adalah seorang insinyur di tim LunaDb, tempat ia bekerja untuk membantu membangun dan menskalakan platform Pemuatan Data inti Asana—infrastruktur penting yang memastikan pengguna selalu melihat pembaruan yang akurat, reaktif, dan secepat kilat di seluruh aplikasi web dan API kami.
Pekerjaan untuk meningkatkan skala LunaDb ini telah menjadi upaya tim jangka panjang selama beberapa tahun terakhir, yang melibatkan banyak anggota LunaDb, baik saat ini maupun sebelumnya: Brandon Zhang, Alex Matevish, Sean Wentzel, Eric Walton, Spencer Yu, Sophia Yao, George Ong, Tyler Prete, Koushik Ghosh, Natan Dubitski, Vinodh Chandra Murthy, dan banyak lagi
Yang perlu diperhatikan, kami membangunnya sebelum Facebook membuka sumber GraphQL
Ada lebih banyak detail dalam postingan sebelumnya tentang pemanasan proses, tetapi secara singkat, kami mengandalkan cara yang memungkinkan pod sinkronisasi untuk melakukan burst secara sewenang-wenang sebagai cara untuk dengan cepat melakukan penskalaan terhadap lonjakan lalu lintas lokal
Kami juga melakukan pembaruan pada granularitas per objek, bukan per bidang (karena alasan historis). Ini menambah jumlah data yang terlibat dalam pemuatan ulang invalidasi.
Kami menggunakan murmur hash 128-bit untuk menghindari kolisi.
Perlu dicatat, kami telah menggunakan kompresi websocket untuk waktu yang sangat lama, yang mungkin mengurangi efek yang dirasakan klien.
Perlu dicatat, kami juga awalnya memiliki beberapa isu terkait overhead hop jaringan. Setelah melakukan penyelidikan, kami menemukan bahwa sebagian besar overhead sebenarnya disebabkan oleh service mesh kami (Istio/Envoy) dan kami melakukan beberapa penyesuaian terfokus untuk meningkatkan kinerja di sini.
Faktor penting dalam memori yang disimpan adalah menulis ulang penyimpanan yang dapat disinkronkan di sisi server kami agar hanya menyimpan hash data. Itu bukan masalah yang mudah dan kami mungkin akan merilis postingan lanjutan tentang hal ini! Tanpa ini, data klien yang disimpan membuat session broker menggunakan memori yang jauh lebih besar.
Sebelum ini, semua server sinkronisasi berjalan pada instans yang dioptimalkan untuk memori. Di sisi lain, loader yang dapat disinkronkan mendapatkan manfaat dari tipe node yang lebih kaya CPU seperti instans campuran.
Atribut penting dari menjalankan TS pada GraalVM dengan cara ini adalah bahwa ia menggunakan cache kode yang sedikit lebih banyak daripada yang biasa digunakan dalam aplikasi Scala/Java JVM yang lebih standar.
Sejauh yang kami ketahui, polimorfisme yang tinggi membuat kompilasi TS lebih mahal. Kami sedang mempertimbangkan untuk beralih ke antarmuka monomorfik dan memanfaatkan pelaporan spesialisasi polimorfik