Нова версія KVX 6.4

KVX — слой абстракції, який складається з двох частин: базового API та API керування стрімами, тейк, дроп, фолд, катаморфізм, як ви любите. Головний API в модулі kvx, а стрімовий — в kvx_stream. Користуючись нагодою, хочу пропіарити схожі рішення в області зберігання даних на erlang: datum та серія бібліотек від Дмитра Колесникова, replayq від Фенга Лі, а також небагатьох інших, хто намагався абстрактно вирішити проблему.

Чому KVX?

Призначення KVX:

— надання інтерфейсу абстрагування широкого спектра сховищ;
— надання зручного Erlang REPL інтерфейсу для роботи з записами (records);
— розділення на базовий (get put) інтерфейс, та стрім інтерфейс ітераторів (next prev);
— набір драйверів (внутрішня база, зовнішня база і файлова система);
— шари даних: файлова система, ланцюжки повідомлень, банківські транзакції, дерева підписів, трейси бізнес-процесів, блокчейни, системи черг, тайм серіес, ось це все.

Розпочинаючи з незначної модифікації KVX поміняла назву, тепер це "Абстрактна База Ланцюжків", а не "Абстрактна База Термів", оскільки відділився окремий стрім API.

synrc/kvx — 6.4

Розповімо трохи про теоретичні основи KVX. Ця бібліотека дозволяє або повинна дозволяти зберігати та діставати структури будь-якого виду, надаючи семантику управління курсорами next prev, якою володіють дерева. Тому можна сказати, що це інтерфейс оператора до деревоподібних сховищ та сховищ ланцюжків. У своїй основі KVX підтримує три механізми зберігання ланцюжків:

1) перший, очевидний — двонаправлені списки, де вказівники next та prev безпосереднім чином присутні в даних. Спосіб підходить навіть для керування деревами;

2) другий, очевидний — однонаправлені списки, де наявний тільки вказівник next. Цей спосіб підходить для списків. Багато людей запитують про цей спосіб, але ми його ніколи не використовували, тому немає імплементації;

3) третій, неочевидний спосіб — пряме вбудовувавання записів в BTree index, zero-overhead.

В якомусь сенсі перший та другий способи реалізують певний шар поверх KVX, оскільки kvx_stream працює з будь-якими стораджами поліморфно, а ось kvx_st драйвер стрімів зроблений спеціально для rocksdb.

Дерева

Двонаправлені списки або дерева, перша модель, яка лягає прямо з С++ класів, при вивченні програмування — це зберігати в базі прямі вказівники, така система зберігання може використовуватися навіть, якщо integer поміняти на pid, тоді можна буде точково відновлювати історію виклику.

-record(iter, { id = [] :: [] | integer(), next = [] :: [] | integer(), prev = [] :: [] | integer() } ).

Перше поле будь-якої таблиці — це її ім'я, друге — id, третє та четверте — next і prev (сигнатура бінарного розгалуження). Поліморфно за цими зміщеннями ми очікуємо наявність цих полів для лінковки даних в ланцюжки. Базова частина разом називається #iter.

kvx_stream — #iter{}

За поліморфое явне управління application level полями next prev рекорда #iter відповідає модуль kvx_stream. Наприклад, файловая система не надає управління своїми курсорами, тому імплементація fs вимагає наявності полів лінковки.

Списки

Однонаправлені списки, чи просто списки, це друга модель. Все таке ж, тільки без повернення назад. Не підтримується пока жодною версією KVX.

-record(ite, { id = [] :: [] | integer(), next = [] :: [] | integer() } ).

kvx_stre — #ite{}

Цей модуль не реалізовано, і запис не специфікований.

Вбудовування в індекс

Третя модель представляє собою прямий, більш ефективний спосіб вбудовування простору бізнес-об'єктів в простір ключів розташованої нижче таблиці, і передача управління курсорами BTree таблиці відразу в драйвер, а не в ручний link walking, як це було в riak. Базова частина поліморфних записів складається тільки з імені таблиці та id, і має назву #it. Ця модель використовується в rocksdb бекенді, який з'явився у 6.4.

-record(it, { id = [] :: [] | integer() } ).

Таким чином, ми можемо емулювати таблиці, в рамках єдиного простору ключів, додаючи їх як префікси до ключа (little endian), який буде автоматично відображатись в головному BTree дереві, також в цьому просторі ви можете зберігати і фіди (стріми), наприклад, топіки: /p2p/maxim/doxtop. Це вимагає спеціальної модифікації kvx_st для работи з записом #it, на відміну від двонаправленого #iter, який працює з драйвером kvx_stream.

kvx_st — #it{}

За керування курсорами по ітератору бази даних відповідає модуль kvx_st.

На прикладі

Код, який показує головну ідею, на прикладі драйвера rocksdb:

1> {ok,Ref} = rocksdb:open("hey",[{create_if_missing,true}]). 2> rocksdb:put(Ref, <<"/users/1">>,<<"maxim">>,[{sync,true}]). 3> rocksdb:put(Ref, <<"/users/2">>,<<"doxtop">>,[{sync,true}]). 4> rocksdb:put(Ref, <<"/users/3">>,<<"vlad">>,[{sync,true}]). 5> rocksdb:put(Ref, <<"/staff/1">>,<<"vlad">>,[{sync,true}]). 6> rocksdb:put(Ref, <<"/staff/2">>,<<"maxim">>,[{sync,true}]). 7> rocksdb:put(Ref, <<"/staff/3">>,<<"doxtop">>,[{sync,true}]). 8> {ok,I} = rocksdb:iterator(Ref,[]). 9> rocksdb:iterator_move(I,{seek,<<"/staff/">>}). 10> rocksdb:iterator_move(I,next). 11> rocksdb:iterator_move(I,next). 12> rocksdb:iterator_move(I,next). 13> rocksdb:iterator_move(I,{seek,<<"/users/">>}). 14> rocksdb:iterator_move(I,next). 15> rocksdb:iterator_move(I,next). 16> rocksdb:iterator_move(I,next).

KVX REPL

Тепер, щоб зробити те ж саме на KVX, ви можете зробити просто:

1> kvx:ver(). {version,"KVX ROCKSDB"} 2> rr(kvx). [emails,id_seq,it,iter,kvx,reader,schema,table,writer] 3> kvx:join(). ok 4> kvx:put(#emails{id=1,email="maxim"}). 5> kvx:put(#emails{id=2,email="doxtop"}). 6> kvx:put(#writer{id=2}). 7> kvx:put(#writer{id=1}). 8> kvx:all(writer). [#writer{id = 1,count = 0,cache = [],args = [],first = []}, #writer{id = 2,count = 0,cache = [],args = [],first = []}] 9> kvx:all(emails). [#emails{id = 1,next = [],prev = [],email = "maxim"}, #emails{id = 2,next = [],prev = [],email = "doxtop"}] 10> kvx:add(#writer{id=chain,args=#emails{email="maxim@synrc.com"}}). 11> kvx:add(#writer{id=chain,args=#emails{email="vlad@synrc.com"}}). 12> kvx:add(#writer{id=chain,args=#emails{email="doxtop@synrc.com"}}). 13> kvx:all(chain). [#emails{id = 1555244691729330000,next = [],prev = [], email = "maxim@synrc.com"}, #emails{id = 1555244699905648000,next = [],prev = [], email = "doxtop@synrc.com"}, #emails{id = 1555244696660271000,next = [],prev = [], email = "vlad@synrc.com"}]

Єдиний тест на стріми, який проходить:

> kvx:check(). ok

Документація

Надіюсь зробити цю рубрику в release notes офіційною та регулярною, на жаль, якщо не брать до уваги man сторінки для модулій драйверів, то залишається лише два модулі, для яких потрібна документація — це kvx та kvx_stream. Я, звичайно, формально додав сторінки для всіх модулів, і в майбутньому хочеться, щоб штучний інтелект генерував документацію. Також я зрозумів, що ченджлог та реліз замітки у власному блозі є чудовим способом авторського анонсування! Зробив історію таких сторінок в README.md.

На закінчення

Репозиторій (що депендить rocksdb NIF драйвер, який повільно компілюється, від Бенуа Кісенау, Rust-версію Rocker від Максима Молчанова вирішив відкласти, надто часто раст оновлюється, неможливо зафрізити) одразу включає файл конфігурації для rocksdb:

voxoz/kv

P.S. Вбудовування в BTree можна застосовувати не лише в таких базах, як rocksdb, але й для mnesia, і для будь-яких баз взагалі.

P.P.S. Також можна використовувати rocksdb у якості драйвера для mnesia (написаний Ульфом Вігером для проекта Аетерніті), або скоріше різновид таблиці rocksdb_copies — mnesia_rocksdb.