Taratool için kendi sınırlı son kullanma tarihi geçmiş modülümüzü yazma

Taratool için kendi sınırlı son kullanma tarihi geçmiş modülümüzü yazma

Bir süre önce boşluklardaki demetleri temizleme sorunuyla karşı karşıya kaldık taran aracı. Temizliğin tarantool'un hafızası tükendiğinde değil, önceden ve belirli bir sıklıkta başlatılması gerekiyordu. Bu görev için tarantool'un Lua'da yazılmış adı verilen bir modülü vardır. son. Bu modülü kısa bir süre kullandıktan sonra bizim için uygun olmadığını fark ettik: Büyük miktarda verinin sürekli temizlenmesi nedeniyle Lua, GC'de asılı kaldı. Bu nedenle, yerel programlama dilinde yazılan kodun sorunlarımızı mümkün olan en iyi şekilde çözeceğini umarak, kendi sınırlı süresi dolmuş modülümüzü geliştirmeyi düşündük.

Bizim için güzel bir örnek tarantool modülüydü. memcached. Kullanılan yaklaşım, uzayda tuple'ın ömrünü yani ttl'yi gösteren ayrı bir alan oluşturulması esasına dayanmaktadır. Arka plandaki modül alanı tarar, TTL'yi geçerli saatle karşılaştırır ve tuple'ın silinip silinmeyeceğine karar verir. Memcached modül kodu basit ve zariftir ancak fazlasıyla geneldir. İlk olarak, taranan ve silinen indeksin türü dikkate alınmaz. İkinci olarak, her geçişte sayısı oldukça fazla olabilen tüm demetler taranır. Ve eğer süresi dolmuş modülde ilk sorun çözüldüyse (ağaç dizini ayrı bir sınıfa ayrılmışsa), o zaman ikincisi hala herhangi bir ilgi görmedi. Bu üç nokta, kendi kodumu yazma yönündeki seçimi önceden belirledi.

Açıklama

Tarantool dokümantasyonu çok iyi bir içeriğe sahiptir. öğretici Saklı yordamlarınızı C'de nasıl yazacağınız hakkında. Öncelikle, aşağıda görünecek komutlar ve kodlarla bu ekleri anlamak için ona aşina olmanızı öneririm. Şuna da dikkat etmekte fayda var referans kendi kapalı modülünüzü yazarken mevcut olan nesnelere, yani kutu, lif, indeks и teşekkürler.

Uzaktan başlayalım ve süresi dolmuş bir modülün dışarıdan nasıl göründüğüne bakalım:

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)

Basit olması açısından, libcapped-expirationd.so kütüphanemizin bulunduğu dizinde tarantool'u başlatıyoruz. Kitaplıktan iki işlev dışa aktarılır: başlat ve sonlandır. İlk adım, box.schema.func.create ve box.schema.user.grant'ı kullanarak bu işlevleri Lua'dan kullanılabilir hale getirmektir. Ardından, demetleri yalnızca üç alan içerecek bir alan oluşturun: birincisi benzersiz bir tanımlayıcıdır, ikincisi e-postadır ve üçüncüsü de demetin ömrüdür. İlk alanın üstüne bir ağaç dizini oluşturuyoruz ve ona birincil adını veriyoruz. Daha sonra bağlantı nesnesini yerel kütüphanemize alıyoruz.

Hazırlık çalışmasından sonra başlatma işlevini çalıştırın:

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

Bu örnek, tarama sırasında Lua'da yazılan, süresi dolmuş modülle tamamen aynı şekilde çalışacaktır. Başlatma işlevinin ilk argümanı görevin benzersiz adıdır. İkincisi alan tanımlayıcıdır. Üçüncüsü, tuple'ların silineceği benzersiz bir dizindir. Dördüncüsü, demetlerin geçileceği dizindir. Beşincisi, ömrü olan demet alanının numarasıdır (numaralandırma 1'dan değil 0'den başlar!). Altıncı ve yedinci tarama ayarlarıdır. 1024, tek bir işlemde görüntülenebilecek maksimum tuple sayısıdır. 3600 — saniye cinsinden tam tarama süresi.

Örneğin tarama ve silme için aynı dizini kullandığını unutmayın. Bu bir ağaç diziniyse, geçiş küçük anahtardan büyük anahtara doğru gerçekleştirilir. Örneğin başka bir karma endeksi varsa, geçiş kural olarak rastgele sırayla gerçekleştirilir. Tüm alan tuple'ları tek taramada taranır.

Ömrü 60 saniye olan boşluğa birkaç tuple ekleyelim:

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}

Eklemenin başarılı olup olmadığını kontrol edelim:

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

Seçimi 60+ saniye sonra tekrarlayalım (ilk demetin eklenmesinin başlangıcından itibaren sayarız) ve sınırlı süresi dolmuş modülün zaten işlenmiş olduğunu görelim:

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

Görevi durduralım:

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

Tarama için ayrı bir dizinin kullanıldığı ikinci bir örneğe bakalım:

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)

Burada her şey birkaç istisna dışında ilk örnektekiyle aynı. Üçüncü alanın üstüne bir ağaç indeksi oluşturuyoruz ve buna exp adını veriyoruz. Bu indeksin, birincil olarak adlandırılan indeksin aksine benzersiz olması gerekmez. Geçiş, exp endeksine göre gerçekleştirilecek ve birincil tarafından silinecektir. Daha önce her ikisinin de yalnızca birincil dizin kullanılarak yapıldığını hatırlıyoruz.

Hazırlık çalışmasının ardından start fonksiyonunu yeni argümanlarla çalıştırıyoruz:

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

Tekrar 60 saniye ömrü olan birkaç tuple'ı boşluğa ekleyelim:

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 saniye sonra, benzetme yoluyla, birkaç tane daha tuple ekleyeceğiz:

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}

Eklemenin başarılı olup olmadığını kontrol edelim:

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

Seçimi 60+ saniye sonra tekrarlayalım (ilk demetin eklenmesinin başlangıcından itibaren sayarız) ve sınırlı süresi dolmuş modülün zaten işlenmiş olduğunu görelim:

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

Uzayda hâlâ yaklaşık 30 saniye daha yaşayacak olan bazı demetler var. Ayrıca, ID'si 2 ve ömrü 1576421257 olan bir tuple'dan, ID'si 3 ve ömrü 1576421287 olan bir demet'e geçildiğinde tarama durdu. Ömrü 1576421287 veya daha fazla olan tuple'lar, şu sıralama nedeniyle taranmadı: exp indeks tuşları. Bu, başlangıçta elde etmek istediğimiz tasarruftu.

Görevi durduralım:

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

uygulama

Bir projenin tüm özelliklerini anlatmanın en iyi yolu orijinal kaynağıdır. kod! Yayının bir parçası olarak yalnızca en önemli noktalara, yani uzay bypass algoritmalarına odaklanacağız.

Start metoduna ilettiğimiz argümanlar expirationd_task adı verilen bir yapıda saklanır:

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 niteliği görevin adıdır. Space_id özelliği, alan tanımlayıcısıdır. rm_index_id niteliği, tuple'ların silineceği benzersiz indeksin tanımlayıcısıdır. it_index_id özelliği, tanımlama dizilerinin geçileceği endeksin tanımlayıcısıdır. it_index_type özelliği, tanımlama dizilerinin geçileceği dizin türüdür. Filed_no özelliği, kullanım ömrüne sahip tuple alanının numarasıdır. scan_size özelliği, bir işlemde taranan maksimum tuple sayısıdır. scan_time özelliği saniye cinsinden tam tarama süresidir.

Argümanları ayrıştırmayı dikkate almayacağız. Bu, kütüphanenin size yardımcı olacağı zahmetli ama basit bir iştir. msgpuck. Zorluklar yalnızca Lua'dan mp_map türüyle karmaşık bir veri yapısı olarak aktarılan ve mp_bool, mp_double, mp_int, mp_uint ve mp_array basit türlerini kullanmayan dizinlerde ortaya çıkabilir. Ancak dizinin tamamını ayrıştırmaya gerek yoktur. Sadece benzersizliğini kontrol etmeniz, türü hesaplamanız ve tanımlayıcıyı çıkarmanız yeterlidir.

Ayrıştırma için kullanılan tüm işlevlerin prototiplerini listeliyoruz:

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

Şimdi en önemli şeye geçelim: alanı atlama ve demetleri silme mantığı. Scan_size'den büyük olmayan her bir tuple bloğu tek bir işlem altında taranır ve değiştirilir. Başarılı olursa bu işlem commit edilir, hata oluşursa geri alınır. expirationd_iterate işlevinin son argümanı, taramanın başladığı veya devam ettiği yineleyiciye yönelik bir işaretçidir. Bu yineleyici, bir hata oluşana, alan bitene veya işlemin önceden durdurulması mümkün olmayana kadar dahili olarak artırılır. expirationd_expired işlevi bir tuple'ın ömrünü kontrol eder, expirationd_delete bir tuple'ı siler, expirationd_breakable ise devam etmemiz gerekip gerekmediğini kontrol eder.

Expirationd_iterate işlev kodu:

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

İşlev kodu 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;
}

Expirationd_delete işlev kodu:

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

İşlev kodu 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;
}

uygulama

Kaynak kodunu şu adreste görebilirsiniz: burada!

Kaynak: habr.com

Yorum ekle