company_banner

Метаданные S3 в PostgreSQL. Лекция Яндекса

    Это вторая лекция с Я.Субботника по базам данных — первую мы опубликовали пару недель назад.

    Руководитель группы СУБД общего назначения Дмитрий Сарафанников рассказал об эволюции хранилища данных в Яндексе: о том, как мы решили делать S3-совместимый интерфейс, почему выбрали PostgreSQL, на какие грабли наступили и как с ними справились.


    — Всем привет! Меня зовут Дима, в Яндексе я занимаюсь базами данных. Расскажу, как мы делали S3, как пришли к тому, чтобы делать именно S3, и какие хранилища были раньше. Первое из них — Elliptics, оно выложено в опенсорсе, доступно на GitHub. Многие, возможно, сталкивались с ним.

    Это, по сути, распределенная хэш-таблица с 512-битным ключом, результат SHA-512. Оно образует такое кольцо ключей, которое случайным образом делится между машинами. Если вы хотите добавить туда машин, ключи перераспределяются, происходит ребалансировка. У этого хранилища есть свои проблемы, связанные, в частности, с ребалансировкой. Если у вас достаточно большое количество ключей, то при постоянно растущих объемах вам нужно постоянно туда докидывать машин, и на очень большом количестве ключей ребалансировка может просто не сходиться. Это было достаточно большой проблемой.

    Но в то же время это хранилище отлично подходит для более-менее статических данных, когда вы разово большой объем заливаете, и потом гоняете на них read-only-нагрузку. Для таких решений это подходит отлично.

    Едем дальше. Проблемы с ребалансировкой были достаточно серьезные, поэтому появился следующий storage.

    В чем его суть? Это не key-value-хранилище, это value-хранилище. Когда вы заливаете туда какой-то объект или файл, он вам отвечает ключом, по которому потом можно забрать этот файл. Что это дает? Теоретически — стопроцентную доступность на запись, если у вас в хранилище есть свободное место. Если у вас лежат одни машинки, вы просто пишете в другие, которые не лежат, на которых есть свободное место, получаете другие ключи и по ним спокойно забираете свои данные.

    Этот storage очень легко масштабируется, можно закидывать его железом, он будет работать. Он очень простой, надежный. Его единственный недостаток: клиент не управляет ключом и все клиенты должны хранить где-то у себя ключи, хранить маппинг своих ключей. Всем это неудобно. По сути, это очень похожая для всех клиентов задача, и каждый решает ее по своему в своих метабазах и т. д. Это неудобно. Но в то же время не хочется потерять надежность и простоту этого storage, по сути оно работает со скоростью сети.

    Тогда мы начали смотреть на S3. Это key-value storage, клиент управляет ключом, все хранилище делится на так называемые бакеты. В каждом бакете пространство ключей от минус бесконечности до плюс бесконечности. Ключ — это какая-то текстовая строка. И мы на нем остановились, на этом варианте. Почему S3?

    Все достаточно просто. К этому моменту уже написано много готовых клиентов для всевозможных языков программирования, уже написано много готовых инструментов для хранения чего-нибудь в S3, скажем, бэкапов баз данных. Андрей рассказывал про один из примеров. Там уже достаточно продуманное API, которое годами обкатывалось на клиентах, и ничего там придумывать не надо. API имеет много удобных фич типа листингов, мультипарт-аплоадов и так далее. Поэтому решили остановиться на нем.

    Как из нашего storage сделать S3? Что приходит в голову? Так как клиенты сами хранят у себя маппинг ключей, мы просто возьмем, поставим рядом БД, и будем в ней хранить маппинг этих ключей. При чтении просто будем находить ключи и storage в нашей базе данных, и отдавать клиенту то, что он хочет. Если изобразить это схематически, как происходит заливка?

    Есть некая сущность, здесь она называется Proxy, так называемый бэкенд. Он принимает файл, заливает его в storage, получает оттуда ключ и сохраняет его в БД, Все достаточно просто.

    Как происходит получение? Прокси находит в базе нужный ключ, идет с ключом в storage, скачивает оттуда объект, отдает его клиенту. Все тоже просто.

    Как происходит удаление? Прокси при удалении напрямую со storage не работает, так как тут трудно координировать базу и storage, поэтому он просто идет в БД, говорит ей, что этот объект удален, там объект перемещается в очередь на удаление, и потом в фоновом режиме специально обученный профессионал-робот забирает эти ключи, удаляет их из storage и из базы. Тут все тоже достаточно просто.

    В качестве БД для этой метабазы мы выбрали PostgreSQL.

    Вы уже знаете, что мы его очень любим. С переездом Яндекс.Почты у нас накопилась достаточная экспертиза по PostgreSQL, и когда переезжали разные почтовые сервисы, у нас выработалось несколько так называемых шаблонов шардирования. На один из них хорошо лег S3 с небольшими изменениями, но он хорошо туда подошел.

    Какие есть варианты шардирования? Это большое хранилище, в масштабах Яндекс сразу надо думать, что объектов будет много, надо сразу продумывать, как это все шардировать. Можно шардировать по хэшу от имени объекта, это самый надежный способ, но он здесь не будет работать, потому что S3 имеет, например, листинги, которые должны показывать список ключей в отсортированном порядке, когда вы захэшируете, у вас все сортировки пропадут, надо вынимать все объекты, чтобы выдача соответствовала спецификации API.

    Следующий вариант, можно шардироваться по хэшу от имени или id бакета. Один бакет может жить внутри одного шарда БД.

    Еще вариант — шардировать по диапазонам ключей. Внутри бакета пространство от минус бесконечности до плюс бесконечности, мы можем его разбить на сколько угодно диапазонов, этот диапазон мы называем чанк, он может жить только в одном шарде.

    Мы выбрали третий вариант, шардирование по чанкам, потому что чисто теоретически в одном бакете может быть бесконечное количество объектов, и он тупо не влезет в одну железку. Тут будут большие проблемы, так мы разрежем и разложим по шардам так, как нам угодно. Здесь все.

    Что получилось? Вся БД состоит из трех компонентов. S3 Proxy — группа хостов, там тоже БД. PL/Proxy стоят под балансером, туда летят запросы от того бэкенда. Дальше S3Meta, такая группа бас, в которой хранится информация про бакеты и про чанки. И S3DB, шарды, где хранятся объекты, очередь на удаление. Если изобразить схематически, то выглядит так.

    В S3Proxy приходит запрос, он ходит в S3Meta и в S3DB и выдает наверх информацию.

    Рассмотрим подробнее. S3Proxy, внутри него созданы функции на процедурном языке PLProxy, это такой язык, который позволяет вам выполнять удаленно хранимые процедуры или запросы. Вот так примерно выглядит код функции ObjectInfo, по сути, Get запрос.

    Кластер по LProxy имеет оператор Cluster, в данном случае это db_ro. Что это значит?

    Если типичная конфигурация шарда БД, есть мастер и две реплики. Master входит в кластер db_rw, все три хоста входят в db-ro, это куда можно послать read only запрос, а в db_rw посылается запрос на запись. В кластер db_rw входят все мастера всех шардов.

    Следующий оператор RUN ON, он принимает либо значение all, значит, выполнить на всех шардах, либо массив, либо какой-то шард. В данном случае он принимает на вход результат функции get_object_shard, это номер шарда, на котором лежит данный объект.

    И target — какую функцию вызывать на удаленном шарде. Он вызовет эту функцию и подставит туда аргументы, которые прилетели в эту функцию.

    Функция get_object_shard тоже написана на PLProxy, уже кластер meta_ro, запрос полетит на шард S3Meta, который вернет эту функцию get_bucket_meta_shard.

    S3Meta может тоже шардироваться, мы тоже это заложили, пока это неактуально, но возможность есть. И вызовет функцию get_object_shard на S3Meta.

    get_bucket_meta_shard — это просто хэш текста от имени бакета, S3Meta мы пошардировали просто по хэшу от имени бакета.

    Рассмотрим S3Meta, что в ней происходит. Самая важная информация, которая там есть, это таблица с чанками. Я немного вырезал ненужную информацию, самое главное осталось — это bucket_id, начальный ключ, конечный и шард, в котором лежит этот чанк.

    Как по такой таблице мог бы выглядеть запрос, который нам вернет чанк, в котором лежит, например, объект test? Примерно вот так. Минус бесконечность в текстовом виде, мы представили ее в качестве значения null, здесь есть такие тонкие моменты, что нужно проверять start_key и end_key на is Null.

    Запрос выглядит не очень, а план выглядит еще хуже. Как один из вариантов плана такого запроса, BitmapOr. И 6000 костов такой план стоит.

    Как можно по-другому? Есть такая замечательная штука в PostgreSQL как gist index, который умеет индексировать range тип, диапазон по сути, то что нам нужно. Мы сделали такой тип, функция s3.to_keyrange нам возвращает нам, по сути, диапазон. Мы можем оператором contains проверить, найти чанк, в который входит наш ключ. И по такому здесь построен exclude constrain, который обеспечивает непересечение этих чанков. Нам нужно допустить, желательно на уровне БД, каким-либо constraint сделать так, чтобы чанки не могли пересекаться между собой, чтобы только одна строка возвращалась в ответ на запрос. Иначе это будет уже не то, что мы хотели. Так выглядит план такого запроса, обычный index_scan. Это условие полностью влазит в index condition, и такой план имеет всего 700 костов, в 10 раз меньше.

    Что такое Exclude Constraint?

    Давайте создадим тестовую таблицу с двумя колонками, и повестим на нее два constraint, один уникальный, который все знают, и один exclude constraint, у которого есть параметры равно, такие операторы. Зададим с двумя операторами равно, построилась такая табличка.

    Дальше пытаемся вставить две одинаковые строки, получаем ошибку нарушения уникальности ключа на первом constraint. Если мы дропнем его, то мы нарушили уже exclusion constraint. Это общий случай уникального constraint.

    На самом деле, уникальный constraint — это тот же exclude constraint с операторами равно, но в случае exclude constraint можно построить какие-то более общие случаи.

    У нас есть такие индексы. Если присмотритесь, увидите, что это оба gist index, и вообще они одинаковые. Вы, наверное, спросите, зачем вообще дублировать это дело. Я вам расскажу.

    Индексы — такая штука, особенно gist index, что таблица живет своей жизнью, происходят апдейты, делиты и так далее, index там портится, перестает быть оптимальным. И есть такая практика, в частности расширение pg repack, индексы перестраивают периодически, раз в какое-то время их перестраивают.

    Как перестроить индекс под уникальным constraint? Создать create index currently, такой же индекс создать спокойно рядом без блокировки, и дальше выражением alter table от constraint user_index такой-то. И все, здесь все четко и хорошо, это работает.

    В случае exclude constraint, перестроить его можно только через reindex блокировкой, точнее у вас индекс эксклюзивно заблокируется, и на самом деле у вас останутся все запросы. Это неприемлемо, gist index может строиться достаточно долго. Поэтому мы держим рядом второй индекс, который меньше по объему, занимает меньше места, планер использует его, и тот индекс мы можем перестраивать конкурентно без блокировки.

    Здесь график потребления процессора. Зеленая линия — потребление процессора в user_space, она скачет от 50% до 60%. В этот момент потребление резко падает, это момент, когда перестраивается индекс. Мы индекс перестроили, старый удалили, у нас потребление процессора резко упало. Это проблема gist index, она есть, и это наглядный пример, как такое может быть.

    Когда мы все это делали, мы начинали на версии 9.5 S3DB, по плану планировали укладывать по 10 млрд объектов в каждый шард. Как вы знаете, больше 1 млрд и еще раньше начинаются проблемы, когда в таблице много строк, все становится гораздо хуже. Есть практика партционирования. На тот момент было два варианта, либо стандартный через наследование, но это работает не очень, так как там линейная скорость выбора партиции. А судя по количеству объектов, партиций нам нужно много. Ребята из Postgres Pro тогда активно пилили расширение pg_pathman.

    Мы выбрали pg_pathman, у нас не было другого выбора. Даже версия 1.4. И как видите, мы используем 256 партиций. Разбили всю таблицу объектов на 256 партиций.

    Что делает pg_pathman? Таким выражением можно создать 256 партиций, которые партиционируются по хэшу от колонки bid.

    Как работает pg_pathman?

    Регистрирует в планере свои хуки, и дальше на запросах подменяет, по сути, план. Мы видим, что на обычный запрос поиска объекта с именем test он не искал по 256 партициям, а сразу определили, что нужно в таблицу objects_54 лезть, но тут было не все гладко, у pg_pathman есть свои проблемы. Во-первых, там было достаточно много багов в начале, пока он пилился, но спасибо ребятам из Postgres Pro, они их оперативно чинили и фиксили.

    Первая проблема — сложность его обновления. Вторая проблема — prepared statements.

    Рассмотрим подробнее. В частности обновление. Из чего состоит pg_pathman?

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

    Отсюда вытекают сложности, примерно такой алгоритм обновления версии pg_pathman, мы сначала катим новый пакет с новой версией, но у PostgreSQL загружены еще в памяти старые версии, он использует ее. Это уже сразу же в любом случае базу надо рестартить.

    Дальше вызываем функцию set_enable_parent, она включает функцию в родительской таблице, которая по умолчанию выключена. Дальше выключаем pathman, перезапускаем базу, говорим ALTER EXTENSION UPDATE, в это время у нас падает все в родительскую таблицу.

    Дальше включаем pathman, и запускаем функцию, которая есть в extension, которая перекладывает объекты из родительской таблицы, которые за этот короткий промежуток времени туда нападали, перекладывает обратно в таблицы, где они должны лежать. И дальше выключаем использование родительской таблицы, поиск в нем.

    Следующая проблема — prepared statements.

    Если мы запрепарим такой же обычный запрос, поиск по bid и ключу, попробуем его повыполнять. Выполняем пять раз — все хорошо. Выполняем шестой — видим такой план. И в этом плане видим все 256 партиций. Если посмотреть внимательно на эти условия, мы здесь видим доллар 1, доллар 2, это так называемый генерик план, общий план. Первые пять запросов планы строились индивидуально, использовались индивидуальные планы для этих параметров, pg_pathman мог сразу определить, потому что параметр заранее известен, мог сразу определить таблицу, куда надо идти. В этом случае он этого сделать не может. Соответственно, в плане должны быть все 256 партиций, а когда экзекьютор пойдет такое исполнять, он пойдет и будет брать shared lock на все 256 партиций, и производительность такого решения сразу же не годится. Она просто теряет все свои преимущества, и любой запрос выполняется безумно долго.

    Как мы вышли из этого положения? Пришлось все завернуть внутри хранимых процедур в execute, в динамическую SQL, чтобы не использовались prepared statements и план каждый раз строился. Так все работает.

    Минус в том, что вам придется весь код запихивать в такие конструкции, которые трогают эти таблицы. Здесь такое сложнее читать.

    Как происходит распределение объектов? В каждом шарде S3DB хранятся счетчики чанков, там тоже есть информация о том, какие чанки в этом шарде лежат, и для них хранятся счетчики. На каждую мутирующую операцию над объектом — добавление, удаление, изменение, перезапись — изменяются эти счетчики для чанка. Чтобы не апдейтить одну и ту же строчку, когда в этот чанк идет активная заливка, мы используем достаточно стандартный прием, когда мы делаем insert дельта-счетчика в отдельную таблицу, и раз в минуту специальный робот проходит и агрегирует все это, апдейтит счетчики у чанка.

    Дальше эти счетчики с некоторой задержкой доставляются на S3Meta, там уже есть полная картина о том, сколько в каком чанке счетчиков, дальше это можно смотреть распределение по шардам, сколько в каком шарде объектов, и на основании этого принимается решение, куда попадает новый чанк. Когда вы создаете бакет, для бакета по умолчанию создается единственный чанк от минус бесконечности до плюс бесконечности, в зависимости от текущего распределения объектов, которое знает S3Meta, он попадает в какой-либо шард.

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

    Мы делаем так, чтобы эти чанки были небольшие. Делаем это для того, чтобы в случае чего этот небольшой чанк можно было перетащить в другой шард. Как происходит сплит чанка? Вот обычный робот, он идет и двухфазным коммитом сплитит этот чанк в S3DB и обновляет информацию в S3Meta.

    Перенос чанка — это чуть более сложная операция, это двухфазный коммит над тремя базами, S3Meta и двумя шардами, S3DB, из одного тащится, в другой складывается.

    В S3 есть такая фича, как листинги, это самая сложная штука, и с ней тоже возникли проблемы. По сути, листинги, это вы говорите S3 — покажи мне объекты, которые у меня лежат. Красным выделен параметр, который сейчас имеет значение Null. Этот параметр, делиметр, разделитель, вы можете указать, листинги с каким разделителем вы хотите.

    Что это значит? Если делиметр не задан — мы видим, что нам просто отдается список файлов. Если мы задаем делиметр, по сути, S3 должно нам показать папки. Должно сообразить, что здесь есть такие папки, и по сути, показывает все папки и файлы в текущей папке. Текущая папка задается префиксом, этот параметр здесь Null. Мы видим, что там лежит 10 папок.

    Все ключи не хранятся в какой-то иерархической структуре древовидной, как в файловой системе. Каждый объект хранится строкой, и у них простой общий префикс. S3 должно само понять, что это попка.

    Такая логика достаточно плохо ложится на декларативной SQL, ее достаточно легко описать императивным кодом. Первый вариант был сделан именно так, просто хранимые процедуры на PL/pgSQL. Он императивно обрабатывал эту логику в цикле, требовал уровня repeatable read. Мы должны видеть только один снимок, все запросы исполнять с одним снапшотом. Иначе, если кто-то после первого запроса туда что-то зальет, мы получим неконсистентные листинги.

    Потом нам удалось переписать все это на Recursive CTE, он получился очень громоздкий со сложной логикой, там без пол-литра не разберешься, а еще все это обернуто в execute внутри PL/pgSQL. Зато получили ускорение, в некоторых случаях до ста раз. Здесь приведены, например, графики перцентилей, таймингов ответа функции list objects. То, что было до и после.

    Эффект визуально ощутимый, и по нагрузке тоже.

    Мы проводили оптимизацию в несколько этапов. Вот еще один график еще одной оптимизации, когда у нас высокие квантили просто упали до низких.

    Для тестирования мы используем Docker, про Behave и тестирование Behave есть замечательный доклад Александра Клюева. Обязательно посмотрите, все очень удобно, понятно, тесты писать теперь счастье.

    У нас есть что еще пооптимизировать. Самая острая проблема, как я вам показывал, это потребление CPU на S3Meta. Gist index съедает очень много CPU, особенно когда он становится неоптимально построенным после многочисленных апдейтов, делитов. CPU на S3Meta явно не хватает. Можно штамповать реплики, но это будет неэффективная утилизация железа. У нас есть группа хостов с PLProxy под балансером, которые стоят и удаленно вызывают функции на S3Meta и S3DB. По сути, там процессор можно заставить сжечь прокси. Для этого нужно организовать логическую репликацию этих чанков с S3Meta на все прокси. В принципе, мы этим планируем заняться.

    В логической репликации есть ряд проблем, которые мы решим, попробуем это дотолкать до апстрима. Второй вариант — можно отказаться от гиста, попробовать положить этот текстовый range в btree. Это не одномерный тип, а btree работает только с одномерными типами. Но условие того, что чанки у нас не должны пересекаться, позволит наш кейс положить в btree. Мы буквально вчера сделали прототип, который работает. Он реализован на PL/pgSQL-функциях. Мы получили заметное ускорение, будем оптимизировать в этом направлении.
    • +36
    • 6,9k
    • 9

    Яндекс

    480,00

    Как мы делаем Яндекс

    Поделиться публикацией
    Комментарии 9
      +4
      Хм. Прям чем-то знакомым повеяло…

      Лет 6–7 назад мне в составе команды одного крупного американо-белорусского аутсорсера довелось делать сетевое файлохранилище для некоего очень большого зелёного банка. Метаданные в базе, файловое хранилище отдельно, классический CRUD с блобами по ключу. Не в стиле S3, но близко. К счастью, масштаб был не на дофигаллиарды блобов, а всего лишь на миллионы, так что БД, по условиям заказчика — DB2 (та ещё гадость) — как-то без шардирования справлялась. Oracle на тестах вела себя получше, конечно, но не разрешили…

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

      Где-то у меня даже лекция-постмортем валяется о том, как подобные файлохранилища проектируются, — взгляд с чуточку более высокого уровня, чем данная статья, — могу попробовать раскопать, если кому-то это покажется интересным.
        +1
        Обязательно раскопать!
          0
          Ок, попробую оформить в статью. Но быстро не обещаю, ближайшие несколько выходных у меня заняты.
        0
        Сколько теперь котиков помещается в БД?
          –3
          Интереснейшая статья неумехи с высш(ейш)им образованием.

          Что называется, диплом этому парню дали за милые глаза. Вполне возможно, подведенные косметикой.

          Для затравки ( дальше, чую серцем, будет ишшо теплее ) — цитата — Как происходит удаление? Прокси при удалении напрямую со storage не работает, так как тут трудно координировать базу и storage, поэтому он просто идет в БД, говорит ей, что этот объект удален, там объект перемещается в очередь на удаление, и потом в фоновом режиме специально обученный профессионал-робот забирает эти ключи, удаляет их из storage и из базы. Тут все тоже достаточно просто.
          — конец цитаты — А теперь, дети, кто откроет руководство по формату файлов средненькой СУБД.

          Итак, Ванечка, что там говорится сначала
          — Ванечка — Сначала была системная страница, разграничивающая системные ячейки зарезервированного блока запоминающего устройства ( размером в 8192 байта, из которых 16 отданы для адреса следующей странички, а ещё 16 отданы для адреса самой себя, поскольку это якорь ).
          — Петечка, а что дальше?
          — Петечка — ( немного бодрее ) дальше идет блок системных страниц, разграничивающих уже странички запоминающие текст ( цифровое карго ) данных
          — Гришенька, а что ещё дальше
          — Гришенька — как что, Яков Исаевич, вы же нам вчера только все уши прожужжали, что дальше идет целый суперблок этих самых страничек текста?! Вы что, риторические вопросы задаете.
          — и тут детей прорвало.

          И здесь, к унынию Яндекса и его незадачливых выпускников МГУ и прочих ВУЗ-ов, которые по праву считаются источником неисчерпаемого богатства знаний приходит понимание, что вместо того, чтобы построить свою собственную базу, они построили, с огромными издержками в ЗВР ( поскольку быстрые запоминающие устройства продаются только за валюту ) каталог СУБД сверху каталога СУБД, то есть затраты на его хранение и содержание выше чем нормальной СУБД, которую четыре нормальных студента ПТУ пишут за пол — года, по экспоненте выше.

          Вы что господа, нарочно профессионалов провоцируете громить Ваши статьи?

          Ну да… про то самое удаление. Оно во всех многопользовательских СУБД выполняется отдельной службой, после того как запись помечается в системных страничках, как удаленная. Но без затрат на выполнение SQL запроса, языка бизнес запросов, каждый и которых изменяется единицами или больше в валютных эквивалентах.

          Так ведь, ребята, можно вообще, провода лаборатории через Луну закрутить, и сказать, что это в память о великих космонавтах страны, где на своих не скупятся.
            0

            На самом деле "многофазное" удаление это очень хорошая практика в любой системе хранения. Основной её плюс в том, что она позволяет легко "откатить" состояние базы данных на уровне приложения без танцев с бинлогами/бекапами/снапшотами/логамибубном.


            Двухшаговое удаление ещё упрощает написание verifier'ов которые следят за нарушениями соглашений "модели данных", например о том что данные которые были "удалены" в одной базе всё ещё имеют живые ссылки в какой-либо другой базе. У нас так регулярно находятся проблемы в процедуре подчистки базы от старых ревизий файлов, не потому что она бажная, а потому что мир вокруг неё меняется.


            ПС. Вау! У кого-то серьёзный батхёрт на тему высшего образования. Ну и уровней абстракций походу тоже.

              –1
              За деревьями леса не увидел. Батхерт по поводу неоправданных и необратимых / неокупаемых затрат, на устройства, которые принесли 0 инноваций в копилку ПО страны.

              А поскольку людей, которые имеют высшее образование понимать затраты — против целесообразности — против стоимости труда, обучают взвешивать и принимать решения с младых ногтей, есть причины высказать крайнее неудовольствие не только работой, но уже школой людей, в которой проглядывают проблемы имеющие крайне сложные проблемы. Все остальное можно натаскать делать макаку из зоопарка.
            0

            Весьма практическая статья, спасибо.


            Я не фанат SQL, поэтому если бы я проектировал эту систему, то спрятал бы S3DB за API и сделал бы всю коммуникацию RPC-based (у нас внутри gRPC, у FB Thrift) ala services.
            Meta бы делал на Zookeeper/etcd, чтобы Proxy могли подписываться на изменения "топологии". S3DB "сервис" бы в случае ошибки маршрутизации просто бы говорил, что proxy ошиблась и ей нужно получить новую таблицу у Meta. Что же использует S3DB для хранения не так важно, это может быть Postgres/MySQL/RocksDB/etc.


            Ну и да, если бы это делалось в Dropbox, то полный S3 API мы бы наверное не поддерживали, а в замен сделали бы свой S3-подобный API. Описали бы протобафы, а код для клиентов под все языки сгенерился бы автоматом (aka codegen).


            Disclaimer: в своё время я работал над Elliptics: делал из eblob LSM и писал первые версии dnet_recovery.

              0

              Хотелось бы чуть больше информации по эксплуатации этой системы.


              По структурной целостности:


              • Как бекапите метаданные Андрей Бородин рассказывал в лекции "Разгоняем бэкап". Было бы интересно услышать больше про то как эти бекапы верифицируются. В QnA секции прошлись по этому немного, но только с точки зрения физической структуры (checksum'ы и index'ы), а не логической — то что бекап действительно является репликой существующей базы и способен подключиться к кластеру как слейв.
              • Какие verifier'ы вы запускаете на данных, например, как вы проверяете что реплики не разошлись, что данные что были записаны всё ещё доступны и тд и тп? Shameless plug: у нас кажется верификаций больше чем кода самого приложения.

              По доступности:


              • Что за система отвечает за замену умерающих/мёртвых машин. Особенно интересен случай мееедленно умирающей машины, который очень сложно отличить от перегрева шарда.
              • Как боритесь с горячими шардами по IOPS/QPS?

              Ну и около-админские вещи: как вы безопасно деплоите код/ОС/прошивки/железо на кластер, как мониторите всю систему, итд итп

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое