Mendobrak hambatan skala: Bagaimana kami mengoptimalkan penggunaan Elasticsearch di Intercom

Diterbitkan: 2022-09-22

Elasticsearch adalah bagian tak terpisahkan dari Intercom.

Ini mendukung fitur inti Intercom seperti Kotak Masuk, Tampilan Kotak Masuk, API, Artikel, daftar pengguna, pelaporan, Bot Resolusi, dan sistem pencatatan internal kami. Cluster Elasticsearch kami berisi lebih dari 350TB data pelanggan, menyimpan lebih dari 300 miliar dokumen, dan melayani lebih dari 60 ribu permintaan per detik secara maksimal.

Seiring meningkatnya penggunaan Elasticsearch Intercom, kami perlu memastikan skala sistem kami untuk mendukung pertumbuhan berkelanjutan kami. Dengan peluncuran Inbox generasi berikutnya baru-baru ini, keandalan Elasticsearch menjadi lebih penting dari sebelumnya.

Kami memutuskan untuk mengatasi masalah dengan penyiapan Elasticsearch kami yang menimbulkan risiko ketersediaan dan mengancam downtime di masa mendatang: distribusi lalu lintas/pekerjaan yang tidak merata antara node dalam klaster Elasticsearch kami.

Tanda-tanda awal inefisiensi: Ketidakseimbangan beban

Elasticsearch memungkinkan Anda untuk menskalakan secara horizontal dengan meningkatkan jumlah node yang menyimpan data (data node). Kami mulai melihat ketidakseimbangan beban di antara node data ini: beberapa di antaranya berada di bawah tekanan lebih (atau "lebih panas") daripada yang lain karena penggunaan disk atau CPU yang lebih tinggi.

Gbr. 1 Ketidakseimbangan dalam penggunaan CPU
(Gbr. 1) Ketidakseimbangan dalam penggunaan CPU: Dua hot node dengan ~20% utilisasi CPU lebih tinggi dari rata-rata.

Logika penempatan pecahan bawaan Elasticsearch membuat keputusan berdasarkan perhitungan yang secara kasar memperkirakan ruang disk yang tersedia di setiap simpul dan jumlah pecahan indeks per simpul. Pemanfaatan sumber daya oleh pecahan tidak menjadi faktor dalam perhitungan ini. Akibatnya, beberapa node dapat menerima lebih banyak pecahan yang haus sumber daya dan menjadi "panas". Setiap permintaan pencarian diproses oleh beberapa node data. Sebuah node panas yang didorong melebihi batas selama lalu lintas puncak dapat menyebabkan penurunan kinerja untuk seluruh cluster.

Alasan umum untuk hot node adalah logika penempatan shard yang menetapkan shard besar (berdasarkan penggunaan disk) ke cluster, membuat alokasi yang seimbang lebih kecil kemungkinannya. Biasanya, sebuah node mungkin diberi satu pecahan besar lebih dari yang lain, membuatnya lebih panas dalam pemanfaatan disk. Kehadiran shard besar juga menghalangi kemampuan kami untuk menskalakan cluster secara bertahap, karena menambahkan node data tidak menjamin pengurangan beban dari semua hot node (Gbr. 2).

Gbr. 4 Menambahkan node

(Gbr. 2) Penambahan node data tidak menghasilkan pengurangan beban pada Host A. Menambahkan node lain akan mengurangi beban pada Host A, tetapi cluster akan tetap memiliki distribusi beban yang tidak merata.

Sebaliknya, memiliki shard yang lebih kecil membantu mengurangi beban pada semua node data saat kluster menskala – termasuk yang "panas" (Gbr. 3).

Gbr. 3 Banyak pecahan yang lebih kecil

(Gbr. 3) Memiliki banyak shard yang lebih kecil membantu mengurangi beban pada semua node data.

Catatan: masalahnya tidak terbatas pada cluster dengan pecahan berukuran besar. Kami akan mengamati perilaku serupa jika kami mengganti "ukuran" dengan "penggunaan CPU" atau "lalu lintas pencarian", tetapi membandingkan ukuran membuatnya lebih mudah untuk divisualisasikan.

Selain memengaruhi stabilitas cluster, ketidakseimbangan beban memengaruhi kemampuan kami untuk menskalakan secara hemat biaya. Kami selalu harus menambahkan lebih banyak kapasitas daripada yang dibutuhkan untuk menjaga node yang lebih panas di bawah level berbahaya. Memperbaiki masalah ini berarti ketersediaan yang lebih baik dan penghematan biaya yang signifikan dari pemanfaatan infrastruktur kami secara lebih efisien.

Pemahaman mendalam kami tentang masalah ini membantu kami menyadari bahwa beban dapat didistribusikan secara lebih merata jika kami memiliki:

  • Lebih banyak pecahan relatif terhadap jumlah node data. Ini akan memastikan bahwa sebagian besar node menerima jumlah pecahan yang sama.
  • Pecahan yang lebih kecil relatif terhadap ukuran node data. Jika beberapa node diberi beberapa pecahan tambahan, itu tidak akan menghasilkan peningkatan beban yang berarti untuk node tersebut.

Solusi cupcake: Lebih sedikit node yang lebih besar

Rasio jumlah shard terhadap jumlah node data, dan ukuran shard dengan ukuran node data, dapat diubah dengan memiliki jumlah shard yang lebih kecil dalam jumlah yang lebih besar. Tapi itu bisa di-tweak lebih mudah dengan pindah ke node data yang lebih sedikit tapi lebih besar.

Kami memutuskan untuk memulai dengan kue mangkuk untuk memverifikasi hipotesis ini. Kami memigrasikan beberapa cluster kami ke instance yang lebih besar dan lebih kuat dengan lebih sedikit node – mempertahankan kapasitas total yang sama. Misalnya, kami memindahkan kluster dari 40 instans 4xlarge ke 10 instans 16xlarge, mengurangi ketidakseimbangan beban dengan mendistribusikan shard secara lebih merata.

Gbr. 4 Distribusi beban yang lebih baik di seluruh disk dan CPU

(Gbr. 4) Distribusi beban yang lebih baik di seluruh disk dan CPU dengan berpindah ke node yang lebih kecil dan lebih besar.

Mitigasi node besar yang lebih sedikit memvalidasi asumsi kami bahwa mengubah jumlah dan ukuran node data dapat meningkatkan distribusi beban. Kami bisa saja berhenti di sana, tetapi ada beberapa kelemahan dari pendekatan ini:

  • Kami tahu ketidakseimbangan beban akan muncul lagi saat shard tumbuh lebih besar dari waktu ke waktu, atau jika lebih banyak node ditambahkan ke cluster untuk memperhitungkan peningkatan lalu lintas.
  • Node yang lebih besar membuat penskalaan inkremental lebih mahal. Menambahkan satu node sekarang akan lebih mahal, bahkan jika kami hanya membutuhkan sedikit kapasitas ekstra.

Tantangan: Melewati ambang batas Compressed Ordinary Object Pointers (OOPs)

Pindah ke node yang lebih kecil dan lebih besar tidak sesederhana hanya mengubah ukuran instans. Hambatan yang kami hadapi adalah mempertahankan ukuran tumpukan total yang tersedia (ukuran tumpukan pada satu node x jumlah total node) saat kami bermigrasi.

Kami telah membatasi ukuran tumpukan di node data kami menjadi ~30,5 GB, seperti yang disarankan oleh Elastic, untuk memastikan kami tetap berada di bawah batas sehingga JVM dapat menggunakan OOP terkompresi. Jika kami membatasi ukuran heap menjadi ~30,5 GB setelah pindah ke node yang lebih sedikit dan lebih besar, kami akan mengurangi kapasitas heap kami secara keseluruhan karena kami akan bekerja dengan lebih sedikit node.

“Instance yang kami migrasikan sangat besar dan kami ingin menetapkan sebagian besar RAM ke heap sehingga kami memiliki ruang untuk pointer, dengan sisa yang cukup untuk cache sistem file”

Kami tidak dapat menemukan banyak saran tentang dampak melewati ambang ini. Instance tempat kami bermigrasi sangat besar dan kami ingin menetapkan sebagian besar RAM ke heap sehingga kami memiliki ruang untuk pointer, dengan sisa yang cukup untuk cache sistem file. Kami bereksperimen dengan beberapa ambang batas dengan mereplikasi lalu lintas produksi kami untuk menguji kluster, dan menetapkan ~33% hingga ~42% RAM sebagai ukuran tumpukan untuk mesin dengan RAM lebih dari 200 GB.

Perubahan ukuran tumpukan berdampak pada berbagai klaster secara berbeda. Sementara beberapa cluster tidak menunjukkan perubahan dalam metrik seperti "JVM % heap in use" atau "Young GC Collection Time", tren umumnya adalah peningkatan. Terlepas dari itu, secara keseluruhan ini adalah pengalaman yang positif, dan cluster kami telah berjalan selama lebih dari 9 bulan dengan konfigurasi ini – tanpa masalah.

Perbaikan jangka panjang: Banyak pecahan yang lebih kecil

Solusi jangka panjang adalah bergerak ke arah memiliki lebih banyak pecahan yang lebih kecil dibandingkan dengan jumlah dan ukuran node data. Kita bisa mendapatkan pecahan yang lebih kecil dengan dua cara:

  • Migrasi indeks untuk memiliki lebih banyak pecahan utama: ini mendistribusikan data dalam indeks di antara lebih banyak pecahan.
  • Memecah indeks menjadi indeks yang lebih kecil (partisi): ini mendistribusikan data dalam indeks di antara lebih banyak indeks.

Penting untuk dicatat bahwa kita tidak ingin membuat sejuta pecahan kecil, atau memiliki ratusan partisi. Setiap indeks dan pecahan memerlukan beberapa memori dan sumber daya CPU.

“Kami berfokus untuk mempermudah eksperimen dan memperbaiki konfigurasi suboptimal dalam sistem kami, daripada terpaku pada konfigurasi 'sempurna'”

Dalam kebanyakan kasus, satu set kecil pecahan besar menggunakan lebih sedikit sumber daya daripada banyak pecahan kecil. Tetapi ada opsi lain – eksperimen akan membantu Anda mencapai konfigurasi yang lebih sesuai untuk kasus penggunaan Anda.

Untuk membuat sistem kami lebih tangguh, kami berfokus untuk mempermudah eksperimen dan memperbaiki konfigurasi suboptimal dalam sistem kami, daripada terpaku pada konfigurasi "sempurna".

Indeks partisi

Meningkatkan jumlah pecahan utama terkadang dapat memengaruhi kinerja kueri yang menggabungkan data, yang kami alami saat memigrasikan klaster yang bertanggung jawab untuk produk Pelaporan Intercom. Sebaliknya, mempartisi indeks menjadi beberapa indeks mendistribusikan beban ke lebih banyak pecahan tanpa menurunkan kinerja kueri.

Intercom tidak memiliki persyaratan untuk menempatkan data bersama untuk banyak pelanggan, jadi kami memilih untuk mempartisi berdasarkan ID unik pelanggan. Ini membantu kami memberikan nilai lebih cepat dengan menyederhanakan logika partisi dan mengurangi pengaturan yang diperlukan.

“Untuk mempartisi data dengan cara yang paling tidak memengaruhi kebiasaan dan metode teknisi kami, pertama-tama kami menginvestasikan banyak waktu untuk memahami bagaimana teknisi kami menggunakan Elasticsearch”

Untuk mempartisi data dengan cara yang paling tidak memengaruhi kebiasaan dan metode teknisi kami, pertama-tama kami menginvestasikan banyak waktu untuk memahami bagaimana teknisi kami menggunakan Elasticsearch. Kami sangat mengintegrasikan sistem observabilitas kami ke dalam pustaka klien Elasticsearch dan menyapu basis kode kami untuk mempelajari semua cara berbeda yang dilakukan tim kami dalam berinteraksi dengan API Elasticsearch.

Mode pemulihan kegagalan kami adalah mencoba kembali permintaan, jadi kami membuat perubahan yang diperlukan saat kami membuat permintaan non-idempoten. Kami akhirnya menambahkan beberapa linter untuk mencegah penggunaan API seperti `update/delete_by_query`, karena mempermudah membuat permintaan non-idempoten.

Kami membangun dua kemampuan yang bekerja bersama untuk menghadirkan fungsionalitas penuh:

  • Cara untuk merutekan permintaan dari satu indeks ke indeks lainnya. Indeks lain ini bisa berupa partisi, atau hanya indeks yang tidak dipartisi.
  • Cara untuk menulis ganda data ke beberapa indeks. Ini memungkinkan kami untuk menjaga agar partisi tetap sinkron dengan indeks yang dimigrasikan.

“Kami mengoptimalkan proses kami untuk meminimalkan radius ledakan dari setiap insiden, tanpa mengurangi kecepatan”

Secara keseluruhan, proses migrasi indeks ke partisi terlihat seperti ini:

  1. Kami membuat partisi baru dan mengaktifkan penulisan ganda sehingga partisi kami tetap up-to-date dengan indeks asli.
  2. Kami memicu pengisian ulang semua data. Permintaan pengisian ulang ini akan ditulis ganda ke partisi baru.
  3. Saat pengisian ulang selesai, kami memvalidasi bahwa indeks lama dan baru memiliki data yang sama. Jika semuanya terlihat baik-baik saja, kami menggunakan flag fitur untuk mulai menggunakan partisi untuk beberapa pelanggan dan memantau hasilnya.
  4. Setelah kami yakin, kami memindahkan semua pelanggan kami ke partisi, sambil menulis ganda ke indeks lama dan partisi.
  5. Ketika kami yakin bahwa migrasi telah berhasil, kami menghentikan penulisan ganda dan menghapus file index.

Langkah-langkah yang tampaknya sederhana ini memiliki banyak kerumitan. Kami mengoptimalkan proses kami untuk meminimalkan radius ledakan dari setiap insiden, tanpa mengurangi kecepatan.

Menuai manfaat

Pekerjaan ini membantu kami meningkatkan keseimbangan beban di kluster Elasticsearch kami. Lebih penting lagi, kami sekarang dapat meningkatkan distribusi beban setiap kali menjadi tidak dapat diterima dengan memigrasi indeks ke partisi dengan shard primer yang lebih sedikit, mencapai yang terbaik dari kedua dunia: shard lebih sedikit dan lebih kecil per indeks.

Menerapkan pembelajaran ini, kami dapat membuka keuntungan dan penghematan kinerja yang penting.

  • Kami mengurangi biaya dua klaster kami masing-masing sebesar 40% dan 25%, dan melihat penghematan biaya yang signifikan pada klaster lain juga.
  • Kami mengurangi penggunaan CPU rata-rata untuk kluster tertentu sebesar 25% dan meningkatkan latensi permintaan median sebesar 100%. Kami mencapai ini dengan memigrasikan indeks lalu lintas tinggi ke partisi dengan lebih sedikit shard utama per partisi dibandingkan dengan yang asli.
  • Kemampuan umum untuk memigrasikan indeks juga memungkinkan kami mengubah skema indeks, memungkinkan Product Engineers untuk membangun pengalaman yang lebih baik bagi pelanggan kami, atau mengindeks ulang data menggunakan versi Lucene yang lebih baru yang membuka kemampuan kami untuk meningkatkan ke Elasticsearch 8.

Gbr. 5 Peningkatan ketidakseimbangan beban dan pemanfaatan CPU

(Gbr. 5) 50% peningkatan ketidakseimbangan beban dan 25% peningkatan penggunaan CPU dengan memigrasikan indeks lalu lintas tinggi ke partisi dengan shard primer yang lebih sedikit per partisi.

Gbr.6 Latensi permintaan rata-rata

(Gbr. 6) Latensi Permintaan Median meningkat rata-rata 100% dengan memigrasikan indeks lalu lintas tinggi ke partisi dengan shard primer yang lebih sedikit per partisi.

Apa berikutnya?

Memperkenalkan Elasticsearch untuk memperkuat produk dan fitur baru harus mudah. Visi kami adalah mempermudah teknisi kami untuk berinteraksi dengan Elasticsearch sebagaimana kerangka kerja web modern membuatnya untuk berinteraksi dengan database relasional. Seharusnya mudah bagi tim untuk membuat indeks, membaca atau menulis dari indeks, membuat perubahan pada skemanya, dan banyak lagi – tanpa harus khawatir tentang bagaimana permintaan dilayani.

Apakah Anda tertarik dengan cara tim Teknik kami bekerja di Intercom? Pelajari lebih lanjut dan lihat peran terbuka kami di sini.

Karir CTA - Teknik (horizontal)