Pull to refresh
39
0
Олег Анастасьев @m0nstermind

Главный инженер

Send message
Это перевод статьи с code.fb.com/data-center-engineering/tupperware о чем компания Southbridge скромно умалчивает. Оригинальная статья называется «Efficient, reliable cluster management at scale with Tupperware» — ни слова про убийц.

Но, на что не пойдешь, чтобы твой бложик читали.
Лучше поздно, чем никода ;-)
Случай занес меня на доклад по этой БД на highload 2018 в Москве. На основании прослушанного ( впрочем, ошибиться можем как я, так и докладчик ), можно насравнивать следующее:

Foundation DB является binary KV хранилищем, то есть структура записи не описывается в БД
Из чего есть следующие следствия:
1. Задачи (де)сериализации ключей и значений переходят на прикладного разработчика. эволюция схемы — неприменимое понятие к FDB, соответсвенно разработка прикладной логики усложняется — нужно или писать (де)сериализаторы с поддержкой версионирования и/или разрабатывать процедуры миграции схемы данных самому. В C*One же, так как она использует кассандру все эти задачи решаются на стороне БД.
2. Индексы тоже неприменимое понятие — поскольку FDB не знает о структуре данных, то и индексы по полю построить не может. Саму задачу индексирования придется решать тоже на уровне прикладной логики ( чему и была посвящена значительная часть доклада ). И если в рамках ACID транзакции относительно просто поддержать индексирования новых данных, то для (пере)индексирования старых придется писать какие то приложения и вряд ли они будут эффективными, так как потребуют транзакций на каждый ключ сущности, а сама логика ( даже и ) параллельного обхода всех данных будет производится на клиенте.
В C*One построение индекса происходит непосредственно на нодах с данными, параллельно и независимо друг от друга без необходимости транзакций в каком то виде за счет наличия часов лэмпорта и встроенного механизма разрешения конфликтов.
3. Невозможность изменения в рамках транзакции какой-то части данных или ключа — придется менять значение или ключ целиком. Это приводит к умножению записи в основную таблицу, все ее «индексы» и коммит лог, что может быть проблемой уже при достаточно средних размерах записи и количестве их изменений.

Ноды с данными являются и координаторами транзакций, о минусах чего уже писал.

Интересно работает распределение данных между нодами кластера. Все данные сортированы по значению ключей, ноды сами выбирают какая область ключей будет храниться на какой из них исходя из текущих данных. Заранее такие области выбрать, очевидно, невозможно из-за возможных перекосов в количестве ключей той или иной области значений. Определив эту область нода переносит на себя недостающие части и удаляет лишние после завершения переносов. Процесс повторяется непрерывно по мере жизни кластера. Звучит ОК, но такую машинерию на практике чрезвычайно сложно реализовать так, чтобы она работала надежно и не ломалась при различных сценариях отказов.

Вот и все, что я смог вынести из доклада Олега Илларионова. большое спасибо ему за доклад и удачи в инновациях!
Одна реплика. Поскольку связь между ДЦ хорошая, нет смысла делать дополнительные реплики.
Блокирует на запись только, т.е. остальные клиенты могут читать?

Читать может, если не находится в контексте текущей транзакции. В противном случае ждет освобождения блокировки.

А если ваш клиент в это время делает синхронный запрос в другой ваш сервис, которому надо получить из хранилища данные, актуальные относительно текущей транзакции?

Естественно, запросы в другой сервис не рекомендуется делать в пределах транзакции, но за всем не уследишь. У транзакции c*one есть таймаут неактивности, сейчас он около 3 секунд, соотвественно если клиент сделал синхронный запрос в какой то другой сервис, и подвис, то через 3 секунды все блокировки автоматически снимаются и такая транзакция откатывается. Такой таймаут — еще одна фича, которой сильно не хватало в SQL Server — при отказе клиента его соединения часто провисали на долгое время, удерживая локи в транзакции и вызывая отказы нормально функционирующих клиентов.

И последний: R + W > N? на сколько нод пишете и со скольки читаете?

У нас 3 ДЦ, должны выдерживать отказ 1 из них, соотвественно 2 + 2 > 3
длительность же всей транзакции ( та, которая 40мс ) — измеряется на координаторе от момента получения команды start от клиента до момента получение commit от клиента.
Правильно ли я понимаю, что «в среднем» — это AVG от времени выполнения всех транзакций?
Время считается от входа в координатор до выхода сообщения о комите на координаторе или на клиенте?


Здесь сравнивалось среднее время задержки выполнения методов DAO один с реализацией хранения данных в SQL против другого с реализацией в c*one. Методы семантически повторяют друг друга 1 в 1. Задержки измерялись на клиенте.

Верно ли, что коммит выполняется за 2 милисекунды – время от отправки batch-запроса в Cassandra от получения ОК от неё?


Это время от момента запроса на коммит с клиента до момента подтверждения его от координатора. Соответственно время включает в себя коммуникацию с координатором, время формирования и отправки батча в кассандру, применения кассандрой батча, удаление батча, ну и всевозможные acks.

Если да, то этот ОК кссандра отправляет после успешной реальной физической записи на диск какого количества копий?


В Cassandra, в отличии от классических СУБД физические копии на диск не пишутся для каждой мутации. Записи применяются в memtables и записываются в коммит лог ( более подробно тут )
Вместо этого используется кворум нод — запись считается успешной, если ее прием подтвердили 2 из 3 нод. Потеря такой записи в этом случае вероятна только если единовременно откажут 2 реплики. Поэтому важно эти реплики располагать так, чтобы такого не происходило — например мы располагаем их в разных датацентрах.

И ещё вопрос — диски в кассандре SSD, крутящиеся или какая-то смесь?

Смесь. Для хранения сстаблиц используется SSD, для коммит логов и архивов — HDD.

Архивы — другая интересная деталь позволяет нам не бакапить данные с хранилищ. В сочетании со ежедневными снапшотами и архиворованием коммит логов они позволяют нам восстановить состояние данных на любой момент за последние 30 дней. И этот процесс достаточно быстрый — старая копия данных поднимается за 15-30 минут, что очень быстро для 36ТБ датасета. Этой фичей мы уже пользовались несколько раз при расследовании различного рода инцидентов связанных с пропажей данных.

Тут речь идет о «классическом» индексе, основанным в большинстве классических БД на вариантах Б-дерева. Запись в Б-дерево подразумевает предварительное чтение и возможную последующую модификацию нескольких страниц индекса — как минимум на каждом уровне дерева ( для инсерта ) или даже 2 подобных прохода ( для апдейта ). Само по себе подобное чтение означает некоторое количество random reads на диск, что медленно. Кроме того, поскольку в таком классическом индексе обновляются единственные копии этих страниц, для предотвращения их одновременной модификации могут применяться локи на страницы индексов в транзакции, что вызывает дополнительные ожидания локов.
Чем больше ключей в индексе, тем больше глубина дерева, тем больше таких чтений и локов необходимо на изменение каждого ключа -> скорость записи деградирует нелинейно.
Таким образом запись в индекс становится дороже, чем запись в основную таблицу ( если она heap, как в oracle ) или такой же ( если она тоже btree как в SQL Server ).

В c*one ( на самом деле в Cassandra ) для хранения данных исползуется LSM Tree, запись в которую не деградирует при увеличении их количества. Кроме того запись не требует предварительного чтения и применяется сначала в memtable ( т.е. в память ).
Поэтому, c*one для генерации изменения в индекс не нужно его предварительно читать — все изменения всех индексов могут быть сгенерированы на основании данных одной только изменяемой в транзакции записи. При коммите в батч записывается просто несколько дополнительных мутаций для индексов, то есть батч вырастает на несколько десятков байт, что ничтожно, относительно затрат на обработку самого батча. Сами эти затраты тоже будут легче относительно «классической» БД, так как это запись в лог батча и применение мутаций в мемтаблицы соотвествующих реплик, плюс необходимая коммуникация, которая, впрочем, происходит параллельно и асинхронно ( без необходимости что то еще читать с дисков ).
шенанду думали, пробовали, но пока далеко не продвинулись — были некоторые проблемы в нем.
Да, пока кассандровский, пару Verbs добавили для транзакций. Функции StorageProxy выполняются полностью на клиенте, что снимает часть нагрузки с хранилищ. Частая проблема с сильно нагруженными на чтение кластерами — недостаток cpu на разрешение конфликтов/объединение ответов реплик — в c*one решена тем, что выполняется на клиентах, которых сильно больше, чем хранилищ и координаторов.

Недавно правда для клиентов начали свой протокол делать — в эксплуатации не очень удобно иметь двунаправленные соединения на всх клиентов.
ответил ниже
CREATE KEYSPACE guest_history WITH replication = {
'class': 'NetworkTopologyStrategy',
'kc': '1',
'pc': '1',
'dc': '1'
};


да, между ДЦ у нас 0.6-0.7мс пинг, ну и каналы норм толщины, в таких условиях использование глобального кворума не вносит неприемлемую для нас задержку.
В c*one — только QUORUM, в хранилищах со слабосогласованными данными — от local read до QUORUM. В редких случаях даже ALL ( но — не для обслуживания клиентских запросов, его для этого использовать ни в коем случае нельзя )
Нет, ведь они начали разрабатываться уже после того как мы рассказали про то, что мы сделали и запустили global indexes, можно почитать issues.apache.org/jira/browse/CASSANDRA-6477 ( вообще даже стоит почитать, чтобы понимать как это работает на самом деле )

С точки зрения реализации, materialized view приносит с собой дополнительную нагрузку при записи: + дополнительное чтение и logged batch на каждый индекс на каждой реплике.
В c*one индекс функционально похож ( местами даже более удобен ), но за счет гарантий согласованности на координаторе его поддержка практически ничего не стоит ( только дополнительная запись, которая в Cassandra значительно дешевле чтения ).

Ну и понятно MV не может быть строго консистентен ( только eventually ) с измененными данными в основной таблице, об этом нужно помнить.
Не уверен что полностью понял вопрос. Поскольку клиент — это java приложение, поэтому строго говоря никакого contact point у него нет — он сам и есть fat client. Как и обычная нода кассандры он подсоединяется к кластеру и делает gossip exchange с координаторами и хранилищами сам, точно так же как это делают сами ноды Cassandra. Естественно, запускать gossip exchange между самими клиентами бессмысленно и на эту тему мы дописали кода в gossip.
По дизайну нагрузка на координаторов небольшая, растет медленно. Сейчас на самом нагруженном кластере их всего 12, пока с SLA все ок.
Основная разработка велась силами 2 человек — ваш покорный слуга и hristoforov, около 6 месяцев прошло от начала проекта до начала внедрения. Впоследствии, как у нас принято, каждый из разработчиков — кто хотел — смог поучаствовать.
Переход на c*one практически уже совершен, в SQL Server остались всякие некритичные данные — то, что долго и бессмысленно переносить. Вся новая разработка происходит только на c*one.
да, эти бенчи я видел, они явно маркетинговые и поэтому неинтересны. буду ждать от вас отчета с нетерпением ;-)
Запускаем на Java8, понятие «машина» сейчас довольно расплывчатое — выделено 8 vcores в one-cloud.
В ключах тоже все более менее стандартно:
-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 некритичны благодаря спекуляциям.
сорри, мазанул по кнопкам, ответил ниже
Да, до того как новые запросы могут работать нужно создать под них индекс. Само создание индекса происходит в фоне, на работу клиентов почти не влияет — есть только небольшие флуктуации во временах отработки запросов. На практике самое долгое создание индекса занимало около 10 часов, но там и датасет достаточно большой — фотки, около 20ТБ.

С индексами интересно также то, что в C*one можно выключать/включать чтение из них индивидуально. Таким образом мы можем ставить какие либо эксперименты с ними — например сделать 2 индекса с одинаковым индексным выражением, но разным порядком следования ключей или перестраивать их плавно переводя чтения из старого в новый индекс итп
Если более внимательно прочитать предложение, к которому вы прицепились, оно начинается со слов «Допустим, у вас сервер» и продолжается «А если у вас 200 серверов». По этому можно предположить, что речь идет о некоем гипотетическом сайте. А цель данного гипотетического предположения в том, чтобы продемонстрировать читателю простую математику сложения отказов при масштабировании системы.

Наш сайт — конечно же доступен при отказе любого из компонентов системы, в том числе и при полном отказе датацентра. Более подробно об этом можно посмотреть например тут: www.youtube.com/watch?v=JZiQKgx2HJM

Information

Rating
Does not participate
Location
Латвия
Works in
Date of birth
Registered
Activity