Menulis modul tamat tempoh dihadkan kami sendiri untuk tarantool

Menulis modul tamat tempoh dihadkan kami sendiri untuk tarantool

Beberapa ketika dahulu kita berhadapan dengan masalah membersihkan tupel di ruang angkasa tarantool. Pembersihan harus dimulakan bukan apabila tarantool sudah kehabisan memori, tetapi lebih awal dan pada frekuensi tertentu. Untuk tugas ini, tarantool mempunyai modul yang ditulis dalam Lua dipanggil tamat tempoh. Selepas menggunakan modul ini untuk masa yang singkat, kami menyedari bahawa ia tidak sesuai untuk kami: disebabkan pembersihan berterusan sejumlah besar data, Lua digantung di GC. Oleh itu, kami berfikir untuk membangunkan modul tamat tempoh dihadkan kami sendiri, dengan harapan kod yang ditulis dalam bahasa pengaturcaraan asli akan menyelesaikan masalah kami dengan cara yang terbaik.

Contoh yang baik untuk kami ialah modul tarantool yang dipanggil memcached. Pendekatan yang digunakan di dalamnya adalah berdasarkan fakta bahawa medan berasingan dicipta dalam ruang, yang menunjukkan jangka hayat tupel, dengan kata lain, ttl. Modul di latar belakang mengimbas ruang, membandingkan TTL dengan masa semasa dan memutuskan sama ada untuk memadam tuple atau tidak. Kod modul memcached adalah ringkas dan elegan, tetapi terlalu generik. Pertama, ia tidak mengambil kira jenis indeks yang sedang dirangkak dan dipadamkan. Kedua, pada setiap pas semua tupel diimbas, bilangannya boleh agak besar. Dan jika dalam modul yang tamat tempoh masalah pertama telah diselesaikan (indeks pokok dipisahkan ke dalam kelas yang berasingan), maka yang kedua masih tidak mendapat perhatian. Tiga mata ini telah menentukan pilihan yang memihak kepada menulis kod saya sendiri.

ОписаниС

Dokumentasi untuk tarantool mempunyai yang sangat baik tutorial tentang cara menulis prosedur tersimpan anda dalam C. Pertama sekali, saya cadangkan anda membiasakan diri dengannya untuk memahami sisipan tersebut dengan arahan dan kod yang akan muncul di bawah. Ia juga patut diberi perhatian rujukan kepada objek yang tersedia semasa menulis modul berhad anda sendiri, iaitu kotak, serat, indeks ΠΈ txn.

Mari kita mulakan dari jauh dan lihat rupa modul yang telah tamat tempoh dihadkan 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 memudahkan, kami melancarkan tarantool dalam direktori tempat perpustakaan libcapped-expirationd.so kami berada. Dua fungsi dieksport dari perpustakaan: mulakan dan bunuh. Langkah pertama ialah menjadikan fungsi ini tersedia daripada Lua menggunakan box.schema.func.create dan box.schema.user.grant. Kemudian buat ruang yang tupel akan mengandungi hanya tiga medan: yang pertama ialah pengecam unik, yang kedua ialah e-mel dan yang ketiga ialah jangka hayat tupel. Kami membina indeks pokok di atas medan pertama dan memanggilnya utama. Seterusnya kami mendapat objek sambungan ke perpustakaan asli kami.

Selepas kerja persediaan, jalankan fungsi mula:

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 berfungsi semasa pengimbasan sama seperti modul tamat tempoh, yang ditulis dalam Lua. Argumen pertama untuk fungsi permulaan ialah nama unik tugas. Yang kedua ialah pengecam ruang. Yang ketiga ialah indeks unik yang mana tupel akan dipadamkan. Keempat ialah indeks di mana tupel akan dilalui. Yang kelima ialah bilangan medan tupel dengan seumur hidup (penomboran bermula dari 1, bukan 0!). Yang keenam dan ketujuh ialah tetapan pengimbasan. 1024 ialah bilangan maksimum tupel yang boleh dilihat dalam satu transaksi. 3600 β€” masa imbasan penuh dalam beberapa saat.

Ambil perhatian bahawa contoh menggunakan indeks yang sama untuk merangkak dan memadam. Jika ini adalah indeks pokok, maka traversal dijalankan dari kunci yang lebih kecil kepada yang lebih besar. Jika terdapat beberapa yang lain, sebagai contoh, indeks hash, maka traversal dijalankan, sebagai peraturan, dalam susunan rawak. Semua tupel ruang diimbas dalam satu imbasan.

Mari masukkan beberapa tupel ke dalam ruang dengan seumur hidup 60 saat:

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 semak sama ada sisipan berjaya:

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

Mari ulangi pilihan selepas 60+ saat (mengira dari permulaan sisipan tuple pertama) dan lihat bahawa modul yang telah tamat tempoh dihadkan telah diproses:

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

Mari hentikan tugas:

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

Mari lihat contoh kedua di mana indeks berasingan 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)

Segala-galanya di sini adalah sama seperti dalam contoh pertama, dengan beberapa pengecualian. Kami membina indeks pokok di atas medan ketiga dan memanggilnya exp. Indeks ini tidak semestinya unik, tidak seperti indeks yang dipanggil primer. Traversal akan dijalankan oleh indeks exp, dan pemadaman oleh utama. Kami ingat bahawa sebelum ini kedua-duanya dilakukan hanya menggunakan indeks utama.

Selepas kerja persediaan, kami menjalankan fungsi mula dengan hujah baharu:

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 masukkan beberapa tupel ke dalam ruang sekali lagi dengan seumur hidup 60 saat:

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}

Selepas 30 saat, dengan analogi, kami akan menambah 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 semak sama ada sisipan berjaya:

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 pilihan selepas 60+ saat (mengira dari permulaan sisipan tuple pertama) dan lihat bahawa modul yang telah tamat tempoh dihadkan telah diproses:

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

Masih terdapat beberapa tupel yang tinggal di angkasa yang akan mempunyai kira-kira 30 saat lagi untuk hidup. Selain itu, imbasan berhenti apabila berpindah dari tupel dengan ID 2 dan seumur hidup 1576421257 ke tupel dengan ID 3 dan seumur hidup 1576421287. Tuple dengan seumur hidup 1576421287 atau lebih tidak diimbas disebabkan oleh pesanan kunci indeks exp. Ini adalah penjimatan yang kami mahu capai pada awal-awal lagi.

Mari hentikan tugas:

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

РСализация

Cara terbaik untuk memberitahu tentang semua ciri projek ialah sumber asalnya. kod! Sebagai sebahagian daripada penerbitan, kami akan menumpukan hanya pada perkara yang paling penting, iaitu, algoritma pintasan ruang.

Argumen yang kami hantar ke kaedah mula disimpan dalam struktur yang dipanggil 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 ialah nama tugas. Atribut space_id ialah pengecam ruang. Atribut rm_index_id ialah pengecam indeks unik yang mana tupel akan dipadamkan. Atribut it_index_id ialah pengecam indeks yang mana tupel akan dilalui. Atribut it_index_type ialah jenis indeks yang mana tupel akan dilalui. Atribut filed_no ialah bilangan medan tupel dengan seumur hidup. Atribut scan_size ialah bilangan maksimum tupel yang diimbas dalam satu transaksi. Atribut scan_time ialah masa imbasan penuh dalam beberapa saat.

Kami tidak akan mempertimbangkan menghuraikan hujah. Ini adalah kerja yang susah payah tetapi mudah, yang mana perpustakaan akan membantu anda msgpuck. Kesukaran hanya boleh timbul dengan indeks yang dihantar dari Lua sebagai struktur data yang kompleks dengan jenis mp_map, dan tidak menggunakan jenis mudah mp_bool, mp_double, mp_int, mp_uint dan mp_array. Tetapi tidak perlu menghuraikan keseluruhan indeks. Anda hanya perlu menyemak keunikannya, mengira jenis dan mengekstrak pengecam.

Kami menyenaraikan prototaip semua fungsi yang digunakan untuk menghurai:

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 kepada perkara yang paling penting - logik memintas ruang dan memadam tupel. Setiap blok tupel tidak lebih besar daripada scan_size diimbas dan diubah suai di bawah satu transaksi. Jika berjaya, urus niaga ini dilakukan; jika ralat berlaku, ia akan ditarik balik. Argumen terakhir untuk fungsi expirationd_iterate ialah penunjuk kepada iterator dari mana pengimbasan bermula atau diteruskan. Iterator ini ditambah secara dalaman sehingga ralat berlaku, ruang kehabisan, atau tidak mungkin untuk menghentikan proses terlebih dahulu. Fungsi expirationd_expired menyemak hayat tuple, expirationd_delete memadam tuple, expirationd_breakable menyemak sama ada kita perlu meneruskan.

Kod 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;
}

Kod 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;
}

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

Kod fungsi expiration_breakable:

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

Permohonan

Anda boleh melihat kod sumber di di sini!

Sumber: www.habr.com

Tambah komen