<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="description" content="" /> <meta name="author" content="Maxim Sokhatsky" /> <title>Нова версія KVX</title> <link rel="stylesheet" href="https://forms.n2o.space/priv/static/css/forms.css?v=1" /> <link rel="stylesheet" href="../../blank.css?v=2" /> </head> <body> <nav> <a href='https://n2o.dev'>DEV</a> <a href='https://kvx.n2o.space'>KVS</a> <a href='#' style="background:#ededed;">KVX</a> </nav> <main> <section> <h3>Нова версія KVX 6.4</h3> <p>KVX — слой абстракції, який складається з двох частин: базового API та API керування стрімами, тейк, дроп, фолд, катаморфізм, як ви любите. Головний API в модулі kvx, а стрімовий — в kvx_stream. Користуючись нагодою, хочу пропіарити схожі рішення в області зберігання даних на erlang: <a href="https://github.com/fogfish/datum">datum</a> та серія бібліотек від Дмитра Колесникова, <a href="https://github.com/emqx/replayq">replayq</a> від Фенга Лі, а також небагатьох інших, хто намагався абстрактно вирішити проблему.</p> <h3>Чому KVX?</h3> <p>Призначення KVX:</p> <p>— надання інтерфейсу абстрагування широкого спектра сховищ;<br> — надання зручного Erlang REPL інтерфейсу для роботи з записами (records);<br> — розділення на базовий (get put) інтерфейс, та стрім інтерфейс ітераторів (next prev);<br> — набір драйверів (внутрішня база, зовнішня база і файлова система);<br> — шари даних: файлова система, ланцюжки повідомлень, банківські транзакції, дерева підписів, трейси бізнес-процесів, блокчейни, системи черг, тайм серіес, ось це все.<br> </p> <p>Розпочинаючи з незначної модифікації KVX поміняла назву, тепер це "Абстрактна База Ланцюжків", а не "Абстрактна База Термів", оскільки відділився окремий стрім API.</p> <p>— <a href="https://github.com/synrc/kvx">synrc/kvx</a> — 6.4<br></p> <p> Розповімо трохи про теоретичні основи KVX. Ця бібліотека дозволяє або повинна дозволяти зберігати та діставати структури будь-якого виду, надаючи семантику управління курсорами next prev, якою володіють дерева. Тому можна сказати, що це інтерфейс оператора до деревоподібних сховищ та сховищ ланцюжків. У своїй основі KVX підтримує три механізми зберігання ланцюжків:</p> <p>1) перший, очевидний — двонаправлені списки, де вказівники next та prev безпосереднім чином присутні в даних. Спосіб підходить навіть для керування деревами;</p> <p>2) другий, очевидний — однонаправлені списки, де наявний тільки вказівник next. Цей спосіб підходить для списків. Багато людей запитують про цей спосіб, але ми його ніколи не використовували, тому немає імплементації;</p> <p>3) третій, неочевидний спосіб — пряме вбудовувавання записів в BTree index, zero-overhead.</p> <p>В якомусь сенсі перший та другий способи реалізують певний шар поверх KVX, оскільки kvx_stream працює з будь-якими стораджами поліморфно, а ось kvx_st драйвер стрімів зроблений спеціально для rocksdb.</p> <h3>Дерева</h3> <p>Двонаправлені списки або дерева, перша модель, яка лягає прямо з С++ класів, при вивченні програмування — це зберігати в базі прямі вказівники, така система зберігання може використовуватися навіть, якщо integer поміняти на pid, тоді можна буде точково відновлювати історію виклику.</p> <figure> <code> -record(iter, { id = [] :: [] | integer(), next = [] :: [] | integer(), prev = [] :: [] | integer() } ). </code> </figure> <p>Перше поле будь-якої таблиці — це її ім'я, друге — id, третє та четверте — next і prev (сигнатура бінарного розгалуження). Поліморфно за цими зміщеннями ми очікуємо наявність цих полів для лінковки даних в ланцюжки. Базова частина разом називається #iter.</p> <h4>kvx_stream — <b>#iter{}</b></h4> <p> За поліморфое явне управління application level полями next prev рекорда #iter відповідає модуль kvx_stream. Наприклад, файловая система не надає управління своїми курсорами, тому імплементація fs вимагає наявності полів лінковки.</p> <h3>Списки</h3> <p>Однонаправлені списки, чи просто списки, це друга модель. Все таке ж, тільки без повернення назад. Не підтримується пока жодною версією KVX.</p> <figure> <code> -record(ite, { id = [] :: [] | integer(), next = [] :: [] | integer() } ). </code> </figure> <h4>kvx_stre — <b>#ite{}</b></h4> <p>Цей модуль не реалізовано, і запис не специфікований.</p> <h3>Вбудовування в індекс</h3> <p>Третя модель представляє собою прямий, більш ефективний спосіб вбудовування простору бізнес-об'єктів в простір ключів розташованої нижче таблиці, і передача управління курсорами BTree таблиці відразу в драйвер, а не в ручний link walking, як це було в riak. Базова частина поліморфних записів складається тільки з імені таблиці та id, і має назву #it. Ця модель використовується в rocksdb бекенді, який з'явився у 6.4.</p> <figure> <code> -record(it, { id = [] :: [] | integer() } ). </code> </figure> <p>Таким чином, ми можемо емулювати таблиці, в рамках єдиного простору ключів, додаючи їх як префікси до ключа (little endian), який буде автоматично відображатись в головному BTree дереві, також в цьому просторі ви можете зберігати і фіди (стріми), наприклад, топіки: <b>/p2p/maxim/doxtop</b>. Це вимагає спеціальної модифікації kvx_st для работи з записом #it, на відміну від двонаправленого #iter, який працює з драйвером kvx_stream.</p> <h4>kvx_st — <b>#it{}</b></h4> <p>За керування курсорами по ітератору бази даних відповідає модуль kvx_st.</p> <h3>На прикладі</h3> <p>Код, який показує головну ідею, на прикладі драйвера rocksdb:</p> <figure> <code> 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). </code> </figure> <h3>KVX REPL</h3> <p>Тепер, щоб зробити те ж саме на KVX, ви можете зробити просто:</p> <figure> <code> 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"}] </code> </figure> <p>Єдиний тест на стріми, який проходить:</p> <figure> <code> > kvx:check(). ok </code> </figure> <h3>Документація</h3> <p>Надіюсь зробити цю рубрику в release notes офіційною та регулярною, на жаль, якщо не брать до уваги man сторінки для модулій драйверів, то залишається лише два модулі, для яких потрібна документація — це <a href="https://kvx.n2o.space/man/kvx.htm">kvx</a> та <a href="https://kvx.n2o.space/man/kvx_stream.htm">kvx_stream</a>. Я, звичайно, формально додав сторінки для всіх модулів, і в майбутньому хочеться, щоб штучний інтелект генерував документацію. Також я зрозумів, що ченджлог та реліз замітки у власному блозі є чудовим способом авторського анонсування! Зробив історію таких сторінок в README.md.</p> <h3>На закінчення</h3> <p>Репозиторій (що депендить <a href="https://gitlab.com/barrel-db/erlang-rocksdb">rocksdb NIF драйвер</a>, який повільно компілюється, від Бенуа Кісенау, <a href="https://github.com/Vonmo/rocker">Rust-версію Rocker</a> від Максима Молчанова вирішив відкласти, надто часто раст оновлюється, неможливо зафрізити) одразу включає файл конфігурації для rocksdb:</p> <p>— <a href="https://github.com/voxoz/kv">voxoz/kv</a></p> <p>P.S. Вбудовування в BTree можна застосовувати не лише в таких базах, як rocksdb, але й для mnesia, і для будь-яких баз взагалі.</p> <p>P.P.S. Також можна використовувати rocksdb у якості драйвера для mnesia (написаний Ульфом Вігером для проекта Аетерніті), або скоріше різновид таблиці rocksdb_copies — <a href="https://github.com/aeternity/mnesia_rocksdb">mnesia_rocksdb</a>.</p> </section> </main> <footer>Namdak Tonpa <span class="heart"> ❤ </span> 2009—2019</footer> </body> </html>