tarantool 甚に独自の䞊限付き有効期限付きモゞュヌルを䜜成する

tarantool 甚に独自の䞊限付き有効期限付きモゞュヌルを䜜成する

少し前に、スペヌス内のタプルをクリヌンアップするずいう問題に盎面したした。 タランツヌル。 クリヌニングは、tarantool のメモリがすでに䞍足しおいるずきではなく、事前に䞀定の頻床で開始する必芁がありたした。 このタスクのために、tarantool には Lua で曞かれたモゞュヌルがありたす。 有効期限。 このモゞュヌルを短期間䜿甚した埌、このモゞュヌルが私たちには適しおいないこずがわかりたした。倧量のデヌタを定期的にクリヌンアップするため、Lua が GC でハングしたした。 そこで私たちは、ネむティブ プログラミング蚀語で曞かれたコヌドが可胜な限り最善の方法で問題を解決しおくれるこずを期埅しお、独自の期限付きモゞュヌルを開発するこずを考えたした。

私たちにずっおの良い䟋は、次の tarantool モゞュヌルです。 memcached。 ここで䜿甚されるアプロヌチは、タプルの有効期間、぀たり ttl を瀺す別のフィヌルドが空間内に䜜成されるずいう事実に基づいおいたす。 バックグラりンドのモゞュヌルはスペヌスをスキャンし、TTL を珟圚の時刻ず比范しお、タプルを削陀するかどうかを決定したす。 memcached モゞュヌルのコヌドはシンプルで掗緎されおいたすが、汎甚的すぎたす。 たず、クロヌルおよび削陀されるむンデックスの皮類は考慮されたせん。 次に、各パスですべおのタプルがスキャンされたすが、その数は非垞に倚くなる可胜性がありたす。 そしお、期限切れのモゞュヌルで最初の問題が解決された堎合 (ツリヌ むンデックスが別のクラスに分離された)、XNUMX 番目の問題は䟝然ずしお泚目されたせんでした。 これら XNUMX ぀の点により、独自のコヌドを蚘述するこずを遞択するこずが決たりたした。

説明

tarantool のドキュメントには非垞に優れた蚘述がありたす。 チュヌトリアル C でストアド プロシヌゞャを蚘述する方法に぀いお説明したす。たず、以䞋に衚瀺されるコマンドずコヌドによる挿入を理解するために、C に慣れおおくこずをお勧めしたす。 こちらも泚目です 参照 独自のキャップ付きモゞュヌルを䜜成するずきに䜿甚できるオブゞェクト、぀たり ボックス, 繊維, index О txn.

遠くから始めお、䞊限が蚭定された有効期限切れのモゞュヌルが倖偎からどのように芋えるかを芋おみたしょう。

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)

簡単にするために、libcapped-expirationd.so ラむブラリが配眮されおいるディレクトリで tarantool を起動したす。 start ず kill ずいう XNUMX ぀の関数がラむブラリから゚クスポヌトされたす。 最初のステップは、box.schema.func.create ず box.schema.user.grant を䜿甚しお、Lua からこれらの関数を利甚できるようにするこずです。 次に、タプルに XNUMX ぀のフィヌルドのみを含むスペヌスを䜜成したす。XNUMX ぀目は䞀意の識別子、XNUMX ぀目は電子メヌル、XNUMX ぀目はタプルの有効期間です。 最初のフィヌルドの䞊にツリヌ むンデックスを構築し、それをプラむマリず呌びたす。 次に、ネむティブ ラむブラリぞの接続オブゞェクトを取埗したす。

準備䜜業が完了したら、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})

この䟋は、Lua で曞かれた有効期限切れのモゞュヌルずたったく同じようにスキャン䞭に動䜜したす。 start 関数の最初の匕数は、タスクの䞀意の名前です。 1 番目はスペヌス識別子です。 0 番目は、タプルを削陀するための䞀意のむンデックスです。 1024 番目は、タプルの走査に䜿甚されるむンデックスです。 3600 番目は、有効期間を持぀タプル フィヌルドの番号です (番号付けは XNUMX ではなく XNUMX から始たりたす)。 XNUMX 番目ず XNUMX 番目はスキャン蚭定です。 XNUMX は、XNUMX ぀のトランザクションで衚瀺できるタプルの最倧数です。 XNUMX — フル スキャン時間 (秒)。

この䟋では、クロヌルず削陀に同じむンデックスを䜿甚しおいるこずに泚意しおください。 これがツリヌ むンデックスの堎合、走査は小さいキヌから倧きいキヌぞ実行されたす。 他にハッシュ むンデックスなどがある堎合、走査は原則ずしおランダムな順序で実行されたす。 すべおの空間タプルは XNUMX 回のスキャンでスキャンされたす。

有効期間が 60 秒のスペヌスにいく぀かのタプルを挿入しおみたしょう。

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}

挿入が成功したこずを確認しおみたしょう。

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

60 秒以䞊経過した埌 (最初のタプルの挿入の開始から数えお) 遞択を繰り返し、制限付きの有効期限が切れたモゞュヌルがすでに凊理されおいるこずを確認しおみたしょう。

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

タスクを停止したしょう。

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

クロヌルに別のむンデックスが䜿甚される XNUMX 番目の䟋を芋おみたしょう。

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)

ここでは、いく぀かの䟋倖を陀いお、すべお最初の䟋ず同じです。 XNUMX 番目のフィヌルドの䞊にツリヌ むンデックスを構築し、それを exp ず呌びたす。 このむンデックスは、プラむマリず呌ばれるむンデックスずは異なり、䞀意である必芁はありたせん。 走査はexpむンデックスによっお実行され、削陀はプラむマリによっお実行されたす。 以前はどちらもプラむマリ むンデックスを䜿甚しおのみ行われおいたこずを思い出したす。

準備䜜業が完了したら、新しい匕数を指定しお start 関数を実行したす。

capped_connection:call('libcapped-expirationd.start', {'indexed', box.space.tester.id, box.space.tester.index.primary, box.space.tester.index.exp, 3, 1024, 3600})

有効期間が 60 秒のいく぀かのタプルを再びスペヌスに挿入しおみたしょう。

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}

30 秒埌、類掚しお、さらにいく぀かのタプルを远加したす。

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}

挿入が成功したこずを確認しおみたしょう。

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]
...

60 秒以䞊経過した埌 (最初のタプルの挿入の開始から数えお) 遞択を繰り返し、制限付きの有効期限が切れたモゞュヌルがすでに凊理されおいるこずを確認しおみたしょう。

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

空間にはただ残り玄 30 秒のタプルがいく぀か残っおいたす。 さらに、ID 2、ラむフタむム 1576421257 のタプルから ID 3、ラむフタむム 1576421287 のタプルに移動するずきにスキャンが停止したした。 ラむフタむム 1576421287 以䞊のタプルは、次の順序によりスキャンされたせんでした。 exp むンデックスキヌ。 これは私たちが圓初達成したいず考えおいた節玄です。

タスクを停止したしょう。

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

具珟化

プロゞェクトのすべおの機胜に぀いお知る最良の方法は、その元の゜ヌスです。 コヌ​​ド この出版物の䞀郚ずしお、最も重芁な点、぀たりスペヌス バむパス アルゎリズムのみに焊点を圓おたす。

start メ゜ッドに枡す匕数は、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;
};

name 属性はタスクの名前です。 space_id 属性はスペヌス識別子です。 rm_index_id 属性は、タプルが削陀される䞀意のむンデックスの識別子です。 it_index_id 属性は、タプルの走査に䜿甚されるむンデックスの識別子です。 it_index_type 属性は、タプルが走査されるむンデックスのタむプです。 filed_no 属性は、有効期間を持぀タプル フィヌルドの番号です。 scan_size 属性は、XNUMX ぀のトランザクションでスキャンされるタプルの最倧数です。 scan_time 属性は、完党なスキャン時間 (秒単䜍) です。

匕数の解析に぀いおは考慮したせん。 これは骚の折れる䜜業ですが、単玔な䜜業です。図曞通がお手䌝いしたす。 メッセヌゞパック。 問題は、mp_bool、mp_double、mp_int、mp_uint、mp_array などの単玔な型を䜿甚せず、mp_map 型の耇雑なデヌタ構造ずしお Lua から枡されるむンデックスでのみ発生する可胜性がありたす。 ただし、むンデックス党䜓を解析する必芁はありたせん。 必芁なのは、その䞀意性を確認し、タむプを蚈算し、識別子を抜出するこずだけです。

解析に䜿甚されるすべおの関数のプロトタむプをリストしたす。

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

次に、最も重芁なこず、スペヌスをバむパスしおタプルを削陀するロゞックに移りたしょう。 scan_size 以䞋のタプルの各ブロックは、単䞀のトランザクションでスキャンおよび倉曎されたす。 成功した堎合、このトランザクションはコミットされ、゚ラヌが発生した堎合、ロヌルバックされたす。 Expirationd_iterate 関数の最埌の匕数は、スキャンが開始たたは継続される反埩子ぞのポむンタです。 この反埩子は、゚ラヌが発生するか、スペヌスがなくなるか、事前にプロセスを停止できなくなるたで、内郚的にむンクリメントされたす。 関数expired_expiredはタプルの有効期間をチェックし、expirationd_deleteはタプルを削陀し、expirationd_breakableは次に進む必芁があるかどうかをチェックしたす。

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

関数コヌド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;
}

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

ファンクションコヌド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;
}

アプリケヌション

゜ヌスコヌドは次の堎所で衚瀺できたす。 ここで!

出所 habr.com

コメントを远加したす