Pendahuluan
Di Internet, ada banyak informasi dan perdebatan tentang pilihan pendekatan sql / nosql, serta pro dan kontra dari satu atau lain penyimpanan KV. Apa yang Anda baca saat ini bukanlah manual atau agitasi rockdb untuk menggunakan repositori ini dan driver saya untuk itu. Saya ingin berbagi hasil sementara dari mengoptimalkan proses pengembangan NIF untuk Erlang. Artikel ini menyajikan driver yang bisa diterapkan untuk rockdb, yang dikembangkan selama beberapa malam.
Jadi, dalam salah satu proyek tugas muncul pengolahan yang dapat diandalkan dari sejumlah besar peristiwa. Setiap acara berlangsung dari 50 hingga 350 byte, lebih dari 80 juta peristiwa dihasilkan per node per hari. Hanya ingin mencatat bahwa masalah toleransi kesalahan pengiriman pesan ke node tidak dipertimbangkan. Juga salah satu batasan pemrosesan adalah perubahan atom dan konsisten dari kelompok peristiwa.
Dengan demikian, persyaratan utama untuk pengemudi adalah:
- Keandalan
- Performa
- Keamanan (dalam arti kanonik)
- Fungsi:
- Semua fungsi dasar kv
- Keluarga kolom
- Transaksi
- Kompresi data
- Dukungan konfigurasi penyimpanan yang fleksibel
- Basis kode minimum
Ikhtisar solusi yang ada
- erocksdb adalah solusi dari pengembang leofs. Keuntungannya termasuk persetujuan dalam proyek nyata. Dengan kontra - basis kode yang ketinggalan jaman dan kurangnya transaksionalitas. Pengemudi ini didasarkan pada rocksdb 4.13.
- rockse memiliki sejumlah keterbatasan, misalnya, kurangnya opsi konfigurasi, tetapi yang paling penting, semua kunci dan nilai harus berupa string. Dia masuk ke review hanya sebagai contoh dari sejumlah driver yang mengimplementasikan satu atau yang lain fungsional dan membatasi yang lain.
- erlang-rocksdb adalah proyek berfitur lengkap, yang pengembangannya dimulai pada 2014. Seperti erocksdb digunakan dalam proyek nyata. Ini memiliki basis kode besar di C / C ++ dan fungsionalitas yang luas. Pengemudi ini cocok untuk praktik umum dan digunakan di sebagian besar proyek.
Setelah analisis cepat dari situasi saat ini dengan driver erlang untuk rocksdb, menjadi jelas bahwa tidak satupun dari mereka sepenuhnya memenuhi persyaratan proyek. Meskipun dimungkinkan untuk menggunakan erlang-rocksdb, ada beberapa malam yang bebas, dan setelah pengembangan dan implementasi filter Bloom yang sukses pada Rust dan rasa ingin tahu: apakah mungkin untuk menerapkan semua persyaratan proyek saat ini dan mengimplementasikan sebagian besar fungsi di NIF dalam waktu singkat?
Rocker
Rocker adalah NIF untuk Erlang, menggunakan pembungkus Rust untuk rocksdb. Fitur utamanya adalah keamanan, kinerja, dan basis kode minimal. Kunci dan data disimpan dalam bentuk biner, yang tidak memaksakan pembatasan pada format penyimpanan. Saat ini, proyek ini cocok untuk digunakan dalam solusi pihak ketiga.
Kode sumber ada di repositori proyek .
Tinjauan API
Pembukaan dasar
Bekerja dengan database dimungkinkan dalam dua mode:
Ruang kunci total. Dalam mode ini, semua kunci Anda akan ditempatkan dalam satu set. Rocksdb memungkinkan Anda mengkonfigurasi opsi penyimpanan secara fleksibel untuk tugas saat ini. Tergantung pada mereka, database dapat dibuka dengan dua cara:
menggunakan seperangkat opsi standar
rocker:open_default(<<"/project/priv/db_default_path">>) -> {ok, Db}.
Hasil dari operasi ini akan menjadi penunjuk untuk bekerja dengan database, dan database akan diblokir untuk setiap upaya lain untuk membuka. Basis data akan terbuka secara otomatis segera setelah menghapus pointer ini.
- baik mengkonfigurasi opsi untuk tugas tersebut
{ok, Db} = rocker:open(<<"/project/priv/db_path">>, #{ create_if_missing => true, set_max_open_files => 1000, set_use_fsync => false, set_bytes_per_sync => 8388608, optimize_for_point_lookup => 1024, set_table_cache_num_shard_bits => 6, set_max_write_buffer_number => 32, set_write_buffer_size => 536870912, set_target_file_size_base => 1073741824, set_min_write_buffer_number_to_merge => 4, set_level_zero_stop_writes_trigger => 2000, set_level_zero_slowdown_writes_trigger => 0, set_max_background_compactions => 4, set_max_background_flushes => 4, set_disable_auto_compactions => true, set_compaction_style => universal }).
- Pengelompokan menjadi beberapa ruang. Kunci disimpan dalam apa yang disebut keluarga kolom, dan setiap keluarga kolom dapat memiliki opsi yang berbeda. Mari kita perhatikan contoh membuka database dengan opsi standar untuk semua keluarga kolom
{ok, Db} = case rocker:list_cf(BookDbPath) of {ok, CfList} -> rocker:open_cf_default(BookDbPath, CfList); _Else -> CfList = [], rocker:open_default(BookDbPath) end.
Penghapusan basis
Untuk menghapus database dengan benar, Anda harus memanggil rocker:destroy(Path).
Dalam hal ini, pangkalan tidak boleh digunakan.
Pemulihan basis data setelah kegagalan
Jika terjadi kegagalan sistem, database dapat dipulihkan dengan menggunakan metode rocker:repair(Path)
.Proses ini terdiri dari 4 langkah:
- pencarian file
- mengembalikan tabel dengan memainkan WAL
- ekstrak metadata
- catatan deskriptor
Membuat keluarga kolom
Cf = <<"testcf1">>, rocker:create_cf_default(Db, Cf) -> ok.
Penghapusan keluarga kolom
Cf = <<"testcf1">>, rocker:drop_cf(Db, Cf) -> ok.
Operasi CRUD
Entri data kunci
rocker:put(Db, <<"key">>, <<"value">>) -> ok.
Mendapatkan data dengan kunci
rocker:get(Db, <<"key">>) -> {ok, <<"value">>} | notfound
Penghapusan data utama
rocker:delete(Db, <<"key">>) -> ok.
Entri data kunci dalam CF
rocker:put_cf(Db, <<"testcf">>, <<"key">>, <<"value">>) -> ok.
Pengambilan data utama dalam CF
rocker:get_cf(Db, <<"testcf">>, <<"key">>) -> {ok, <<"value">>} | notfound
Penghapusan Kunci CF
rocker:delete_cf(Db, <<"testcf">>, <<"key">>) -> ok
Iterator
Seperti yang Anda ketahui, salah satu prinsip dasar dari rockDB adalah penyimpanan kunci yang teratur. Fitur ini sangat berguna dalam tugas nyata. Untuk menggunakannya, kita membutuhkan iterator data. Rocksdb memiliki beberapa mode untuk meneruskan data (contoh kode terperinci dapat ditemukan dalam tes ):
- Dari awal tabel. Rocker bertanggung jawab untuk ini di iterator
{'start'}
- Dari ujung tabel:
{'end'}
- Mulai dari kunci maju tertentu
{'from', Key, forward}
- Mulai dari kunci khusus kembali
{'from', Key, reverse}
Perlu dicatat bahwa mode ini juga berfungsi untuk melewati data yang disimpan dalam keluarga kolom.
Buat iterator
rocker:iterator(Db, {'start'}) -> {ok, Iter}.
Periksa iterator
rocker:iterator_valid(Iter) -> {ok, true} | {ok, false}.
Membuat Iterator untuk CF
rocker:iterator_cf(Db, Cf, {'start'}) -> {ok, Iter}.
Membuat Iterator Awalan
Iterator awalan membutuhkan secara eksplisit menentukan panjang awalan saat membuat database.
{ok, Db} = rocker:open(Path, #{ prefix_length => 3 }).
Contoh membuat iterator menggunakan awalan “aaa”:
{ok, Iter} = rocker:prefix_iterator(Db, <<"aaa">>).
Membuat Iterator Awalan untuk CF
Mirip dengan iterator awalan sebelumnya, ia membutuhkan prefix_length
eksplisit untuk keluarga kolom
{ok, Iter} = rocker:prefix_iterator_cf(Db, Cf, <<"aaa">>).
Dapatkan item selanjutnya
Metode mengembalikan kunci / nilai berikutnya, atau ok jika iterator selesai.
rocker:next(Iter) -> {ok, <<"key">>, <<"value">>} | ok
Transaksi
Kejadian yang cukup umum adalah persyaratan untuk secara bersamaan merekam perubahan ke grup kunci. Rocker memungkinkan Anda untuk menggabungkan operasi CRUD baik dalam satu set bersama, maupun dalam CF.
Contoh ini menggambarkan bekerja dengan transaksi:
{ok, 6} = rocker:tx(Db, [ {put, <<"k1">>, <<"v1">>}, {put, <<"k2">>, <<"v2">>}, {delete, <<"k0">>, <<"v0">>}, {put_cf, Cf, <<"k1">>, <<"v1">>}, {put_cf, Cf, <<"k2">>, <<"v2">>}, {delete_cf, Cf, <<"k0">>, <<"v0">>} ]).
Performa
Anda dapat menemukan tes kinerja di suite tes. Ini menunjukkan sekitar 30k RPS untuk menulis dan 200k RPS untuk membaca di komputer saya. Dalam kondisi nyata, Anda dapat mengharapkan 15-20k RPS untuk menulis dan sekitar 120k RPS untuk membaca dengan ukuran data rata-rata sekitar 1 Kb per kunci dan jumlah total kunci lebih dari 1 miliar.
Kesimpulan
Pengembangan dan penerapan Rocker di proyek kami memungkinkan kami untuk mengurangi waktu respons sistem, meningkatkan keandalan, dan mengurangi waktu mulai ulang. Keuntungan ini diperoleh dengan biaya pengembangan dan implementasi yang minimal.
Bagi saya sendiri, saya menyimpulkan bahwa untuk proyek Erlang yang membutuhkan optimisasi, penggunaan Rust optimal. Erlang mengelola dengan cepat dan efisien menerapkan 95% dari kode, sementara Rust menulis ulang / menambah penghambatan 5% tanpa mengorbankan keandalan sistem secara keseluruhan.
PS Ada pengalaman positif dalam mengembangkan NIF untuk aritmatika presisi-Sewenang-wenang di Erlang, yang dapat ditulis dalam artikel terpisah. Saya ingin mengklarifikasi apakah topik NIF tentang Rust menarik bagi masyarakat?