Комментарии 60
но вообще на тему исследований распределенных БД стоит иногда заглядывать на jepsen.io, думаю рано или поздно там появится тест и про 4.0.0.
Итак:
1. Координатор транзакций и сторадж совмещен. Соотвественно, кратковременные тормоза в подсистеме ввода вывода приводят к тормозам на коммите. В c*one — координатор отделен от стораджа, стораджа составляют отдельный кворум со спекулятивным исполнением, что исключает подобный сценарий.
2. Запись попадает сначала на мастер и потом на все слейвы. Соотвественно если мастер сначала тормознул и потом выключился ( как это обычно и происходит ), то часть изменений будет пропущена, а при возврате такого мастера возникнет «Data branching», который «can be reviewed and the situation be resolved» — как я понимаю вручную. До этого времени, предположу, БД не работает как минимум на запись, а может и на чтение тоже. В c*one такая ситуация невозможна.
3. Выборы взамен отказавшего мастера происходят после обнаружения отказа протоколом raft. про что подробно написано в статье — в c*one выборы не запускаются.
4. Не нашел про партиционирование транзакций ни слова, предположу что мастер глобальный на все транзакции кластера. А как написано «If the master goes down, any running write transaction will be rolled back and new transactions will block or fail until a new master has become available.» означает что до завершения выборов запись данных полностью не работает. В c*one нет единого глобального мастера — их несколько, выборов нет, как уже писал.
5. А вот это намекает на проблемы в масштабировании «All instances in the cluster have full copies of the data in their local database files». Ну или это некорректно сформулировано.
В общем и целом, на основании доки, в neo4j достаточно классический подход к HA, он приблизительно такой же, как и в sql server например.
1) А как же ваш тарантул, почему он не подошел?
2) Ваша база опровергает CAP ?
2. нет
Не эксперт по Тарантулу, но, насколько я знаю, три года назад (когда, судя по посту, начинался проект из топика) они только начинали создавать персистентное хранилище Vinyl. Если бы это было год назад, мой первый вопрос тоже был бы — почему не Тарантул.
Выходит если у вас есть строгая консистентность, значит вы жертвуете доступностью, а иначе CAP для вас не работает.
Я не знаю, подошел бы он вам или нет. Возможно это дороже, так как все в ОЗУ хранится, но работа должна быть быстрее чем твердотельными носителями.
В CAP доступность определяется как бинарное св-во, поэтому считается не возможным выполнение таких строгих требований одновременно.
Но на практики, нам достаточно что система доступна 99.9999%, что позволяет системе удовлетворять всем 3-м св-м из теоремы в большую часть времени. На сколько я это понимаю.
А вы пробовали большее количестве координаторов, укладываетесь в SLA или нет (ведь потребности будут расти)?
Еще вопрос, сколько человек писало проект?
И когда планируется полный переход на вашу базу?
Основная разработка велась силами 2 человек — ваш покорный слуга и hristoforov, около 6 месяцев прошло от начала проекта до начала внедрения. Впоследствии, как у нас принято, каждый из разработчиков — кто хотел — смог поучаствовать.
Переход на c*one практически уже совершен, в SQL Server остались всякие некритичные данные — то, что долго и бессмысленно переносить. Вся новая разработка происходит только на c*one.
По итогам эксплуатации основная идея видно, что рабочая, за это время было множество мелких инцидентов, да и несколько крупных аварий, что позволило обнаружить и пофиксить множество проблем в основном в Cassandra — gossip (очень нехорошо ведет себя в нестабильной сети), repair, streaming, range tombstones, compaction, всего не упомнишь что потрогали или переписали.
Что, с одной стороны, может напугать, но с другой — подтверждает правильность того, что изначально выбирали движок для хранилища который мы знаем и можем сами поддерживать. Ведь проблемы есть абсолютно со всеми СУБД ( про синие экраны SQL Server рассказывал в лицах на Джокере, если помните ) весь вопрос в том кто и как быстро их может диагностировать и исправлять.
В опенсурс не планируете?
Cерьезный прирост — это слишком обще. Интересно было бы узнать подробности что было до перехода на scylla и после? Что за данные? Насколько запросы к ним попадают в кеш? Да и общий профиль нагрузки. Не знаю, насколько это влезет все в коммент — может и на статью потянет ;-)
Наш сайт — конечно же доступен при отказе любого из компонентов системы, в том числе и при полном отказе датацентра. Более подробно об этом можно посмотреть например тут: www.youtube.com/watch?v=JZiQKgx2HJM
А цель данного гипотетического предположения в том, чтобы продемонстрировать читателю простую математику сложения отказов при масштабировании системы.
Возможно, если Вы перечитаете собственную статью, то Вы найдете, что это предложение описывает, среди прочего, недостатки использования СУБД и преимущества архитектуры отказывающейся от СУБД службы.
Моя цель была объяснить, что 200 серверов хранилища данных выходят из строя по иным причинам, их отказ, когда серверов такое множество, выходят из строя одинаково часто, не зависимо от присутствия службы СУБД, или её отсутствия, и что в принципе, если отказаться от содержания статьи, рассматривать отказы техники, дело интересное, и устройства ПО, которые при этом надо выполнить, ни чем не отличаются, если противопоставить СУБД или не СУБД службы транспортировки и складирования цифровых запасов.
После чего отсутствие СУБД становится бессмысленным и зловредным.
Нам удалось добиться такого времени отклика с использованием сборщика мусора G1, позволяющего указать цель по продолжительности пауз GC. Однако, иногда, достаточно редко, паузы сборщика выходят за рамки 50 мс, что может привести к ложному обнаружению отказа. Чтобы такого не было, координатор не сообщает об отказе удаленной ноды при пропаже первого же heartbeat-сообщения от нее, только если пропало несколько подряд.Так нам удалось добиться обнаружения отказа ноды координатора за 200 мс.
А можно подробностей про машины на которых запускаете, с какими параметрами запускаете, какая версия java и были ли сравнения с другими GC ?
В ключах тоже все более менее стандартно:
-XX:+DisableExplicitGC -XX:+UseG1GC -XX:MaxGCPauseMillis=50 XX:SurvivorRatio=8 XX:MaxTenuringThreshold=3 -XX:MetaspaceSize=256M -XX:-OmitStackTraceInFastThrow -XX:+PerfDisableSharedMem -Xms7g -Xmx7g -XX:+ParallelRefProcEnabled -XX:InitiatingHeapOccupancyPercent=25 -XX:GCPauseIntervalMillis=400
Сравнивали в основном с CMS, есть планы попробовать metronome из OpenJ9, да руки не дошли. По сравнению с CMS, G1 дает лучшую адаптацию к изменению паттернов нагрузки и короче паузы на ГЦ в ситуации наличия значительного запаса по памяти и CPU. Поскольку для координаторов минимальные паузы критичны, то выбрали G1. При этом на нодах-хранилищах работает CMS — там такие большие запасы экономически неоправданны, да и паузы в 150-200ms некритичны благодаря спекуляциям.
Не думали shenandoah погонять или java10 где G1 еще более паралельный? В любом случаи интересно будет ознакомиться с результатми тестирования вами metronome.
С индексами интересно также то, что в C*one можно выключать/включать чтение из них индивидуально. Таким образом мы можем ставить какие либо эксперименты с ними — например сделать 2 индекса с одинаковым индексным выражением, но разным порядком следования ключей или перестраивать их плавно переводя чтения из старого в новый индекс итп
www.postgres-xl.org — это смотрели как вариант?
> [..] То есть добавление индексов почти не потребляет ресурсы и практически не влияет на скорость применения модификаций.
При обновлении записи, лок над записью удерживается пока не обновятся ее «копии» во всех индексах? Не могли бы рассказать эту часть подробнее, почему реализованный механизм быстрее тех, что используется в «обычных SQL БД»?
Чем больше ключей в индексе, тем больше глубина дерева, тем больше таких чтений и локов необходимо на изменение каждого ключа -> скорость записи деградирует нелинейно.
Таким образом запись в индекс становится дороже, чем запись в основную таблицу ( если она heap, как в oracle ) или такой же ( если она тоже btree как в SQL Server ).
В c*one ( на самом деле в Cassandra ) для хранения данных исползуется LSM Tree, запись в которую не деградирует при увеличении их количества. Кроме того запись не требует предварительного чтения и применяется сначала в memtable ( т.е. в память ).
Поэтому, c*one для генерации изменения в индекс не нужно его предварительно читать — все изменения всех индексов могут быть сгенерированы на основании данных одной только изменяемой в транзакции записи. При коммите в батч записывается просто несколько дополнительных мутаций для индексов, то есть батч вырастает на несколько десятков байт, что ничтожно, относительно затрат на обработку самого батча. Сами эти затраты тоже будут легче относительно «классической» БД, так как это запись в лог батча и применение мутаций в мемтаблицы соотвествующих реплик, плюс необходимая коммуникация, которая, впрочем, происходит параллельно и асинхронно ( без необходимости что то еще читать с дисков ).
Не пробовали materialised views? Похоже на ваши индексы.
https://www.datastax.com/dev/blog/materialized-view-performance-in-cassandra-3-x
С точки зрения реализации, materialized view приносит с собой дополнительную нагрузку при записи: + дополнительное чтение и logged batch на каждый индекс на каждой реплике.
В c*one индекс функционально похож ( местами даже более удобен ), но за счет гарантий согласованности на координаторе его поддержка практически ничего не стоит ( только дополнительная запись, которая в Cassandra значительно дешевле чтения ).
Ну и понятно MV не может быть строго консистентен ( только eventually ) с измененными данными в основной таблице, об этом нужно помнить.
в четырех московских дата-центрах, что позволяет обеспечивать сетевую задержку менее 1 мс между ними
Я правильно понял, что тут речь идет о задержке <1мс именно между ДЦ?
CREATE KEYSPACE guest_history WITH replication = {
'class': 'NetworkTopologyStrategy',
'kc': '1',
'pc': '1',
'dc': '1'
};
да, между ДЦ у нас 0.6-0.7мс пинг, ну и каналы норм толщины, в таких условиях использование глобального кворума не вносит неприемлемую для нас задержку.
Недавно правда для клиентов начали свой протокол делать — в эксплуатации не очень удобно иметь двунаправленные соединения на всх клиентов.
Не могли бы вы привести краткое сравнение с Foundation DB?
Случай занес меня на доклад по этой БД на highload 2018 в Москве. На основании прослушанного ( впрочем, ошибиться можем как я, так и докладчик ), можно насравнивать следующее:
Foundation DB является binary KV хранилищем, то есть структура записи не описывается в БД
Из чего есть следующие следствия:
1. Задачи (де)сериализации ключей и значений переходят на прикладного разработчика. эволюция схемы — неприменимое понятие к FDB, соответсвенно разработка прикладной логики усложняется — нужно или писать (де)сериализаторы с поддержкой версионирования и/или разрабатывать процедуры миграции схемы данных самому. В C*One же, так как она использует кассандру все эти задачи решаются на стороне БД.
2. Индексы тоже неприменимое понятие — поскольку FDB не знает о структуре данных, то и индексы по полю построить не может. Саму задачу индексирования придется решать тоже на уровне прикладной логики ( чему и была посвящена значительная часть доклада ). И если в рамках ACID транзакции относительно просто поддержать индексирования новых данных, то для (пере)индексирования старых придется писать какие то приложения и вряд ли они будут эффективными, так как потребуют транзакций на каждый ключ сущности, а сама логика ( даже и ) параллельного обхода всех данных будет производится на клиенте.
В C*One построение индекса происходит непосредственно на нодах с данными, параллельно и независимо друг от друга без необходимости транзакций в каком то виде за счет наличия часов лэмпорта и встроенного механизма разрешения конфликтов.
3. Невозможность изменения в рамках транзакции какой-то части данных или ключа — придется менять значение или ключ целиком. Это приводит к умножению записи в основную таблицу, все ее «индексы» и коммит лог, что может быть проблемой уже при достаточно средних размерах записи и количестве их изменений.
Ноды с данными являются и координаторами транзакций, о минусах чего уже писал.
Интересно работает распределение данных между нодами кластера. Все данные сортированы по значению ключей, ноды сами выбирают какая область ключей будет храниться на какой из них исходя из текущих данных. Заранее такие области выбрать, очевидно, невозможно из-за возможных перекосов в количестве ключей той или иной области значений. Определив эту область нода переносит на себя недостающие части и удаляет лишние после завершения переносов. Процесс повторяется непрерывно по мере жизни кластера. Звучит ОК, но такую машинерию на практике чрезвычайно сложно реализовать так, чтобы она работала надежно и не ломалась при различных сценариях отказов.
Вот и все, что я смог вынести из доклада Олега Илларионова. большое спасибо ему за доклад и удачи в инновациях!
У меня вопрос
Вы пишите
С внедрением C*One снизились и задержки: в SQL операция записи занимала около 4,5 мс. В C*One — около 1,6 мс. Длительность транзакции в среднем меньше 40 мс, коммит выполняется за 2 мс
Правильно ли я понимаю, что «в среднем» — это AVG от времени выполнения всех транзакций?
Время считается от входа в координатор до выхода сообщения о комите на координаторе или на клиенте?
Верно ли, что коммит выполняется за 2 милисекунды – время от отправки batch-запроса в Cassandra от получения ОК от неё?
Если да, то этот ОК кссандра отправляет после успешной реальной физической записи на диск какого количества копий?
И ещё вопрос — диски в кассандре SSD, крутящиеся или какая-то смесь?
Заранее спасибо за ответы и ещё раз спасибо за подробный пост с картинками!
Правильно ли я понимаю, что «в среднем» — это AVG от времени выполнения всех транзакций?
Время считается от входа в координатор до выхода сообщения о комите на координаторе или на клиенте?
Здесь сравнивалось среднее время задержки выполнения методов DAO один с реализацией хранения данных в SQL против другого с реализацией в c*one. Методы семантически повторяют друг друга 1 в 1. Задержки измерялись на клиенте.
Верно ли, что коммит выполняется за 2 милисекунды – время от отправки batch-запроса в Cassandra от получения ОК от неё?
Это время от момента запроса на коммит с клиента до момента подтверждения его от координатора. Соответственно время включает в себя коммуникацию с координатором, время формирования и отправки батча в кассандру, применения кассандрой батча, удаление батча, ну и всевозможные acks.
Если да, то этот ОК кссандра отправляет после успешной реальной физической записи на диск какого количества копий?
В Cassandra, в отличии от классических СУБД физические копии на диск не пишутся для каждой мутации. Записи применяются в memtables и записываются в коммит лог ( более подробно тут )
Вместо этого используется кворум нод — запись считается успешной, если ее прием подтвердили 2 из 3 нод. Потеря такой записи в этом случае вероятна только если единовременно откажут 2 реплики. Поэтому важно эти реплики располагать так, чтобы такого не происходило — например мы располагаем их в разных датацентрах.
И ещё вопрос — диски в кассандре SSD, крутящиеся или какая-то смесь?
Смесь. Для хранения сстаблиц используется SSD, для коммит логов и архивов — HDD.
Архивы — другая интересная деталь позволяет нам не бакапить данные с хранилищ. В сочетании со ежедневными снапшотами и архиворованием коммит логов они позволяют нам восстановить состояние данных на любой момент за последние 30 дней. И этот процесс достаточно быстрый — старая копия данных поднимается за 15-30 минут, что очень быстро для 36ТБ датасета. Этой фичей мы уже пользовались несколько раз при расследовании различного рода инцидентов связанных с пропажей данных.
Допустим, клиент прислал координатору запрос на открытие транзакции для такой-то сущности с таким-то первичным ключом. Координатор эту сущность блокирует и помещает в таблицу блокировок в памяти.
Блокирует на запись только, т.е. остальные клиенты могут читать?
Таким образом, клиент может прочитать собственные изменения, а другие клиенты эти изменения не видят, потому что хранятся они только в памяти координатора, в нодах Cassandra их еще нет.
А если ваш клиент в это время делает синхронный запрос в другой ваш сервис, которому надо получить из хранилища данные, актуальные относительно текущей транзакции?
И последний: R + W > N? на сколько нод пишете и со скольки читаете?
Блокирует на запись только, т.е. остальные клиенты могут читать?
Читать может, если не находится в контексте текущей транзакции. В противном случае ждет освобождения блокировки.
А если ваш клиент в это время делает синхронный запрос в другой ваш сервис, которому надо получить из хранилища данные, актуальные относительно текущей транзакции?
Естественно, запросы в другой сервис не рекомендуется делать в пределах транзакции, но за всем не уследишь. У транзакции c*one есть таймаут неактивности, сейчас он около 3 секунд, соотвественно если клиент сделал синхронный запрос в какой то другой сервис, и подвис, то через 3 секунды все блокировки автоматически снимаются и такая транзакция откатывается. Такой таймаут — еще одна фича, которой сильно не хватало в SQL Server — при отказе клиента его соединения часто провисали на долгое время, удерживая локи в транзакции и вызывая отказы нормально функционирующих клиентов.
И последний: R + W > N? на сколько нод пишете и со скольки читаете?
У нас 3 ДЦ, должны выдерживать отказ 1 из них, соотвественно 2 + 2 > 3
Блокируем альбом по ключу.
Создаем запись в таблице фотографий.
Если у фотографии публичный статус, то накручиваем в альбоме счетчик публичных фотографий, обновляем запись и коммитим транзакцию.
А не рассматривали возможность применить подход Event Soursing?
Т.е. есть некая очередь, допустим, в Kafka.
— При добавлении фотографии туда кладется событие «PhotoAdded»
— Очередь разбирается процессами, в данном случае нам нужен процесс «Album»
— У каждого процесса своя позиция в очереди
— Если нужно отмодерировать фотографию, в очередь кладется «PhotoModerated»
— Процесс «Album» должен понимать события «PhotoAdded» и «PhotoModerated»
— Процесс «Album» может быть запущен в нескольких экземплярах, каждый альбом всегда обрабатывается только один процессом (обеспечивается Kafka)
Конкурентное модифицирование отсутствует, транзакции не нужны.
Две копейки на счет RAFT. А вот твой коврумный протокол, он разве не больше генерируется сообщений чем RAFT в стейбл состоянии? Насколько я помню, когда выборы случились, хертбиты нужны только между лидером и фолловерами.
А у тебя между всеми нодами, если я правильно понял.
Имея RAFT можно также реализовать твой алгоритм. Например поверх ETCD (у которого внутри RAFT), я делал похожий алгоритм выбора лидера. ETCD обеспечивал atomic changes состояния (список нод), в лидером выбиралась нода с меньшам mod_revision. Liveness через leases.
NewSQL = NoSQL+ACID