CR: latency measurement TL;DR Решил померять свой XA/CR сервер. Докладываю о результатах. Пока я держу основной лог операций в KVS, в будущем собираюсь перейти на append-only бинарный лог, который я использую для рафтера. Сделал на KVS чтобы посмотреть на цифры, как будет если сделать вообще без оптимизаций. Итак лог операций я держу в KVS. Сами результаты replay операций хранятся тоже в KVS. Как известно консистентное хеш кольцо имеет несколько vnode на одну машину. Поэтому я держу каждой vnode свой лог операций, незная пока хорошо это или плохо. Поэтому append лога операций можно не лочить, так как vnode не пересекаются. Чего нельзя сказать об оперционных данных. Они могут пересекаться поэтому все реплеи операций нужно ограничивать каким-то контекстов пространства базы данных. Также я использую gen_server:{call,cast} вместо TCP пока, но TCP interconnect тоже закомичен, нужно просто зароутить все. Ждет своего часа, не думаю что переход на gen_tcp даст большие улучшения, но переходить на TCP точно нужно. (cr@127.0.0.1)40> cr:dump(). vnode i n top log latency 121791803110908576516973736059690251637994378581 1 1 21169 5238 2/535/279 243583606221817153033947472119380503275988757162 2 1 21158 5425 2/529/267 365375409332725729550921208179070754913983135743 3 1 21160 5280 2/536/261 487167212443634306067894944238761006551977514324 4 1 21168 5223 3/533/251 608959015554542882584868680298451258189971892905 5 2 21440 5277 1/592/269 730750818665451459101842416358141509827966271486 6 2 21439 5449 2/581/273 852542621776360035618816152417831761465960650067 7 2 21436 5345 2/595/275 974334424887268612135789888477522013103955028648 8 2 21435 5307 2/574/249 1096126227998177188652763624537212264741949407229 9 3 21195 5233 1/570/257 1217918031109085765169737360596902516379943785810 10 3 21194 5400 1/565/244 1339709834219994341686711096656592768017938164391 11 3 21191 5275 2/570/255 1461501637330902918203684832716283019655932542972 12 3 21187 5273 1/554/242 ok Как видим среднее латенси для CR транзакции на кластере из трех машин составляет не больше 300ms. Здесь log обозначает сколько операций есть в логе, а top показывает на id первой операции в списке, который можно протраверсить. Посмотреть этот список лога операций, можно просто указав номер порядковый номер vnode (если число меньше 13) либо указав id операции. (cr@127.0.0.1)43> cr:dump(5). operation id prev i size user:100:feed::true:: 21440 21438 5 467 transaction:21667:feed::false: 21438 21437 5 473 transaction:21658:feed::false: 21437 21431 5 473 transaction:21625:feed::false: 21431 21416 5 473 transaction:21535:feed::false: 21416 21407 5 473 transaction:21662:feed::false: 21407 21403 5 446 transaction:21654:feed::false: 21403 21401 5 446 transaction:21653:feed::false: 21401 21399 5 446 transaction:21481:feed::false: 21399 21398 5 473 transaction:21652:feed::false: 21398 21396 5 446 size -- это размер payload в байтах, как видно одна операция занимает около 1/2 килобайта. Также сами данные еще будут занимать некоторое место, как минимум столько же, после реплея операции. Сами операции за счет цепочечной архитектуры KVS выглядят как добавления объектов в какие-то цепочки. Например опреации user и transaction добавляют объекты унифицированно: > cr:tx(#user{id=maxim,feed_id='Kiev Users'}) > cr:tx(#transaction{id=11,feed_id={maxim,transactions}}). Это все выльется в операции создание пользователя и транзакции и линковке этого объекта к голове. Сам бекенд драйвер (ну да я ведь не базу данных пишу, а фреймфорк для построения распределенных баз данных) выглядит очень просто (опять же благодаря цепочной архитектуре KVS): dispatch({prepare,_,_,Tx}, _) -> kvs:info(?MODULE,"KVS PUT ~p:~p~n",[element(1,Tx),element(2,Tx)]), kvs:put(Tx); dispatch({commit,_,_,Tx}, _) -> kvs:info(?MODULE,"KVS LINK ~p:~p~n",[element(1,Tx),element(2,Tx)]), kvs:link(Tx); dispatch({rollback,_,_,Tx}, _) -> kvs:info(?MODULE,"KVS REMOVE ~p:~p~n",[element(1,Tx),element(2,Tx)]), kvs:remove(Tx); kvs:link это тоже самое что kvs:add только add создает объект если его нет, и не добавляет если есть, а линк делает ровно наоборот, линкует к голове только существующие. Голова в KVS, как изветсно указывается через feed_id. Так вот этот драйвер для XA протокола. Сам CR протокол и XA протокол можно реализовать в рамках одной системы, так как XA просто отличается двойной цепочкой сообщений, а CR только одинарной. Вы можете думать об моем сервер как об распределнном координаторе транзакций который обслуживается распределенным консистентным хеш кольцом. Поэтому у меня в коде это выглядит вот так: % XA PROTOCOL last(#operation{body={prepare,{Sender,Time},_,Tx}},S) -> {{commit,{Sender,Time},cr:chain(element(2,Tx)),Tx},S#state.latency}; last(#operation{body={commit,{Sender,Time},_,Tx}},S) -> {{nop,{Sender,Time},[],[]},new_latency(Time,S)}; % CR PROTOCOL last(#operation{body={_,{Sender,Time},_,Tx}},S) -> {{nop,{Sender,Time},[],[]},new_latency(Time,S)}. Это парвило говорит что делать если обслужена последняя реплика-нода в цепочке. Заканчивать как в CR или еще разок волну подтверждающих коммитов, как в XA.