Содержание статьи
Классификация
Говоря о NewSQL-базах, мы имеем в виду базы, удовлетворяющие нескольким критериям:
- поддержка реляционной модели и транзакционности;
- SQL как основной интерфейс доступа к данным;
- горизонтальная масштабируемость;
- совершенно новый движок, не унаследованный от классических СУБД;
- появились в последние 3–5 лет.
Можно выделить несколько классов решений в области хранения и обработки данных, относящихся к NewSQL. Во‑первых, к NewSQL причисляют способы использования обычных SQL-баз в кластере из нескольких физических узлов для хранения и обработки больших объемов данных. Сюда можно отнести MySQL Cluster, Postgres-XC, Oracle RAC и прочие. Все промышленные СУБД пытаются собраться в кластер. Все эти решения представляют собой промежуточный слой (middleware), который распределяет запросы между узлами, хранящими данные (обычными БД), и объединяет результаты — собственно, осуществляет шардинг. Как правило, на запросы накладываются сильные ограничения на использование внешних ключей и джойнов. Мы не будем рассматривать эти системы здесь, хотя это интересная тема для дальнейшего исследования.
Во‑вторых, NewSQL — это новые движки хранения данных для существующих БД. Самый популярный среди них TokuDB — движок для MySQL. Он использует индексы на так называемых фрактальных деревьях. Эти индексы значительно быстрее B-деревьев в случаях, когда индексы не помещаются в оперативной памяти и их приходится читать с диска. Так как основная особенность новых движков — тонкие отличия в производительности в некоторых экзотических случаях, мы исключаем их из этого обзора. Нас в первую очередь интересует функционал.
info
Фрактальные индексы прикрутили и к MongoDB — в одном неофициальном форке.
В‑третьих, NewSQL — это совершенно новые СУБД, которые поддерживают SQL. Тут мы остановимся подробнее. Многие NewSQL БД — это in-memory БД. Они хранят все данные в оперативной памяти. Создатели этих БД считают фатальным недостатком существующих БД стремление все хранить на диске. Если тебе нужно обрабатывать данные быстро, нужно хранить их в ОЗУ. А относительно небольшой объем ОЗУ на одной машине можно легко компенсировать, собрав кластер из сотен узлов. Хранение в ОЗУ не означает, что данные будут потеряны при выключении питания. Все эти БД ведут журнал операций и периодически скидывают снимки данных на диск. Мы рассмотрим VoltDB и MemSQL.
Другие разработчики NewSQL борются со сложностью развертывания и управления кластерной СУБД в современных виртуальных и облачных средах. Создаются красивые веб‑консоли для управления и мониторинга. Рассмотрим в этом качестве NuoDB. Третьи разработчики NewSQL замахиваются на создание универсальных платформ для хранения, представления и обработки структурированных данных, в виде реляционной модели, но не только. Здесь мы познакомимся с замечательной FoundationDB. Итак, чтобы новая СУБД в полной мере могла называться NewSQL-решением, она должна быть SQL и NoSQL одновременно.
VoltDB
VoltDB — это in-memory СУБД, написанная на Java. Это самая старая БД в нашем обзоре, первый публичный релиз состоялся аж в мае 2010 года. VoltDB также единственная СУБД в обзоре, полностью доступная под свободной лицензией (AGPL). Есть и проприетарная Enterprise-версия, в которую добавлены плюшки вроде VoltDB Web Studio — «среды разработки» в виде веб‑приложения. В отличие от других СУБД, где на одном сервере ты можешь создать несколько баз данных, в VoltDB один процесс обслуживает одну базу. Точнее, множество процессов на множестве хостов кластера обслуживают одну базу, и других баз в этом кластере нет. Каждый процесс однопоточен, асинхронен, имеет свой каталог для хранения снапшотов, работает на одном CPU и называется партицией (partition). Соответственно, на каждом хосте кластера нужно запускать несколько процессов, согласно числу ядер CPU.
Сама БД описывается в виде каталога. В этом каталоге находится описание кластера: сколько узлов, сколько процессов на узле, количество копий данных (тут оно называется K-factor). Еще тут есть полное описание схемы БД, прямо в файле ddl.sql, в виде CREATE TABLE, ALTER TABLE и прочих конструкций. А еще есть исходные тексты хранимых процедур на Java (да, в VoltDB хранимые процедуры — только на Java). Весь этот каталог компилируется (!) в один JAR-архив. И при запуске процесса СУБД нужно указать этот JAR-файл. Это больше похоже на какой‑нибудь J2EE сервер приложений, чем на СУБД.
При компиляции для каждого запроса в хранимых процедурах строится план выполнения. Потом этот план не меняется. Можно дать оптимизатору подсказки о количестве записей в таблицах, но в целом статичный план выполнения запроса, не использующий актуальную статистику, выглядит анахронизмом. Запустив процессы на всех узлах кластера, можно подключаться к любому узлу, используя вольтдибишный JDBC-драйвер. Есть клиентские библиотеки для других ЯП. А можно делать запросы к хранимым процедурам через простенький HTTP-интерфейс.
Хранимые процедуры очень важны в VoltDB. Это единица транзакции, вызов каждой хранимой процедуры представляет собой транзакцию, и других транзакций нет. То есть весь набор доступных транзакционных действий мы определяем на этапе компиляции каталога БД. Хранимые процедуры являются частью сервера (вкомпилированы в сервер). «Пятью девятками» тут даже не пахнет: хочешь новую функцию — перекомпилируйся и перезапусти базу. Вся оптимизация вертится вокруг хранимых процедур.
Мультиверсионности нет, блокировок тоже, поскольку на партицию работает только один процесс и конкурировать ему не с кем. С одной стороны, пропускная способность действительно возрастает, и действительно уровень изоляции транзакций serialized. Хотя сложно говорить об уровне изоляции при фактически однопользовательской работе. Если запрос затрагивает несколько партиций, то это будет единственный запрос, выполняющийся в базе в этот момент. При таком подходе даже неудобно упоминать о разделении прав доступа к данным. Их тоже нет.
Вспомним про такое свойство транзакций, как durability: здесь оно реализовано через журнал транзакций и периодические снапшоты. Журнал хранится только со времени последнего снапшота, поэтому обращение к историческим данным невозможно. Надежность хранения достигается за счет резервирования данных на нескольких узлах кластера. Распределенные транзакции (да, они здесь возможны) фиксируются двухфазным коммитом.
Что, собственно, нового? Эта SQL БД работает в кластере. И поэтому у нас есть два типа таблиц: партицированные (partitioned) и реплицированные (replicated). С реплицированными таблицами (такие создаются по умолчанию, обычным CREATE TABLE) все просто — они целиком копируются на все узлы кластера. Это удобно для небольших таблиц‑справочников, с которым часто делаются джойны и которые редко меняются. Партицированные таблицы шардируются между узлами кластера (точнее, между процессами‑партициями). Шардируются по хешу ключа. Ключом может быть любой столбец таблицы с целочисленным или строковым значение (но не null). Ключ у таблицы может быть только один. В общем, таблица размазывается ровным слоем по всему кластеру (учитывая, конечно, K-factor).
Соответственно, выборка множества строк из партицированной таблицы — недешевое удовольствие, к тому же не гарантируется порядок выборки в случае отсутствия явной сортировки. При этом строки с разных узлов будут выданы в том порядке, в каком узлы ответили. Обычно запрос из процедур отправляется на все узлы кластера, которые могут содержать интересующие нас данные, а затем агрегируются. Однако если процедура оперирует данными по одному ключу и все задействованные таблицы партицированы по этому же ключу, то вся процедура может быть выполнена на одном узле. Это, конечно же, намного эффективнее. Таким образом, при проектировании БД нужно тщательно подумать о том, какие данные будут использоваться вместе, и партицировать их по одному и тому же ключу.
Помни, что любое изменение структуры данных требует перекомпиляции каталога БД, а для некоторых (к примеру, добавление уникального индекса) требуется полный перезапуск базы. Такой подход к управлению структурой ну совсем не гибкий. Кроме того, поддерживать нормализованную реляционную схему для более‑менее связных данных не представляется возможным: запросы ограничены по сложности, джойнить шардированные таблицы можно только по ключу шардинга и только на равенство. В update и delete подзапросы вообще запрещены. Кроме того, есть ограничения на размер резалт‑сета — не более 50 Мб. Вдобавок VoltDB не поддерживает внешние ключи и check constraint, а уникальность записей для unique constraint обеспечивает только в пределах партиции. При этом заявляется, что СУБД поддерживает реляционную модель. Кодд переворачивается в гробу.
Эдгар Кодд
Британский ученый. Создатель реляционной модели данных (60–70-е годы ХХ века). Работал в IBM. В 1985 году опубликовал «12 правил Кодда», которые должны выполняться в правильной реляционной СУБД. Ни тогда, ни, как видим, сейчас ни одна существующая СУБД не удовлетворяет всем этим правилам :).
MemSQL
MemSQL очень похожа на VoltDB. Тоже in-memory СУБД, только написана на C++. Первый публичный релиз состоялся в июне 2012 года. У MemSQL имеется только проприетарная версия. Бесплатных версий нет, но есть триалка. В MemSQL узлы кластера неравнозначны. Здесь есть агрегаторы и листы (leaf). Агрегаторы принимают запросы от пользователей, модифицируют и рассылают запросы листам, агрегируют результаты. Есть главный агрегатор, который отвечает за создание БД и таблиц и целостность кластера в целом. Листы непосредственно хранят данные и выполняют (частичные) запросы. При падении мастер‑агрегатора кластер остается работоспособным, хотя DDL-запросы выполнять уже нельзя. Автоматически новый мастер‑агрегатор не назначается.
В отличие от VoltDB, в кластере можно держать несколько баз данных. При создании базы сразу создается определенное число партишенов (по умолчанию — по восемь на лист). Любопытно, что партишены на листах представлены отдельными базами данных, только с числовыми суффиксами. Например, если ты создал в кластере БД под именем «test», но на листах будут созданы БД‑партиции «test_1», «test_2» и так далее.
В MemSQL, как и в VoltDB, присутствует два типа таблиц: справочные (reference) таблицы, которые копируются на все узлы кластера (включая агрегаторы), и шардированные (sharded) таблицы, которые размазываются по партициям по хешу ключа. Ключом шардирования может быть любой столбец таблицы, только этот столбец должен входить в первичный ключ этой таблицы. И в уникальный индекс, если такой понадобится. И во внешний ключ, конечно, тоже. Так в MemSQL решили проблему связности данных, на которую в VoltDB просто закрыли глаза.
MemSQL может определить, что запрос затрагивает только одну партицию, и оптимизирует его, отсылая только на один лист. Все запросы на изменение данных могут выполняться только на узлах‑агрегаторах, вне зависимости от того, сколько партиций затрагивает запрос. Кроме того, явно запрещены любые манипуляции с первичным ключом (для защиты от изменения ключа шардинга). Главной особенностью MemSQL является компиляция запросов. Любой SQL-запрос (все DML и некоторые DDL) превращается в код на C++ (SELECT * FROM TEST превращается в две сотни строк чего‑то нечитаемого). Этот код компилируется обычным GCC в разделяемую библиотеку, которая подключается к серверу.
Код параметризованный, все значения, встречающиеся в WHERE секции запроса, являются параметрами. В дальнейшем все подобные запросы (с тем же набором параметров) уже выполняются в нативном коде. Проявляется это в том, что первый запрос выполняется десятки секунд, а последующие — со вполне нормальной скоростью. Пока не изменится структура базы. После этого все затронутые изменениями запросы будут перекомпилированы заново. Кеширование планов запросов само по себе неплохо, если при этом следить за актуальностью статистики и структуры данных. Статистика и селективность выборок в MemSQL игнорируются. Для запроса можно посмотреть план выполнения, но там мало информации для оптимизации. Это, скорее, декларация решения оптимизатора.
В отличие от VoltDB, MemSQL не накладывает ограничений на сложность запросов и количество участвующих в них партицированных таблиц, но все промежуточные результаты хранит во временных таблицах на агрегаторе и честно предупреждает о возможности отказа из‑за нехватки памяти на больших запросах. Для репликации MemSQL умеет создавать только две копии данных: основную и резервную. При этом резервная копия не участвует в операциях и должна быть создана на отдельном листе. Если хочешь включить резервирование, тебе нужно в два раза больше листов в кластере. Не очень удобно.
Никаких хранимых процедур в MemSQL нет: база не поддерживает даже триггеры и представления. Ну, если не считать скомпилированные запросы за процедуры :). Хотя MemSQL декларирует поддержку ACID-транзакций, фактически каждый запрос выполняется в отдельной транзакции. А для некоторых изощренных случаев, таких как ошибка на вставке нескольких записей перечислением значений, может закоммититься только часть измененных записей. Вот такая атомарность. Уровень изоляции read committed. Но разработчики признают, что при изменении данных на нескольких партициях возможно грязное чтение. Вот такая изолированность.
Надежность фиксации данных, как и в VoltDB, обеспечивается снапшотами и журналом транзакций. Да вот беда, по умолчанию журнал пишется на диск асинхронно. Недостаточно кисло? Несмотря на то что MemSQL заявляется как lock-free база, блокировки используются, и на это явно указывается в документации, например, удаление индекса блокирует операции изменения данных. И да, есть команда для установки явной блокировки на таблицу. Реверансом в сторону классических баз является разделение прав доступа. База поддерживает многопользовательскую работу. Набор привилегий достойный, но выдавать на отдельные объекты базы их нельзя.
NuoDB
Создатели NuoDB решили сделать развертывание кластера в облаке приятным и необременительным занятием. Кажется, удалось это им не очень. Зато уровень рекламного буллшита в документации зашкаливает. Это проприетарная СУБД, написанная преимущественно на Java. Первый релиз состоялся в январе 2013-го. Начнем с типов узлов, точнее, типов процессов, которые запускаются в кластере (в терминологии NuoDB — домене). Есть один брокер — основной процесс, который собирает домен в единое целое. К нему клиент подключается в первую очередь и узнает о конфигурации домена. На каждом узле запущены агенты, которые общаются с брокером и докладывают о состоянии узлов. База данных создается через брокер, в нее входит один или более движков транзакций (Transaction Engine) и один или более менеджеров хранения (Storage Manager). К первому подключаются клиенты для выполнения запросов, второй отвечает за хранение данных. При создании БД задается, сколько именно должно быть создано движков и менеджеров и на каких именно узлах домена они должны быть расположены.
Все это можно настроить через довольно удобную веб‑админку, с красивым логотипом в виде зеленой птички. Однако на практике не все так просто. Это единственная БД в обзоре, которой понадобился корректно работающий DNS (или правильные /etc/hosts). Узел идентифицирует себя по доменному имени, другие узлы должны иметь возможность подключиться к нему по этому имени.
Внезапно — шардинга здесь нет. Хоть репликация есть, и на том спасибо. Все данные в БД дублируются на все менеджеры хранения данной БД. Утверждается, что БД остается частично работоспособной при падениях брокера, агента и даже движков транзакций и менеджера хранения. В качестве разнообразия можно настроить хранение данных в хадуповой HDFS (ну хоть тут появляется какой‑то шардинг).
Никакой совместимости на уровне протокола нет, JDBC-драйвер — свой уникальный. Было бы понятно, если бы это была какая‑то консолька по развертыванию кластера обычных СУБД (вроде Amazon RDS). А так, в чем смысл существования NuoDB? Несмотря ни на что, в NuoDB нормальная реализация транзакций: база поддерживает уровни изоляции от read committed до serializable (по умолчанию repeatable read), обеспечивает атомарность многопроцедурных транзакций.
Нет шардированных таблиц — нет проблем с ограничениями уникальности и ссылочной целостности. Присутствуют все привычные для реляционных баз констрейнты. В целом все как у людей — разграничение прав доступа, оптимизатор запросов на основе статистики, B-tree индексы. Обычный SQL двадцатилетней давности. Непонятно, что здесь нового.
FoundationDB
FoundationDB — самый молодой представитель нашего обзора. Первый публичный релиз состоялся в мае 2013 года. Ребята из FoundationDB пошли совершенно уникальным путем. Они решили, что в СУБД механизм хранения данных, модель представления данных и язык запросов должны быть независимы. Например, если вы берете PostgreSQL, то вы вынуждены использовать его MVCC движок хранения, реляционную модель данных и SQL как язык запросов (впрочем, в MySQL движок хранения можно выбирать). Еще они сказали, что транзакции — это круто, и создали отдельную, универсальную, надежную, распределенную технологию хранения структурированных данных.
FoundationDB — это key-value хранилище, с упорядоченными ключами, с поддержкой ACID-транзакций (в операциях на множестве ключей!), которое развертывается на кластере равнозначных узлов. Авторам неизвестны аналогичные NoSQL-решения. Транзакции на key-value поддерживаются в BerkeleyDB, но это вообще‑то встраиваемая СУБД. Упорядоченность ключей есть в HBase (при особых настройках и в Cassandra), но тут никто не говорит о транзакциях. Lightweight-транзакции не в счет. Получается уникальное и мощное сочетание. А поверх key-value находятся слои (так и называются: Layers), которые представляют более высокоуровневую модель хранения и языки запросов. Самим FoundationDB разрабатывается SQL Layer, раньше он был отдельным проектом под названием Akiban. Есть слои для хранения документов и графов.
Key-value хранилище — проприетарный продукт. Есть ограниченная бесплатная версия. А вот SQL Layer и многие другие слои — уже свободное ПО, под AGPL. Хранилище написано на C++. Процессы однопоточны, асинхронны, работают на одном ядре CPU и обслуживают каждый свою порцию данных. На каждом узле кластера нужно запускать несколько процессов, согласно числу CPU, что делается вполне прозрачно. Все узлы кластера равнозначны, однако некоторые (требуется нечетное число) играют роль координаторов. Координаторы отвечают за поддержку топологии кластера для репликации данных, определение доступности сегмента кластера в случае разделения.
Репликация прозрачна. Кластер может хранить до трех копий данных. Учитывается распределение узлов между дата‑центрами. Шардинг ключей осуществляется по диапазонам (примерно как в MongoDB), что требует постоянной перебалансировки кластера, которая проходит тихо и незаметно. Наличие и объем фоновой балансировки — важный параметр кластера. Он виден в выводе стандартной команды статуса. Сетевой протокол и API — свои собственные. Поддерживаются операции записи и чтения по ключу, сканирования диапазона (помним, ключи упорядочены), начало и завершения транзакций. Имеется два способа хранения данных. Либо B-деревья хранятся прямо в файлах на диске, что хорошо работает только на SSD. Либо копия данных живет в ОЗУ, а на диск пишутся логи и снапшоты.
SQL Layer написан на Java. Это совершенно отдельное сетевое приложение. Клиенты подключаются к SQL Layer по сети, SQL Layer подключается к key-value хранилищу тоже по сети. Это увеличивает время обработки запроса, но разработчики и не утверждают, что latency у них лучше всех. Слой SQL не хранит состояния, все данные записываются в хранилище, поэтому размещать этот процесс можно где угодно и в любом количестве. Разработчики рекомендуют запускать SQL Layer на каждом клиентском узле (на бэкенде твоего веб‑приложения, например). Но можно разворачивать и на узлах с хранилищем, лишь бы памяти хватило. C SQL Layer можно общаться по протоколу PostgreSQL, совместимость почти полная.
У key-value хранилища имеется ряд интересных ограничений. Размер ключа — не более 10 Кб. Размер значения — не более 100 Кб. Длительность транзакции — не более 5 с. Одна транзакция может изменить не более 10 Мб данных. Вообще, к транзакциям у FoundationDB отношение трепетное. Первым пунктом в white paper идет ода транзакциям: они удобные, понятные, полезные и не такие уж дорогие. После стольких грубых слов, сказанных о транзакциях разработчиками NoSQL-решений, на глаза наворачивается скупая мужская слеза. Транзакции честные. Атомарность распространяется на несколько операторов, укладывающихся в указанные выше ограничения по времени и объему транзакции.
Изоляция обеспечивается мультиверсионностью представления данных, кроме того, если две транзакции конфликтуют (например, одна изменяет те же ключи, которые читает другая), то более поздняя завершается с ошибкой. На клиенте рекомендуется повторять транзакции до достижения успеха. Зато обеспечивается уровень изоляции serialized для многопользовательского доступа. Есть оператор явного начала транзакции.
SQL Layer расширяет реляционную модель группами таблиц. Цель та же, которой руководствовались создатели документо‑ориентированных NoSQL, — ускорение доступа к связанным данным. Ты находишь в схеме данных тесно связанные таблицы (типичный пример: заказ и товары в нем) и объединяешь их с помощью специального внешнего ключа в группу таблиц. Не в документ, а в группу таблиц. В одну группу входят иерархически связанные таблицы, так же как в документ объединяются иерархически подчиненные данные.
Группа таблиц хранится целиком, все ключи в группе образуют непрерывный диапазон. Это значит, что с большой вероятностью группа таблиц хранится на одном узле кластера и все операции над ней могут быть выполнены на этом узле. Поэтому джойны внутри группы более эффективны. Тем не менее таблицы внутри группы остаются полноценными SQL-таблицами, их можно спокойно джойнить с таблицами из других групп. Жаль, что нет возможности делать реплицируемые таблицы‑справочники, поскольку добавлять их в группы не всегда возможно (в корне дерева таблиц может быть другой объект), а джойнить данные с разных партиций — дорого.
Огромный плюс групп таблиц перед документами — иерархию подчинения можно поменять, просто переставив внешние ключи, несколькими ALTER TABLE. SQL-слой сам озаботится пересозданием ключей и перераспределением данных. Кроме обычных B-tree индексов, FoundationDB предоставляет spatial-индексы. Причем, в отличие от большинства СУБД (Oracle, PostgreSQL, MySQL), это не R-tree, а z-order индексы. Объясняется этот выбор тем, что под SQL-слоем лежит key-value хранилище. Кроме того, FoundationDB позволяет делать индексы на группу таблиц. Аналогом в традиционных базах могут служить materialized view для Oracle и PostgreSQL или join-indexes для Teradata.
Кроме разнообразных индексов, в FoundationDB есть продвинутый оптимизатор, который умеет не только сочетать несколько различных индексов для одной таблицы / группы таблиц, но и использовать покрывающие индексы. Оптимизатор перестраивает план выполнения запроса при устаревании статистики, в отличие от всех рассмотренных выше СУБД. Статистика собирается автоматически, но может быть обновлена и вручную соответствующей командой ALTER TABLE ... UPDATE STATISTICS. Сам план не содержит всей желаемой информации (ожидаемая мощность выборки, время CPU), но позволяет понять, что же происходит.
Совместимость с ANSI SQL потрясающая даже для реляционной СУБД. Синтаксис не только соответствует стандарту даже в мелочах (GROUP BY и RETURNING), но и имеет свои специфические расширения. К примеру, во внешнем ключе достаточно указать, на какую таблицу идет ссылка, столбцы указывать необязательно. Есть возможность получать строки результирующей выборки в JSON, достаточно написать SELECT ** FROM ... (еще один реверанс в сторону документов). В индексе по табличной группе можно указывать порядок соединения таблиц.
Нет явного ограничения на сложность запроса и подзапросов: их можно использовать как в секциях FROM и WHERE, так и в SELECT и SET. Апофеозом стало то, что фрагмент кода живого проекта на PostgreSQL (около тысячи строк), выбранный для тестирования особенностей диалекта, выполнился без единой синтаксической ошибки.
Заключение
Как видим, вариантов горизонтального масштабирования реляционной модели на кластер не так уж и много. Весьма популярно шардирование по хешу ключа. Но это вносит заметные ограничения на эффективные джойны. Фактически с тем же удобством, что и на одном старом добром SQL-сервере, можно оперировать данными только в одной партиции. А в таком случае это данные, шардированные по одному‑единственному значению ключа.
Гораздо интереснее смотрится подход FoundationDB/Akiban. Все‑таки объединять таблицы в группы‑документы — это гораздо естественнее, чем использовать для их хранения специальные типы колонок (например, JSON) внутри таблиц, как делают классические СУБД. Операции уже внутри этого агрегата становятся более эффективными, как в документо‑ориентированных NoSQL, а самое приятное — состав агрегата можно менять.
Радует, что СУБД переходят на однопоточную асинхронную (событийную, акторную, ...) архитектуру. Ту самую, которая может быть тебе известна по Node.js или Erlang. Один поток, занимающий одно ядро CPU, может обслуживать в таком варианте десятки тысяч одновременных запросов. Это действительно помогает существенно повысить throughput, то есть количество обрабатываемых запросов в единицу времени.
Печалит, что почти все новейшие СУБД — проприетарные. Остается только надеяться, что это болезнь роста и вскоре мы увидим их и под свободными лицензиями. Похоже, что рынок NewSQL еще более перегрет, чем NoSQL. Множество решений конкурируют друг с другом. Каждый обещает совершенно уникальную, а по факту — вполне известную технологию. Поддержка реляционной модели изрядно хромает. Транзакционность снова воспринимается как конкурентное преимущество, но реализовать ее под силу далеко не всем. Действительно стоящих и прорывных решений — единицы. Будем ждать, когда недостойные вымрут, а достойные станут еще лучше. Мы будем болеть за FoundationDB ;).