Menulis modul kedaluwarsa kami yang terbatas untuk tarantool

Menulis modul kedaluwarsa kami yang terbatas untuk tarantool

Beberapa waktu lalu kita dihadapkan pada masalah pembersihan tupel di ruang angkasa alat taran. Pembersihan harus dimulai bukan ketika tarantool sudah kehabisan memori, tetapi terlebih dahulu dan pada frekuensi tertentu. Untuk tugas ini, tarantool memiliki modul yang ditulis dalam Lua bernama kadaluwarsa. Setelah menggunakan modul ini untuk waktu yang singkat, kami menyadari bahwa modul ini tidak cocok untuk kami: karena pembersihan data dalam jumlah besar secara terus-menerus, Lua hang di GC. Oleh karena itu, kami berpikir untuk mengembangkan modul kadaluarsa terbatas kami sendiri, dengan harapan bahwa kode yang ditulis dalam bahasa pemrograman asli akan menyelesaikan masalah kami dengan cara terbaik.

Contoh yang baik bagi kami adalah modul tarantool yang disebut memcached. Pendekatan yang digunakan di dalamnya didasarkan pada fakta bahwa bidang terpisah dibuat di ruang tersebut, yang menunjukkan masa pakai tupel, dengan kata lain, ttl. Modul di latar belakang memindai ruang, membandingkan TTL dengan waktu saat ini dan memutuskan apakah akan menghapus tupel atau tidak. Kode modul memcached sederhana dan elegan, tetapi terlalu umum. Pertama, ini tidak memperhitungkan jenis indeks yang sedang dirayapi dan dihapus. Kedua, pada setiap lintasan, semua tupel dipindai, yang jumlahnya bisa sangat besar. Dan jika dalam modul kadaluarsa masalah pertama terpecahkan (indeks pohon dipisahkan menjadi kelas terpisah), maka masalah kedua masih tidak mendapat perhatian. Tiga poin ini telah menentukan pilihan untuk menulis kode saya sendiri.

ОписаниС

Dokumentasi untuk tarantool sangat bagus tutorial 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 muncul di bawah. Hal ini juga perlu diperhatikan referensi ke objek yang tersedia saat menulis modul terbatas Anda sendiri, yaitu kotak, serat, indeks ΠΈ terima kasih.

Mari kita mulai dari jauh dan melihat seperti apa modul kedaluwarsa yang dibatasi 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 mempermudah, kami meluncurkan tarantool di direktori tempat perpustakaan libcapped-expirationd.so kami berada. Dua fungsi diekspor dari perpustakaan: start dan kill. Langkah pertama adalah 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, dan yang ketiga adalah masa pakai tupel. Kami membuat indeks pohon di atas bidang pertama dan menyebutnya sebagai bidang utama. Selanjutnya kita mendapatkan objek koneksi ke perpustakaan asli kita.

Setelah pekerjaan persiapan, jalankan 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 selama pemindaian dengan cara yang persis sama dengan modul kedaluwarsa yang ditulis dalam Lua. Argumen pertama pada fungsi start adalah nama unik tugas. Yang kedua adalah pengidentifikasi ruang. Yang ketiga adalah indeks unik dimana tupel akan dihapus. Yang keempat adalah indeks dimana tupel akan dilintasi. Yang kelima adalah nomor field tupel dengan masa hidup (penomoran dimulai dari 1, bukan 0!). Yang keenam dan ketujuh adalah pengaturan pemindaian. 1024 adalah jumlah maksimum tupel yang dapat dilihat dalam satu transaksi. 3600 β€” waktu pemindaian penuh dalam hitungan detik.

Perhatikan bahwa contoh ini menggunakan indeks yang sama untuk perayapan dan penghapusan. Jika ini adalah indeks pohon, maka traversal dilakukan dari kunci yang lebih kecil ke kunci yang lebih besar. Jika ada yang lain, misalnya indeks hash, maka traversal biasanya dilakukan dalam urutan acak. Semua tupel luar angkasa dipindai dalam satu pemindaian.

Mari kita masukkan beberapa tupel ke dalam ruang dengan masa hidup 60 detik:

box.space.tester:insert{0, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{1, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{2, '[email protected]', math.floor(fiber.time()) + 60}

Mari kita periksa apakah penyisipan berhasil:

tarantool> box.space.tester.index.primary:select()
---
- - [0, '[email protected]', 1576418976]
  - [1, '[email protected]', 1576418976]
  - [2, '[email protected]', 1576418976]
...

Mari ulangi pemilihan setelah 60+ detik (dihitung dari awal penyisipan tupel pertama) dan lihat bahwa modul kedaluwarsa yang dibatasi telah diproses:

tarantool> box.space.tester.index.primary:select()
---
  - []
...

Mari kita hentikan tugasnya:

capped_connection:call('libcapped-expirationd.kill', {'non-indexed'})

Mari kita lihat contoh kedua di mana indeks terpisah digunakan untuk perayapan:

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)

Semuanya di sini sama seperti pada contoh pertama, dengan beberapa pengecualian. Kami membangun indeks pohon di atas bidang ketiga dan menyebutnya exp. Indeks ini tidak harus unik, berbeda dengan indeks yang disebut primer. Traversal akan dilakukan berdasarkan indeks exp, dan penghapusan berdasarkan indeks utama. Kita ingat sebelumnya keduanya dilakukan hanya dengan menggunakan indeks utama.

Setelah pekerjaan persiapan, kami menjalankan fungsi start 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})

Mari kita masukkan lagi beberapa tupel ke dalam ruang dengan masa pakai 60 detik:

box.space.tester:insert{0, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{1, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{2, '[email protected]', math.floor(fiber.time()) + 60}

Setelah 30 detik, dengan analogi, kami akan menambahkan beberapa tupel lagi:

box.space.tester:insert{3, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{4, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{5, '[email protected]', math.floor(fiber.time()) + 60}

Mari kita periksa apakah penyisipan berhasil:

tarantool> box.space.tester.index.primary:select()
---
- - [0, '[email protected]', 1576421257]
  - [1, '[email protected]', 1576421257]
  - [2, '[email protected]', 1576421257]
  - [3, '[email protected]', 1576421287]
  - [4, '[email protected]', 1576421287]
  - [5, '[email protected]', 1576421287]
...

Mari ulangi pemilihan setelah 60+ detik (dihitung dari awal penyisipan tupel pertama) dan lihat bahwa modul kedaluwarsa yang dibatasi telah diproses:

tarantool> box.space.tester.index.primary:select()
---
- - [3, '[email protected]', 1576421287]
  - [4, '[email protected]', 1576421287]
  - [5, '[email protected]', 1576421287]
...

Masih ada beberapa tupel tersisa di ruang yang memiliki waktu sekitar 30 detik lagi untuk hidup. Selain itu, pemindaian berhenti ketika berpindah dari tupel dengan ID 2 dan seumur hidup 1576421257 ke tupel dengan ID 3 dan seumur hidup 1576421287. Tupel dengan seumur hidup 1576421287 atau lebih tidak dipindai karena pengurutan kunci indeks exp. Ini adalah penghematan yang ingin kami capai sejak awal.

Mari kita hentikan tugasnya:

capped_connection:call('libcapped-expirationd.kill', {'indexed'})

Implementasi

Cara terbaik untuk mengetahui semua fitur suatu proyek adalah dari sumber aslinya. kode! Sebagai bagian dari publikasi, kami hanya akan fokus pada poin terpenting, yaitu algoritma bypass ruang.

Argumen yang kita sampaikan ke metode start disimpan dalam struktur yang disebut expiredd_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 ruang. Atribut rm_index_id adalah pengidentifikasi indeks unik yang akan digunakan untuk menghapus tupel. Atribut it_index_id adalah pengidentifikasi indeks yang akan dilalui tupel. Atribut it_index_type adalah tipe indeks yang akan digunakan untuk melintasi tupel. Atribut filed_no adalah nomor bidang tupel dengan masa pakai. Atribut scan_size adalah jumlah maksimum tupel yang dipindai dalam satu transaksi. Atribut scan_time adalah waktu pemindaian penuh dalam hitungan detik.

Kami tidak akan mempertimbangkan penguraian argumen. Ini adalah pekerjaan yang melelahkan namun sederhana, yang mana perpustakaan akan membantu Anda pesan. Kesulitan hanya dapat muncul dengan indeks yang diteruskan dari Lua sebagai struktur data kompleks dengan tipe mp_map, dan tidak menggunakan tipe sederhana mp_bool, mp_double, mp_int, mp_uint dan mp_array. Namun tidak perlu mengurai seluruh indeks. Anda hanya perlu memeriksa keunikannya, menghitung jenisnya dan mengekstrak pengenalnya.

Kami mencantumkan prototipe semua fungsi yang digunakan untuk penguraian:

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);

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 dipindai dan dimodifikasi dalam satu transaksi. Jika berhasil, transaksi ini dilakukan; jika terjadi kesalahan, maka dibatalkan. Argumen terakhir pada fungsi expiredd_iterate adalah penunjuk ke iterator tempat pemindaian dimulai atau dilanjutkan. Iterator ini bertambah secara internal hingga terjadi kesalahan, ruang habis, atau proses tidak dapat dihentikan terlebih dahulu. Fungsi expiredd_expired memeriksa masa pakai tupel, expiredd_delete menghapus tupel, expiredd_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 expired_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 expired_breakable:

static bool
expirationd_breakable(struct expirationd_task *task)
{
  return task->it_index_id != task->rm_index_id && task->it_index_type == ITER_GT;
}

Web

Anda dapat melihat kode sumbernya di di sini!

Sumber: www.habr.com

Tambah komentar