
Beberapa waktu lalu, kami menghadapi masalah membersihkan tuple di ruang tarantool. Pembersihan harus dimulai bukan ketika tarantool sudah kehabisan memori, tetapi di muka dan dengan frekuensi tertentu. Tarantool memiliki modul yang ditulis dalam Lua untuk tugas ini yang disebut expirationd . Setelah penggunaan modul ini singkat, kami menyadari bahwa itu tidak cocok untuk kami: pada pembersihan data dalam jumlah besar secara konstan, Lua digantung di GC. Oleh karena itu, kami berpikir untuk mengembangkan modul kedaluwarsa capped kami, berharap kode yang ditulis dalam bahasa pemrograman asli akan menyelesaikan masalah kami dengan cara terbaik.
Contoh yang bagus adalah modul tarantool yang disebut memcached . Pendekatan yang digunakan di dalamnya didasarkan pada fakta bahwa bidang terpisah dimasukkan dalam ruang di mana masa hidup tuple ditunjukkan, dengan kata lain, ttl. Modul di latar belakang memindai ruang, membandingkan ttl dengan waktu saat ini dan memutuskan apakah akan menghapus tuple atau tidak. Kode modul memcached sederhana dan elegan, tetapi terlalu umum. Pertama, itu tidak mempertimbangkan jenis indeks yang digunakan untuk menjelajah dan menghapus. Kedua, pada setiap pass, semua tuple dipindai, yang jumlahnya bisa cukup besar. Dan jika dalam modul kedaluwarsa masalah pertama diselesaikan (indeks pohon dialokasikan dalam kelas yang terpisah), maka yang kedua masih tidak menerima perhatian. Ini menentukan pilihan yang mendukung penulisan kode Anda sendiri.
Deskripsi
Dokumentasi untuk tarantool memiliki tutorial yang sangat baik tentang cara menulis prosedur tersimpan Anda di C. Pertama-tama, saya sarankan Anda membiasakan diri dengannya untuk memahami sisipan dengan perintah dan kode yang akan ditemukan di bawah ini. Perlu juga memperhatikan referensi ke objek yang tersedia saat menulis modul Anda sendiri, yaitu kotak , serat , indeks dan txn .
Mari kita mulai dari jauh dan melihat bagaimana modul kedaluwarsa capped terlihat dari luar:
fiber = require('fiber') net_box = require('net.box') box.cfg{listen = 3300} box.schema.func.create('libcapped-expirationd.start', {language = 'C'}) box.schema.user.grant('guest', 'execute', 'function', 'libcapped-expirationd.start') box.schema.func.create('libcapped-expirationd.kill', {language = 'C'}) box.schema.user.grant('guest', 'execute', 'function', 'libcapped-expirationd.kill') box.schema.space.create('tester') box.space.tester:create_index('primary', {unique = true, parts = {1, 'unsigned'}}) capped_connection = net_box:new(3300)
Untuk kesederhanaan, jalankan tarantool di direktori di mana perpustakaan kami libcapped-expirationd.so berada. Dua fungsi diekspor dari perpustakaan: memulai dan membunuh. Pertama, Anda perlu membuat fungsi-fungsi ini tersedia dari Lua menggunakan box.schema.func.create dan box.schema.user.grant. Kemudian buat ruang yang tupelnya hanya akan berisi tiga bidang: yang pertama adalah pengidentifikasi unik, yang kedua adalah email, yang ketiga adalah masa pakai tuple. Di atas bidang pertama, buat indeks pohon dan sebut itu utama. Selanjutnya, kita mendapatkan objek untuk terhubung ke perpustakaan asli kita.
Setelah pekerjaan persiapan, kami memulai fungsi start:
capped_connection:call('libcapped-expirationd.start', {'non-indexed', box.space.tester.id, box.space.tester.index.primary, box.space.tester.index.primary, 3, 1024, 3600})
Contoh ini akan bekerja pada pemindaian seperti modul kedaluwarsa, yang ditulis dalam Lua. Argumen pertama ke fungsi mulai adalah nama unik dari tugas tersebut. Yang kedua adalah pengidentifikasi ruang. Yang ketiga adalah indeks unik di mana tupel akan dihapus. Yang keempat adalah indeks di mana tupel akan dilewati. Kelima - jumlah bidang tuple dengan masa pakai (penomoran berjalan dari 1, bukan 0!). Pengaturan pemindaian keenam dan ketujuh. 1024 adalah jumlah maksimum tupel yang dapat dilihat dalam satu transaksi. 3600 - waktu pemindaian penuh dalam detik.
Perhatikan bahwa indeks yang sama digunakan untuk perayapan dan penghapusan dalam contoh. Jika ini adalah indeks pohon, maka perayapan adalah dari kunci yang lebih kecil ke yang lebih besar. Jika beberapa lainnya, misalnya, indeks hash, maka traversal dilakukan, sebagai suatu peraturan, dalam urutan acak. Dalam satu pemindaian, semua tuple ruang dilihat.
Mari kita masukkan beberapa tuple ke ruang angkasa dengan masa 60 detik:
box.space.tester:insert{0, 'user0@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{1, 'user1@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{2, 'user2@tarantool.io', math.floor(fiber.time()) + 60}
Pastikan sisipan berhasil:
tarantool> box.space.tester.index.primary:select()
Ulangi pemilihan setelah 60+ detik (hitung dari awal penyisipan tuple pertama) dan lihat bahwa modul kedaluwarsa yang di-capped telah berfungsi:
tarantool> box.space.tester.index.primary:select()
Hentikan tugas:
capped_connection:call('libcapped-expirationd.kill', {'non-indexed'})
Mari kita lihat contoh kedua di mana indeks terpisah digunakan untuk merangkak:
fiber = require('fiber') net_box = require('net.box') box.cfg{listen = 3300} box.schema.func.create('libcapped-expirationd.start', {language = 'C'}) box.schema.user.grant('guest', 'execute', 'function', 'libcapped-expirationd.start') box.schema.func.create('libcapped-expirationd.kill', {language = 'C'}) box.schema.user.grant('guest', 'execute', 'function', 'libcapped-expirationd.kill') box.schema.space.create('tester') box.space.tester:create_index('primary', {unique = true, parts = {1, 'unsigned'}}) box.space.tester:create_index('exp', {unique = false, parts = {3, 'unsigned'}}) capped_connection = net_box:new(3300)
Di sini semuanya sama seperti pada contoh pertama, dengan beberapa pengecualian. Di atas bidang ketiga kami membuat indeks pohon dan menyebutnya exp. Indeks ini tidak harus unik, tidak seperti indeks yang disebut primer. Bypass akan dilakukan pada indeks exp, dan penghapusan pada primer. Kami ingat bahwa sebelumnya, keduanya dilakukan hanya menggunakan indeks primer.
Setelah pekerjaan persiapan, kami memulai fungsi mulai dengan argumen baru:
capped_connection:call('libcapped-expirationd.start', {'indexed', box.space.tester.id, box.space.tester.index.primary, box.space.tester.index.exp, 3, 1024, 3600})
Sekali lagi, kami akan memasukkan beberapa tuple ke dalam ruang dengan masa hidup 60 detik:
box.space.tester:insert{0, 'user0@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{1, 'user1@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{2, 'user2@tarantool.io', math.floor(fiber.time()) + 60}
Setelah 30 detik, dengan analogi, tambahkan beberapa tupel:
box.space.tester:insert{3, 'user3@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{4, 'user4@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{5, 'user5@tarantool.io', math.floor(fiber.time()) + 60}
Pastikan sisipan berhasil:
tarantool> box.space.tester.index.primary:select()
Ulangi pemilihan setelah 60+ detik (hitung dari awal penyisipan tuple pertama) dan lihat bahwa modul kedaluwarsa yang di-capped telah berfungsi:
tarantool> box.space.tester.index.primary:select()
Tuples tetap berada di angkasa, yang hidup sekitar 30 detik. Selain itu, pemindaian berhenti ketika beralih dari tuple dengan pengenal 2 dan masa pakai 1576421257 ke tuple dengan pengenal 3 dan masa pakai 1576421287. Tuple dengan masa pakai 1576421287 dan lebih banyak lagi tidak dilihat karena pemesanan kunci indeks exp. Ini adalah tabungan yang ingin kami capai di awal.
Hentikan tugas:
capped_connection:call('libcapped-expirationd.kill', {'indexed'})
Implementasi
Yang terbaik dari semuanya, semua fitur proyek akan selalu memberi tahu kode sumbernya! Sebagai bagian dari publikasi, kami hanya akan fokus pada poin-poin terpenting, yaitu, algoritma pintas ruang.
Argumen yang kami berikan ke fungsi mulai disimpan dalam struktur yang disebut expirationd_task:
struct expirationd_task { char name[256]; uint32_t space_id; uint32_t rm_index_id; uint32_t it_index_id; uint32_t it_index_type; uint32_t field_no; uint32_t scan_size; uint32_t scan_time; };
Atribut nama adalah nama tugas. Atribut space_id adalah pengidentifikasi spasi. Atribut rm_index_id adalah pengidentifikasi indeks unik di mana tupel akan dihapus. Atribut it_index_id adalah pengidentifikasi indeks tempat tupel akan dirayapi. Atribut it_index_type adalah jenis indeks di mana tuple akan dilalui. Atribut yang diajukan adalah nomor bidang tuple dengan seumur hidup. Atribut scan_size adalah jumlah maksimum tupel yang dilihat dalam satu transaksi. Atribut scan_time adalah waktu untuk pemindaian penuh dalam hitungan detik.
Kami tidak akan mempertimbangkan argumen parsing. Ini adalah pekerjaan yang melelahkan tetapi tidak rumit di mana pustaka msgpuck akan membantu Anda. Kesulitan dapat muncul hanya dengan indeks yang ditransfer dari Lua sebagai struktur data yang kompleks dengan tipe mp_map, dan tidak dengan bantuan tipe sederhana mp_bool, mp_double, mp_int, mp_uint dan mp_array. Tetapi keseluruhan indeks tidak diuraikan. Cukup untuk memeriksa keunikannya, menghitung jenisnya, dan mengekstrak pengidentifikasi.
Kami mencantumkan prototipe semua fungsi yang digunakan untuk parsing:
bool expirationd_parse_name(struct expirationd_task *task, const char **pos); bool expirationd_parse_space_id(struct expirationd_task *task, const char **pos); bool expirationd_parse_rm_index_id(struct expirationd_task *task, const char **pos); bool expirationd_parse_rm_index_unique(struct expirationd_task *task, const char **pos); bool expirationd_parse_rm_index(struct expirationd_task *task, const char **pos); bool expirationd_parse_it_index_id(struct expirationd_task *task, const char **pos); bool expirationd_parse_it_index_type(struct expirationd_task *task, const char **pos); bool expirationd_parse_it_index(struct expirationd_task *task, const char **pos); bool expirationd_parse_field_no(struct expirationd_task *task, const char **pos); bool expirationd_parse_scan_size(struct expirationd_task *task, const char **pos); bool expirationd_parse_scan_time(struct expirationd_task *task, const char **pos);
Dan sekarang mari kita beralih ke hal yang paling penting - logika melewati ruang dan menghapus tupel. Setiap blok tupel yang tidak lebih besar dari scan_size dilihat dan diubah dalam satu transaksi. Jika berhasil, transaksi ini dilakukan, jika terjadi kesalahan, itu dibatalkan. Argumen terakhir ke fungsi expirationd_iterate adalah sebuah pointer ke iterator dari mana pemindaian dimulai atau berlanjut. Iterator ini bertambah di dalam sampai kesalahan terjadi, ruang berakhir, atau tidak ada kesempatan untuk menghentikan proses di muka. Fungsi expirationd_expired memeriksa masa hidup tuple, expirationd_delete - menghapus tuple, expirationd_breakable - memeriksa apakah kita perlu melanjutkan.
Kode fungsi Expirationd_iterate:
static bool expirationd_iterate(struct expirationd_task *task, box_iterator_t **iterp) { box_iterator_t *iter = *iterp; box_txn_begin(); for (uint32_t i = 0; i < task->scan_size; ++i) { box_tuple_t *tuple = NULL; if (box_iterator_next(iter, &tuple) < 0) { box_iterator_free(iter); *iterp = NULL; box_txn_rollback(); return false; } if (!tuple) { box_iterator_free(iter); *iterp = NULL; box_txn_commit(); return true; } if (expirationd_expired(task, tuple)) expirationd_delete(task, tuple); else if (expirationd_breakable(task)) break; } box_txn_commit(); return true; }
Kode fungsi Expirationd_expired:
static bool expirationd_expired(struct expirationd_task *task, box_tuple_t *tuple) { const char *buf = box_tuple_field(tuple, task->field_no - 1); if (!buf || mp_typeof(*buf) != MP_UINT) return false; uint64_t val = mp_decode_uint(&buf); if (val > fiber_time64() / 1000000) return false; return true; }
Kode fungsi expirationd_delete:
static void expirationd_delete(struct expirationd_task *task, box_tuple_t *tuple) { uint32_t len; const char *str = box_tuple_extract_key(tuple, task->space_id, task->rm_index_id, &len); box_delete(task->space_id, task->rm_index_id, str, str + len, NULL); }
Kode fungsi Expirationd_breakable:
static bool expirationd_breakable(struct expirationd_task *task) { return task->it_index_id != task->rm_index_id && task->it_index_type == ITER_GT; }
Aplikasi
Lihat kode sumber di sini !