Что такое шардинг в блокчейне
Термин шардинг знаком администраторам баз данных и любителям MMORPG. В контексте криптовалют это понятие используется с подачи основателя Ethereum Виталия Бутерина. Итак, шардинг в блокчейне — что это такое?
Шардинг в блокчейн-проектах
Шардинг (sharding) — технология распределения нагрузки на сеть, путем её равномерного распределения между отдельными серверами или сегментами сети. Потребность в данной технологии возникла для решения одного из наиболее существенных ограничений сети Ethereum — скорости обработки транзакций. Данное ограничение стоит на пути масштабируемости Эфириума, вычислительная сеть которого способна обрабатывать до 20 транзакций в секунду. Для сравнения, платежная система Visa способна обрабатывать около 24 тыс. транзакций в секунду.
Как работает шардинг
Оригинальная технология Ethereum работает (пока) на протоколе Proof-of-Work и предполагает, что все узлы сети должны подтверждать внесение в блок новой информации. Учитывая масштаб сети — это и тормозит процесс обработки транзакций. Шардинг предполагает, что вычислительная и накопительная нагрузка распределяется таким образом, что каждый узел обрабатывает и хранит информацию только о своем шарде.
Что такое шард
Один блокчейн программным образом делится на несколько обособленных цепочек, шард — это такая цепочка блоков. Отдельные шарды объединяются в рамках общего блокчейна при помощи сети валидаторов. Этот процесс может осуществляться на основании алгоритма Proof-of-Work или Proof-of-Stake. Начиная с анонса в 2017 году технологии шардинга ходят слухи о переходе к Ethereum 2.0. Первой фазой перехода должно стать внедрение протокола Casper, который сделает возможным использование гибридного протокола PoW и PoS и тестирование базовой модели шардинга в рамках 64 отдельных шардов внутри блокчейна Beacon Chain (основной блокчейн цепи Ethereum 2.0).
Слабые стороны шардинга
Технология имеет две основные проблемы:
- Безопасность. Сегментация блокчейна делает каждый отдельный шард уязвимым для хакеров, которым для захвата требуется значительно меньше вычислительных ресурсов. Если захват осуществлен, то в основной блокчейн начинают поступать ложные данные, а данные в захваченном шарде могут быть уничтожены.
- Коммуникация. Для взаимодействия пользователей и приложений, оказавшихся в разных шардах, потребуются дополнительные программные решения.
Третья проблема — необходимость проводить хардфорк для внедрения шардинга. И если первые две проблемы теоретически решаются техническими средствами, то хардфорк может привести к расколу внутри сообщества.
Потенциал шардинга
Потенциально шардинг позволит решить трилемму блокчейна, сформулированную Виталием Бутериным. Она заключается в том, что блокчейн предполагает три ключевых особенности: безопасность, децентрализованный принцип работы и масштабируемость. Но одновременно блокчейн может сохранять только две из трех этих особенностей. Если же получится внедрить шардинг на практике и устранить его слабые места, то масштабируемость можно будет обеспечивать без ущерба для децентрализации или безопасности.
Что такое шардинг и как это работает
Одна из самых сложных задач для разработчиков любых баз данных — масштабирование. Когда объем данных и операций с ними растет, нужно тем или иным способом увеличивать производительность системы.
Шардинг представляет собой технологию масштабирования базы данных, основная суть которой в том, чтобы разбить ее на отдельные сегменты, каждый из которых можно было бы вынести на отдельный сервер.
Если применить эту технологию к блокчейну, то шардинг — это деление сети на отдельные сектора. Каждый такой сектор (шард) содержит в себе уникальный набор смарт-контрактов и балансов счетов, и за каждым шардом закрепляются определенные узлы сети. Эти ноды отвечают за верификацию транзакций только в этом шарде, а не за все операции в блокчейне.
Какие проблемы решает шардинг
Чем популярнее становится проект, тем большее число пользователей он привлекает, а они, в свою очередь, проводят в сети множество транзакций, запусков dApps и прочих процессов. Результат — скорость транзакций падает, комиссии растут, и все это становится препятствием для расширения и развития проекта в будущем. Деление сети на шарды (их еще называют осколками) позволяет увеличить пропускную способность блокчейна и таким образом решить эту проблему.
Когда группа узлов отвечает за определенный сегмент реестра, то каждой ноде в сети уже не нужно поддерживать весь блокчейн для выполнения каждой транзакции. Валидация операций проводится параллельно, а не линейно, а это повышает пропускную способность сети. Как следствие, исчезает проблема масштабирования.
Кроме того, в блокчейнах, которые используют шардинг, благодаря снижению нагрузки, ноды работают эффективнее без увеличения вычислительной мощности.
Как все это работает
Блокчейны состоят из тысяч компьютеров, благодаря вычислительным мощностям которых распределенные реестры и функционируют: проводят транзакции, выполняют смарт-контракты, разворачивают dApps и т. д.
Если сеть функционирует на основе последовательного выполнения, то каждый узел должен обрабатывать каждую транзакцию. Поэтому верификация операций занимает достаточно много времени. К примеру, Ethereum обрабатывает около 10 транзакций/сек.
Если к блокчейну добавить узлы, это вовсе не означает, что его производительность вырастет. Просто цепочка верификации станет длиннее.
Суть шардинга заключается в том, чтобы отказаться от линейной модели выполнения в пользу параллельной, в которой осколки делают только определенные вычисления и одновременно обрабатывают большое количество транзакций. Такие себе блокчейны в блокчейне, вся информация из которых переносится в основную цепочку блоков, но уже в сокращенном виде.
Минусы шардинга
Главных проблем шардинга две: коммуникация между осколками и безопасность.
- Коммуникация. Если поделить сеть на изолированные сектора, то каждый из них, по сути, станет отдельным блокчейном. Пользователи и децентрализованные приложения одного шарда смогут общаться с пользователями и приложениями другого только используя особый коммуникационный протокол.
- Безопасность. В сети, разделенной на сегменты, гораздо легче взять под полный контроль один шард, так как для этого нужно гораздо меньше хешрейта, чем для захвата нераспределенной сети. К примеру, для того чтобы совершить атаку 51 % на сеть Bitcoin, потребуются миллиарды долларов на оборудование и сотни миллионов долларов в день на электроэнергию. Дорого, невыгодно, неосуществимо. А вот захватить один шард гораздо дешевле и проще.
Ethereum предлагает решение этой проблемы с помощью случайной выборки — протоколы осколка рандомно назначаются в разные секции для подтверждения аутентификации блоков (для этого и нужен был переход на PoS).
Какие сети уже используют шардинг
Первая сеть, внедрившая шардинг, — Zilliqa. Она позиционирует себя как блокчейн-платформа, целью которой является применение сегментирования для решения проблем масштабируемости. На стадии тестнета она смогла добиться показателя в 2828 транзакций/сек.
Экосистема блокчейна Near называет себя «шардированным блокчейном на PoS» и утверждает, что их технология шардинга дает возможность узлам оставаться достаточно небольшими и использовать для работы устройство с невысокой производительностью.
Ethereum также собирается внедрять технологию шардинга. Среди других шардинговых сетей — Cardano, QuarkChain, PChain.
Итого
В теории технология шардинга способна наконец-то решить трилемму блокчейна — если преодолеет сложности, с которыми сталкивается. В таком случае можно будет масштабировать блокчейны, не жертвуя децентрализацией или безопасностью.
Что такое шардинг?
Шардинг — метод разделения и хранения единого логического набора данных в виде множества баз данных. Другое определение шардинга — горизонтальное разделение данных.
Когда и кто изобрел шардинг?
Концепция шардинга применялась в управлении традиционными централизованными базами данных с конца 1990-х годов. Термин «шард» (фрагмент) получил распространение благодаря одной из первых многопользовательских ролевых онлайн-игр, Ultima Online, в которой разработчики распределили игроков по различным серверам (разным «мирам» в игре), чтобы справиться с трафиком.
Популярный сценарий применения шардинга в бизнесе — разделение базы данных пользователей по географическим локациям. Пользователи, относящиеся к одной географической локации, объединяются в одну группу и размещаются на уникальном сервере.
Что такое шардинг в контексте блокчейна?
Блокчейн — это база данных с нодами, представляющими индивидуальные серверы. Применительно к блокчейну, шардинг подразумевает разделение сети блокчейна на индивидуальные сегменты (шарды). Каждый шард содержит уникальный набор смарт-контрактов и балансов счетов.
За каждым шардом закрепляется нода, верифицирующая транзакции и операции, в отличие от схемы, в которой каждая нода отвечает за верификацию каждой транзакции во всей сети.
Разделение блокчейна на более управляемые сегменты позволяет увеличить пропускную способность транзакций и тем самым решить проблему масштабируемости, с которой сталкивается большинство современных блокчейнов.
Как работает шардинг?
Объяснение на примере Ethereum:
Блокчейн Ethereum состоит из тысяч компьютеров или нод, каждая из которых «одалживает» сети определенный объем хешрейта. Именно этот хешрейт позволяет Ethereum Virtual Machine (EVM) функционировать — выполнять смарт-контракты и управлять децентрализованными приложениями (DApps).
В настоящее время Ethereum работает на основе последовательного выполнения, в котором каждая из нод должна рассчитывать каждую операцию и обрабатывать каждую транзакцию. Поэтому прохождение транзакцией верификационного процесса требует значительного времени: Ethereum осуществляет приблизительно 10 транзакций в секунду, тогда как у Visa, например, этот показатель в районе 24 000.
Добавление к сети компьютеров не обязательно повышает эффективность, поскольку весь реестр хранится на каждом устройстве, и цепь верификации просто становится длиннее.
Идея шардинга состоит в том, чтобы отказаться от модели, в которой каждая нода должна вычислять каждую операцию, в пользу модели параллельного выполнения, в которой ноды обрабатывают только определенные вычисления. Это позволяет параллельно обрабатывать множество транзакций.
Блокчейн разделяется на отдельные шарды (поддомены или сегменты). Ноды управляют только той частью реестра, к которой они прикреплены (выполняют процессы и подтверждают транзакции), а не поддерживают весь реестр.
Какие проблемы решает шардинг?
Шардинг — потенциальное решение проблемы масштабирования.
Чем популярнее становится блокчейн, тем больше пользователей инициируют транзакции, запуск децентрализованных приложений и другие процессы в сети. В результате, скорость транзакций падает, что препятствует расширению блокчейна в долгосрочной перспективе. Рост транзакционной активности требует от нод интенсифицировать процесс верификации транзакций. Существует угроза того, что эти блокчейны могут «закупориться», как это произошло с Ethereum в период бума CryptoKitties, когда на долю игры приходилось 11% транзакций сети.
Если группы нод отвечают за индивидуальные сегменты, то каждой ноде не нужно поддерживать весь реестр для выполнения каждой операции. Поэтому валидация транзакций может осуществляться параллельным, а не линейным образом, что повышает скорость сети. Таким образом решается проблема масштабирования.
Каковы недостатки шардинга?
Основные проблемы шардинга — коммуникация и безопасность. Если разделить блокчейн на изолированные сегменты, то каждый шард станет отдельной сетью. Пользователи и приложения одного поддомена не смогут коммуницировать с пользователями и приложениями другого поддомена, не применяя особый механизм коммуникации.
В сегментированном блокчейне также возникает проблема безопасности, поскольку хакерам легче захватить один шард — по причине меньшего хешрейта, требуемого для контроля индивидуальных сегментов (так называемая атака 1%).
После захвата сегмента атакующие могут направить недействительные транзакции в основную сеть. Также данные в этом конкретном сегменте могут стать недействительными и оказаться безвозвратно утрачены. Ethereum предлагает решение в виде рандомизированной выборки — протоколы шарда случайным образом назначаются в различные секции для подтверждения аутентификации блоков.
Каковы альтернативы шардингу?
Разработчики предложили два решения, позволяющих повысить производительность и скорость транзакций в блокчейнах.
Первое решение — увеличение размера блока. Ключевая идея — чем больше размер блока, тем больше транзакций можно поместить в него и, следовательно, тем больше число транзакций в секунду.
Однако чем больше блок, тем больше вычислительной мощности необходимо для его верификации. Если размер блока увеличить значительно, то лишь наиболее мощные компьютеры смогут управлять вычислительной мощностью, необходимой для деятельности в качестве нод.
Высокая стоимость такого компьютерного оборудования означает, что пулы нод неизбежно станут меньшего размера и более централизованными, что повышает риск атаки 51%. Увеличение размера блока также требует хардфорка, который грозит расколом сообщества: если не все пользователи примут обновление, то возникнут две разные цепи, использующие разные монеты. Увеличение размера блока не может быть долгосрочным решением.
Второе предложение — использовать альткоины с тем, чтобы различные функции и приложения были реализованы на собственных сетях с собственными монетами.
Такая модель повысит производительность, поскольку единый блокчейн не будет перегружен, но также увеличит риски безопасности, поскольку вычислительная мощность будет распределена по нескольким блокчейнам. Опять же, риск взлома сети возрастет и потому, что вычислительная мощность, необходимая для осуществления атаки 51%, будет гораздо меньше.
Кто использует шардинг?
Zilliqa — первая платформа, внедрившая шардинг. На стадии тестнета она сумела достичь показателя в 2828 транзакций в секунду.
Экосистема блокчейна Near позволяет разработчикам создавать и применять децентрализованные приложения. Near называет себя «шардированным блокчейном на PoS» и утверждает, что его технология шардинга позволяет нодам оставаться достаточно небольшими для того, чтобы функционировать на устройствах невысокой производительности — потенциально даже на мобильных телефонах.
Ethereum предлагает экосистему блокчейна для внедрения DApps на основе смарт-контрактов. Ethereum Foundation планирует включить шардинг в обновленную версию протокола Ethereum 2.0.
Среди прочих работающих с шардингом проектов: Cardano, QuarkChain и PChain.
Каково будущее шардинга?
Технология шардинга фигурирует в white paper цифровой валюты Libra. В преддверии запуска компания Facebook приобрела компанию Chainspace, чья команда разработчиков специализируется на шардинге. Конкретные детали пока неизвестны, но можно предположить, что в блокчейн Libra внедрят разновидность шардинга.
Шардинг теоретически может стать решением так называемой трилеммы блокчейна.
Трилемма блокчейна, как пояснил Виталик Бутерин, состоит в том, что одновременно можно сохранять только две из трех ключевых особенностей блокчейна — безопасности, децентрализации и масштабируемости. Если преодолеть сложности, с которыми сталкивается шардинг, то можно будет масштабировать распределенные сети, не жертвуя децентрализацией или безопасностью.
Подписывайтесь на новости Forklog в Facebook!
Подписывайтесь на ForkLog в социальных сетях
Нашли ошибку в тексте? Выделите ее и нажмите CTRL+ENTER
Рассылки ForkLog: держите руку на пульсе биткоин-индустрии!
Шардинг, от которого невозможно отказаться
В принципе, для большинства проектов вcё оправдано. Это может быть еще прототип или круг пользователей ограничен… Да и не факт, что проект вообще выстрелит.
Откладывать можно сколько угодно, но если проект не просто жив, а еще и растет, то до шардинга он доберется. Одна беда, обычно, бизнес логика не готова к таким «внезапным» вызовам.
А вы закладывали возможность шардинга при проектировании коллекций?
Эта статья для продвинутых разработчиков.
Для тех, кто планирует шардинг своего кластера.
Для тех, кто уже шардировал кластер ранее, но админы все еще плачут.
Для тех, кто руками перемещал jumbo-чанки.
Сначала, мы будем учится жить со слонами.
Потом, мы их победим, но не сможем вернуться назад.
Всем привет, от команды разработки Smartcat и наших счастливых админов!
Мы разберём распространенный сценарий, который обычно мешает нам равномерно распределять данные. И опишем способ ослабления требований к ключу шардирования, который при этом не приведет к деградации производительности нашего кластера.
Зачем нам шардинг
Шардинг — штатная возможность горизонтального масштабирования в MongoDB. Но, чтобы стоимость нашего шардинга была линейной, нам надо чтобы балансировщик MongoDB мог:
- выровнять размер занимаемых данных на шард. Это сумма размера индекса и сжатых данных на диске.
- выровнять нагрузку по CPU. Его расходуют: поиск по индексу, чтение, запись и агрегации.
- выровнять размер по update-трафику. Его объем определяет скорость ротации oplog. Это время за которое мы можем: поднять упавший сервер, подключить новую реплику, снять дамп данных.
Конечно, мало толку будет от нового шарда, если на него переместится только пара процентов данных или не дойдет нагрузка на CPU или update-трафик.
Особенности шардинга в MongoDB
Как мы знаем балансировщик MongoDB при отсутствии ограничений (превышение размера данных на шард, нарушение границ зон) выравнивает только количество чанков на шард. Это простой, надежный и сходящийся алгоритм.
Есть документация про лимиты количества чанков и про порядок выбора размещения чанка.
Этот алгоритм и определяет главные ограничения на балансируемые данные:
- Ключ шардирования должен быть высокоселективный. В противном случае мы не получим достаточного числа интервалов данных для балансировки.
- Данные должны поступать с равномерным распределением на весь интервал значений ключа. Тривиальный пример неудачного ключа — это возрастающий int или ObjectId. Все операции по вставке данных будут маршрутизироваться на последний шард (maxKey в качестве верхней границы).
Самое значимое ограничение — гранулярность ключа шардирования.
Или, если сформулировать отталкиваясь от данных, на одно значение ключа должно приходиться мало данных. Где «мало» — это предельный размер чанка (от 1Mb до 1Gb) и количество документов не превышает вот эту вот величину.
К сожалению, в реальной жизни, не всегда удается подогнать модель данных под эти требования.
Бизнес логика требует слонов
Теперь давайте посмотрим, с чем мы будем сталкиваться при проектировании бизнес логики.
Я рассмотрю только самый чувствительный для шардирования случай, который заставляет наши данные группироваться в неравномерные объемы.
Рассмотрим следующий сценарий:
- мы шардируем коллекцию работ
- работа принадлежит только одному проекту
- идентификатор проекта равномерно распределен (а если это не так, то помогает хешированный индекс)
- большая часть поисковых запросов знает только идентификатор проекта
- число работ на проект не лимитировано. Могут быть проекты с одной работой, а могут быть и гиганты с миллионом работ.
Как может выглядеть логика выбора ключа шардирования?
Совершенно точно, в качестве первого поля стоит взять projectId . Он есть в большинстве запросов, т.е. бОльшая часть запросов будет направлена роутером на нужный шард.
Теперь, надо выбрать второе поле ключа или оставить только первое.
Например, у нас 20% запросов используют только поле name , еще 20% только поле creation , а остальные опираются на другие поля.
Если в ключ шардирования включить второе поле, то крупные проекты, те у которых объем работ не помещается в одном чанке, будут разделены на несколько чанков. В процессе разделения, высока вероятность, что новый чанк будет отправлен на другой шард и для сбора результатов запроса нам придётся обращаться к нескольким серверам. Если мы выберем name, то до 80% запросов будут выполняться на нескольких шардах, тоже самое с полем creation. Если до шардирования запрос выполнялся на одном сервере, а после шардирования на нескольких, то нам придется дополнительную читающую нагрузку компенсировать дополнительными репликами, что увеличивает стоимость масштабирования.
С другой стороны, если оставить в ключе только первое поле, то у нас «идеальное» линейное разделение нагрузки. Т.е. каждый запрос с полем projectId будет сразу отправлен на единственный шард. Поэтому имеет смысл остановиться на этом выборе.
Какие риски есть у этого выбора:
- у нас могут появляться не перемещаемые jumbo-чанки. При большом объеме работ в одном проекте мы обязательно превысим предельный размер чанка. Эти чанки не будут разделены балансировщиком.
- у нас будут появляться пустые чанки. При увеличении проекта, балансировщик будет уменьшать диапазон данных чанка для этого проекта. В пределе, там останется только один идентификатор. Когда большой проект будет удален, то на этот узкий чанк данные больше не попадут.
Оба этих случая несколько искажают KPI штатного балансировщика. В первом случае чанк вообще не перемещается. Во втором, он перемещается, но это не приводит к смещению данных. С течением времени, мы можем получить ситуацию когда число пустых чанков превышает число чанков с данными, а еще часть чанков не перемещаемые.
В общем, мы можем посмотреть текущее распределение данных или просто прикинуть развитие проекта и допустить, что хоть jumbo-чанки и будут появляться, но их должно быть не много.
Проблемы с масштабированием
Итак, мы, вопреки ожиданиям, набрали достаточно неперемещаемых чанков, чтобы это стало заметно. То есть буквально, случай из практики. Вы заказываете админам новый шард за X$ в месяц. По логам видим равномерное распределение чанков, но занимаемое место на диске не превышает половины. С одной стороны весьма странное расходование средств было бы, а с другой стороны возникает вопрос: мы что же, не можем прогнозировать совершенно рутинную операцию по добавлению шарда? Нам совсем не нужно участие разработчика или DBA в этот момент.
Что интересно, на этапе проектирования схемы данных, у нас и нет особого выбора. Мы не можем предсказать характер использования на годы вперед. А владельцы продукта очень не охотно формулируют строгие ограничения при проектировании. Даже оглядываясь назад, не всегда понятно, как можно было бы перепроектироваться, чтобы успеть все сделать тогда. Так что продолжаем работать с тем шардированием, которое заложили.
Ну, самое топорное решение — просто назначить зоны размещения коллекции на новые сервера, чтобы на старых остались только неперемещаемые чанки. Конечно, о предсказуемости занимаемого места речи не идет.
Еще можно увеличить максимальный размер чанка, но это глобальный параметр для всего кластера.
Можно попытаться перемещать чанки в ручную, но это может привести к блокировке данных на длительное время и все равно нет гарантии переноса.
Надеюсь, тут все уже запуганы и потеряли надежду. 😉
Решение
Попробуем сначала поправить основную боль администрирования.
Конечно, не хочется заново писать балансировку данных, нам надо просто «попросить» существующий балансировщик перекинуть часть чанков туда, где есть неиспользуемое место. Вариантов у нас два.
1-й воспользоваться командой moveChunk — прямое указание балансировщику о перемещении конкретного чанка.
2-й воспользоваться командой addTagRange — привязка диапазона значений ключа шардирования к некоторому шарду и их группе.
В обоих случаях потребуется также точное знание размера чанка. Это можно сделать командой dataSize, которая возвращает некоторую статистику по объектам в коллекции на заданном интервале ключей.
Предварительное прототипирование 1-го варианта выявило дополнительные особенности.
Команды moveChunk не отменяют работы штатного балансировщика. Он вполне может принять решение об обратном переносе. Тут надо блокировать работу штатного балансировщика. Или кроме перемещения больших чанков на недогруженный шард, надо искать на нем маленькие чанки и синхронно перемещать их на перегруженный шард.
Данные dataSize вычисляются «долго», и при этом устаревают. На практике получается, что надо просканировать некоторое количество чанков, прежде чем найдешь чанк больше среднего. В идеале, надо искать самые большие перемещаемые чанки, т.е. некоторые варианты распределения данных в коллекции могут нам значительно замедлить сканирование.
Массовые сканирования dataSize ухудшают отзывчивость сервера на боевых запросах.
Ну и остается тупиковая ситуация, когда все чанки кроме jumbo просто равны по размеру. В этом случае перемещать просто нечего, или опять же придется блокировать работу штатного балансировщика.
По совокупности проблем с прямой балансировкой, я решил развивать балансировку через привязку диапазона значений ключа шардирования.
Основная идея проста.
Давайте подберем такие диапазоны ключей на каждам шарде, чтобы в итоге части коллекции занимали одинаковый размер на шардах.
Диапазоны ключей привязываются к тегу, а не к шарду. А сам шард может содержать несколько тегов. Поэтому для упрощения дальнейшего описания и работы условимся, что на каждый шард мы добавляем по одному тегу с названием этого шарда. Это позволит привязывать диапазон ключей на конкретный шард.
Процесс выравнивания разделим на 2 основных этапа:
Первый этап — это предварительное разбиение. Оно по сути дефрагментирует данные на непрерывные отрезки на каждый шард.
Второй этап — коррекция границ ключей. Этот этап можно повторять периодически, ориентируясь по фактическому занимаемому месту на диске.
Группировка данных
Данные в шардированной коллекции разбиваются на чанки — интервалы значений ключа шардирования. При штатной работе, балансировщик распределяет чанки по шардам по мере разделения данных без учета их соседства.
Нам же, наоборот, нужно сгруппировать чанки по шардам так, чтобы на одном шарде все чанки принадлежали одному интервалу ключа шардирования. Это позволит разделять такие интервалы минимальным числом границ.
Итак, коллекция уже шардирована.
- Читаем все ее чанки из коллекции config.chunks с сортировкой по возрастанию ключа
- Распределяем чанки по шардам, так чтобы их было примерно одинаковое количество. Но при этом все чанки на одном шарде должны объединяться в один интервал.
У нас есть три шарда — sh0 , sh1 , sh2 с одноименными тегами.
Мы вычитали поток из 100 чанков по возрастанию в массив
var chunks = db.chunks.find(< ns: "demo.coll">).sort(< min: 1>).toArray();
Первые 34 чанка будем размещать на sh0
Следующие 33 чанка разместим на sh1
Последние 33 чанка разместим на sh2
У каждого чанка есть поля min и max. По этим полям мы выставим границы.
sh.addTagRange( "demo.coll", , , "sh0"); sh.addTagRange( "demo.coll", , , "sh1"); sh.addTagRange( "demo.coll", , , "sh2");
Обратите внимание, что поле max совпадает с полем min следующего чанка. А граничные значения, т.е. chunks[0].min и chunks[99].max , всегда будут равны MinKey и MaxKey соответственно.
Т.е. мы покрываем этими зонами все значения ключа шардирования.
Балансировщик начнёт перемещать чанки в указанные диапазоны.
А мы просто ждем окончания работы балансировщика. Т.е. когда все чанки займут свое место назначения. Ну за исключением jumbo-чанков конечно.
Коррекция размера
Продолжим пример выше и возьмем конкретные значения ключей. Первоначальная настройка будет выглядеть так:
sh.addTagRange( "demo.coll", , , "sh0"); sh.addTagRange( "demo.coll", , , "sh1"); sh.addTagRange( "demo.coll", , , "sh2");
Вот так будет выглядеть размещение чанков:
Командой db.demo.coll.stats() можно получить объем данных, которые хранятся на каждом шарде. По всем шардам можно вычислить среднее значение, к которому мы хотели бы привести каждый шард.
Если шард надо увеличить, то его границы надо перемещать наружу, если уменьшить, то внутрь.
Так как уже явно заданы диапазоны ключей, то балансировщик не будет их перемещать с шарда на шард. Следовательно, мы можем пользоваться командой dataSize , мы точно знаем данные какого шарда мы сканируем.
Например, нам надо увеличить sh0 за счет h1 . Границу с ключем будем двигать в бОльшую сторону. Сколько именно данных мы сместим перемещением конкретного 34-го чанка, мы можем узнать командой dataSize с границами этого чанка.
db.runCommand(< dataSize: "demo.coll", keyPattern: < shField: 1 >, min: < shField: 1025 >, max: < shField: 1508 >>)
Последовательно сканируя чанки по одному мы мы можем смещать границу до нужного нам размера sh0 .
Вот так будет выглядеть смещение границы на один чанк
Вот пример команд для фиксации нового состояния границ в конфигурации кластера.
Мы сначала удаляем пару интервалов со старыми значениями границ, потом заводим новые интервалы с актуальными значениями границ.
sh.removeTagRange( "demo.coll", , , "sh0"); sh.removeTagRange( "demo.coll", , , "sh1"); sh.addTagRange( "demo.coll", , , "sh0"); sh.addTagRange( "demo.coll", , , "sh1");
После применения команд балансировщик самостоятельно переместит 34-й чанк на нужный нам шард. Конечно, нет необходимости менять границы после каждого сканирования чанка. Мы можем учесть полученный эффект, продолжить сканирование и сформировать уже итоговый пакет изменений границ. Таким образом, мы можем выровнять размер коллекции по шардам с точностью до максимального размера чанка.
Конечно, в процессе сканирования нам будут попадаться jumbo-чанки. Но ничего страшного, мы их просто игнорируем и переносим границу через него без учета эффекта от перемещения.
В общем случае, мы можем планировать произвольное распределений данных таких коллекций на шардах кластера.
Выгодные особенности этого подхода:
- до смещения границ, данные не перемещаются. Мы можем их сканировать без опасения, что эта информация устареет. Ну конечно, исключая работу приложения для которого и заведена эта БД.
- команда dataSize применяется только к перемещаемым данным. Мы не сканируем лишнего.
- путь к целевому состоянию непрерывный. Т.е. мы можем проводить частичные коррекции, например если видим, что сканирование идет долго и реальное распределение данных успевает сильно измениться.
Дополнительные возможности
Вообще, на практике требуется выравнивание используемого объема диска на шардах, а не только части шардированных коллекции. Частенько, нет времени или возможности проектировать шардирование вообще всех БД и коллекций. Эти данных лежат на своих primary-shard. Если их объем мал, то его легко учесть при коррекции размера и просто часть данных оттащить на другие шарды.
С течением времени, особенно после частых удалений, будут образовываться пустые чанки. Не то чтобы они теперь сильно мешают, но их может быть сильно больше чанков с данными. Чтобы они не заставляли страдать роутер, было бы хорошо их прибрать. Раньше (до дефрагментации) их надо было найти, переместить к другому чанку-соседу на один шард, потом запустить команду mergeChunks. Перемещение нужно, т.к. команда объединяет только соседние чанки на одном шарде.
Есть даже скрипты для автоматизации, но они долго работают, и есть один тикет с печально низким приоритетом.
Теперь все значительно проще. Соседние чанки и так на одном шарде. Можно объединять хоть весь интервал целиком. Если он конечно без jumbo-чанков, их надо исключить. Есть на интервале пустые чанки или нет, это уже не важно. Балансировщик заново сделает разбиение по мере добавления или изменения данных.
Почти итог
Итак, проблема решена.
Хорошо, что не пришлось заменять или сильно вмешиваться в работу штатного балансировщика. Он продолжает рулить другими коллекциями или диапазонами данных. А в наших настроенных диапазонах он выполняет разделение чанков и таскает чанки между серверами как мы попросим.
- точная балансировка занимаемого размера
- лучшее распределение update-трафика
- минимальное сканирование при коррекциях
- бесплатная уборка пустых чанков
- минимальные допуски на запасное/неиспользуемое дисковое пространство на каждом шарде
- мы всегда можем удалить привязки диапазонов ключей и вернуться к исходному состоянию
- требуется настройка и сопровождение привязок диапазонов ключей.
- усложняется процесс добавления нового шарда.
Но это было бы слишком скучно… Время победить слонов и не вернуться!
Победа над слонами!
Как мы уже отмечали, единственная причина неперемещаемых чанков — недостаточная селективность ключа шардирования. Мы сами снижали селективность ключа для привязки запроса к шарду. Но мы уже сгруппировали данные, а теперь мы можем этим коварно воспользоваться!
Можно увеличить селективность ключа шардирования, если добавить к нему второе поле с достаточной селективностью. Поле может быть любым. Подойдет даже _id .
Вспомним пример модели:
Ранее мы выбрали ключ шардирования
Но теперь при проектировании можно выбрать любые уточняющие поля для ключа шардирования:
А если вы пользуетесь MongoDB версии 4.4 и выше, то там есть удобная функция — уточнение ключа шардирования на существующих данных.
Данные дефрагментированы, а это дает нам гарантии того, что основной объем документов работ с одинаковым projectId будет находиться на одном шарде. Как это выглядит на практике?
Вот иллюстрация примеров размещения работ по чанкам. В случае, если мы выбрали ключ шардирования
Здесь, для упрощения примера, идентификаторы представлены целыми числами.
А термином «проект» я буду называть группу работ с одинаковым projectId .
Некоторые проекты будут полностью умещаться в один чанк. Например, проекты 1 и 2 размещены в 1м чанке, а 7-й проект во 2-м.
Некоторые проекты будут размещены в нескольких чанках, но это будут чанки с соседними границами. Например, проект 10 размещен в 3, 4 и 5 чанках, а проект 18 в 6 и 7 чанках.
Если мы будем искать работу по ее полю projectId , но без _id , то как будет выглядеть роутинг запросов?
Планировщик запросов MongoDB отлично справляется с исключением из плана запроса тех шардов, на которых точно нет нужных данных.
Например, поиск по условию будет только на шарде sh0
А если проект разбит границей шарда? Вот как 18-й проект например. Его 6-й чанк находится на шарде sh0 , а 7-й чанк находится на шарде sh1 .
В этом случае поиск по условию будет только на 2х шардах sh0 и sh1 . Если известно, что размер проектов у нас меньше размера шарда, то поиск будет ограничен только этими 2-мя шардами.
Т.е. мы берем условия исходной задачи, и практически полностью убираем все недостатки решения с составным ключом шардирования.
Теперь переформулируем для более общего случая.
У нас есть составной ключ шардирования. По первому полю нам требуется группировка, но оно слабоселективное. Второе поле, нужно просто чтобы увеличить общую селективность ключа.
С помощью дефрагментации, мы добиваемся следующих свойств:
- группа располагается на одном, максимум двух шардах.
- число групп которые имели несчастье разместиться на 2х шардах ограничено числом границ. Для N шардов будет N-1 граница.
Больше одно шардовых запросов, меньше лишней нагрузки на чтение, меньше дополнительных реплик!
Мало нам было бы радости, но с таких сильно селективным ключом мы и от jumbo-чанков избавляемся.
И вот теперь, от дефрагментации данных нам уже никуда не деться.
Точно итог
Изначально мы хотели просто выровнять коллекций по шардам.
По пути избавились от jumbo-чанков.
В итоге, мы можем поднять долю одно шардовых запросов, при этом не добавляя в условия запросов дополнительных полей.
Теперь уже можно оценить достижения и потери.
Достижения:
- гарантированный способ выравнивания занимаемого места для данных, которые мы хоть как нибудь смогли расшардить.
- простая компенсация размера не шардированных коллекций. Сложно представить себе проект, в котором найдется время проектировать каждую коллекцию с учетом шардирования.
- больше никаких jumbo-чанков.
- больше никаких пустых чанков. Соседние чанки на одном шарде — это слияние всего в одну в одну команду всего диапазона.
- если за счет дефрагментации мы убирали jumbo-чанки, то мы теперь с ней навсегда. Вернуть ключ мы не можем. Удаление границ приведет к фрагментации данных и снижению доли одно шардовых запросов.
- периодически надо проводить коррекции границ. Но кажется это мы сможем автоматизировать.
- при добавлении нового шарда надо явно планировать переразбиение.
Осталось спроектировать весь процесс дефрагментации, расчета поправок и коррекции границ… Ждите!