Решардинг и rebalancing бакетов

Рейтинг: 64.6% · 7 голосов
Исчерпывающий курс по Tarantool 3.x: модель данных, движки memtx и vinyl, Lua и файберы, транзакции и MVCC, SQL, конфигурация (box.cfg и декларативная 3.x), репликация и Raft, шардирование vshard, эксплуатация, безопасность. 47 уроков со схемами.
Ответить
Аватара пользователя
denis_tnt
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Решардинг и rebalancing бакетов

Сообщение denis_tnt »

Оглавление курса (47)
  1. Что такое Tarantool: in-memory СУБД и сервер приложений
  2. Архитектура изнутри: процесс, потоки, event-loop
  3. Установка и первый запуск: tt CLI, пакеты, Docker
  4. Интерактив: консоль, admin-консоль, первые команды
  5. Спейсы и кортежи: форматы, типы данных
  6. Типы индексов и их применимость
  7. Движки хранения: memtx vs vinyl
  8. DDL: схема, создание спейсов и индексов, миграции
  9. DML и выборки: insert/update/upsert, итераторы
  10. Персистентность: WAL, снапшоты, recovery
  11. Внутренности memtx: аллокаторы slab/arena, память
  12. Внутренности vinyl: LSM, компакция, тюнинг
  13. Lua и LuaJIT в Tarantool: box, модули, rocks
  14. Файберы: кооперативная многозадачность, каналы
  15. Транзакции: ACID, изоляция, MVCC
  16. Хранимые процедуры, модули, организация приложения
  17. net.box: удалённые вызовы, async
  18. Пулы соединений, балансировка, реконнект
  19. Ошибки и диагностика: box.error, pcall
  20. Типы и сериализация: MsgPack, decimal, datetime, uuid
  21. SQL в Tarantool: возможности и связь с box
  22. SQL: таблицы, JOIN, подзапросы, представления
  23. SQL: подготовленные выражения, транзакции, Lua-интероп
  24. Классическая конфигурация box.cfg (legacy 1.x/2.x)
  25. Декларативная конфигурация 3.x: config.yaml, иерархия
  26. Роли и приложения в 3.x
  27. Централизованная конфигурация: etcd / config storage
  28. tt CLI глубоко: разработка, сборка, запуск
  29. Cartridge (официальный legacy) и миграция на 3.x
  30. Репликация: replicaset, топологии
  31. Механика репликации: WAL-стриминг, vclock
  32. Синхронная репликация и выборы лидера (Raft)
  33. Жизненный цикл узла: bootstrap, join, rejoin
  34. vshard: router/storage, виртуальные бакеты
  35. Решардинг и rebalancing бакетов (вы здесь)
  36. Запросы поверх шардов: map-reduce, crud
  37. Мониторинг: метрики, Prometheus, Grafana
  38. Логирование и аудит
  39. Бэкапы и восстановление
  40. Безопасность: аутентификация, RBAC, TLS
  41. Производительность: профилирование, тюнинг
  42. Обновления: схема, rolling upgrade
  43. Деплой в продакшен: Docker, топология (официальные паттерны)
  44. Администрирование через официальный TCM (Tarantool Cluster Manager)
  45. Коннекторы: Python, Go, Java
  46. Ключевые модули (rocks): crud, metrics, queue, expirationd
  47. Capstone: шардированный отказоустойчивый кластер
Зачем нужен rebalancing

В прошлых уроках мы разобрали, что данные в vshard физически живут не в шардах напрямую, а в виртуальных бакетах (virtual buckets). Весь датасет нарезан на фиксированное число бакетов (sharding.bucket_count, обычно 100N-1000N от числа узлов), каждый бакет целиком лежит в одном replica set, а каждый тапл хранит свой bucket_id в индексированном поле. Решардинг - это процесс, при котором мы меняем состав кластера (добавили или убрали shard, поменяли его вес), а rebalancer фоном переносит бакеты так, чтобы их распределение снова стало ровным. Важно: количество бакетов не меняется при решардинге - меняется только их раскладка по шардам. Именно неизменность bucket_count и крупная гранулярность бакетов делают миграцию дешёвой и прозрачной для приложения.

Как rebalancer считает, кому сколько

У каждого replica set есть эталонное (etalon, идеальное) число бакетов. Оно вычисляется автоматически из общего bucket_count и весов шардов (sharding.weight). Вес - это относительная ёмкость: при весах 1, 0.5, 1.5 и 3000 бакетах эталоны будут 1000, 500 и 1500. Чем больше вес, тем больше бакетов осядет на шарде.

Rebalancer периодически просыпается и для каждого replica set считает disbalance (рассогласование) по формуле:

Код: Выделить всё

|etalon_bucket_number - real_bucket_number| / etalon_bucket_number * 100
Если хоть на одном шарде это значение превышает порог sharding.rebalancer_disbalance_threshold, запускается миграция. Rebalancer строит маршруты (routes): кто, сколько и кому отправляет бакеты, чтобы дотянуть каждый шард до эталона. Сам rebalancer - это один фибер на одном мастер-storage среди всех replica set (роль можно задать явно через sharding.roles или оставить авто-выбор).

Решардинг инициируется конфигурацией, а не командой:
  • Добавить shard: новый replica set с ненулевым весом - rebalancer увидит у него 0 бакетов при ненулевом эталоне и начнёт стягивать бакеты со всех остальных.
  • Вывести shard: выставить ему sharding.weight: 0 - эталон станет 0, и все его бакеты разъедутся по остальным. После опустошения узел можно гасить.
Главное про перезагрузку конфига: при локальном файле сначала перезагрузите конфигурацию на ВСЕХ роутерах, потом на ВСЕХ storage. При централизованном хранилище (etcd) Tarantool подхватывает изменения сам. Перепутаете порядок - роутер может слать запросы туда, где бакета ещё/уже нет.
Механика миграции одного бакета

Бакет переезжает не атомарно-мгновенно, а через цепочку состояний, которые хранятся в системном спейсе _bucket на каждом storage (поля: bucket id, status, destination - UUID шарда-получателя). Source - это шард-источник, destination - шард-приёмник.
  • ACTIVE - бакет на месте, обслуживает чтения и записи.
  • PINNED - как ACTIVE, но запрещён к переезду (закреплён вручную через bucket_pin).
  • SENDING - на source данные копируются на destination; чтения ещё обслуживаются, записи - нет.
  • RECEIVING - на destination бакет наполняется; любые запросы к нему отклоняются.
  • SENT - на source копирование завершено, бакет начинает отвечать ошибкой переезда; через ~0.5 c уходит в GARBAGE.
  • GARBAGE - данные уже не нужны (переехали либо приём оборвался ошибкой); их удаляет garbage collector по частям.
Сам перенос идёт так: на destination создаётся бакет в RECEIVING, начинается копирование; на source бакет помечается SENDING (отдаёт чтения); когда данные скопированы - source ставит SENT и перестаёт принимать запросы, а destination ставит ACTIVE и берёт всю нагрузку. Если в момент переезда прилетел запрос, неприменимый к мигрирующему бакету, возвращается ошибка vshard.error.code.TRANSFER_IS_IN_PROGRESS - её нужно ретраить.

Роутеру про переезд никто заранее не сообщает. Он узнаёт о новом месте бакета через ошибку wrong bucket / код перенаправления, а discovery-фибер на роутере фоном обновляет routing table (карту bucket_id -> replica set). Поэтому миграция прозрачна для приложения: вы продолжаете звать vshard.router.callrw/callro, а роутер сам перенаведётся.

Изображение
Состояния бакета при миграции: source и destination

Параллельный rebalancing и as-is команды

Изначально rebalancer применял маршруты строго последовательно - бакет за бакетом - и на Vinyl это было мучительно медленно (стоимость чтения с диска сравнима с сетью, воркер большую часть времени спал). Сейчас каждый узел шлёт несколько бакетов параллельно, round-robin по получателям. Два параметра управляют нагрузкой:

Код: Выделить всё

sharding:
  bucket_count: 3000
  rebalancer_disbalance_threshold: 1
  rebalancer_max_sending: 5      # сколько бакетов узел шлёт одновременно (default 1)
  rebalancer_max_receiving: 100  # сколько бакетов узел принимает одновременно
Помните: пока бакет в SENDING, он не принимает запись. rebalancer_max_sending напрямую ограничивает объём данных, заблокированных на запись по всему кластеру. Если у вас 100000 бакетов на 10 шардов и нельзя блокировать >0.1% данных - не ставьте max_sending выше 10 на узел (10 шардов * 10 = 100 бакетов = 0.1%).
Полезные функции для наблюдения за процессом (запускать на storage):

Код: Выделить всё

-- сколько бакетов сейчас на этом storage
vshard.storage.buckets_count()

-- идёт ли применение маршрутов rebalancer прямо сейчас
vshard.storage.rebalancing_is_in_progress()

-- общая инфа: bucket.active/sending/receiving/garbage и состояние alerts
vshard.storage.info()

-- детально по конкретному бакету: статус, ref_rw/ref_ro
vshard.storage.buckets_info(42)
Lock, pin и bucket ref

Три механизма влияют на то, что rebalancer вообще может двигать:
  • Replica set lock (sharding.lock: true) - шард невидим для rebalancer: не отдаёт и не принимает бакеты. Rebalancer пересчитывает эталоны остальных так, будто залоченного шарда нет.
  • Bucket pin (vshard.storage.bucket_pin) - конкретный бакет приклеен к шарду. Если запиненных бакетов больше эталона, идеальный баланс недостижим, и rebalancer вытаскивает столько, сколько может.
  • Bucket ref - НЕперсистентный счётчик ссылок на время выполнения запроса. RW-реф не даёт двигать бакет, RO-реф позволяет отправить, но не даёт удалить, пока жив последний читатель. callrw/callro делают ref/unref автоматически.
Частые заблуждения и грабли
  • "Решардинг меняет число бакетов." Нет. bucket_count фиксируется при создании кластера и не меняется онлайн. Двигаются только бакеты между шардами.
  • "Можно делать rebalance командой." Нет ручного "rebalance now": вы меняете веса/состав в конфиге, rebalancer сам решает. Хотите изоляции - lock/pin.
  • "Запиню весь шард = заблокирую его." Пин всех бакетов не равен lock. Незалоченный шард всё равно может ПРИНЯТЬ новые бакеты.
  • "Бакеты застряли в SENDING/RECEIVING." Частая причина - max_sending высокий, а max_receiving низкий: бакеты пытаются переехать и фейлятся, жгут сеть и время. Согласуйте параметры. После reboot застрявшие бакеты чинит bucket recovery fiber.
  • "Забыл bucket_unref в сыром API." При работе через router.route():callrw/callro ref/unref надо звать руками; забыли unref - бакет не уедет до рестарта инстанса.
  • "Перезагрузил конфиг как удобно." При локальном конфиге порядок строгий: сначала роутеры, потом storage.
Мини-лаба

На учебном кластере из 2 storage (storage-a, storage-b) с bucket_count: 3000 добавьте третий шард storage-c с weight: 1. Реализуйте через конфиг, затем понаблюдайте миграцию:
  • До reload снимите vshard.storage.buckets_count() на a и b (ожидаемо ~1500 + ~1500).
  • Перезагрузите конфиг (сначала роутеры, потом storage).
  • Раз в пару секунд вызывайте на каждом storage vshard.storage.rebalancing_is_in_progress() и vshard.storage.info() - поймайте ненулевые sending/receiving.
  • Дождитесь окончания и убедитесь, что стало ~1000 на каждом из трёх шардов.
Подсказка: эталон тут 3000/3 = 1000, порог disbalance изначально превышен на a и b, поэтому rebalancer стартует сам.

Контрольные вопросы
  1. По какой формуле rebalancer определяет, что шард рассогласован, и от чего зависит эталонное число бакетов?
  2. Перечислите состояния бакета при миграции по порядку: что на source, что на destination, и в какой момент бакет перестаёт принимать запись?
  3. Чем replica set lock отличается от пина всех бакетов шарда? Что сможет шард в каждом случае?
  4. Почему опасно ставить большой rebalancer_max_sending, и как с ним связана блокировка данных на запись?
👍2 ❤️2 🔥3 😄 🤔1
Ответить
← Предыдущая глава
vshard: router/storage, виртуальные бакеты
Следующая глава →
Запросы поверх шардов: map-reduce, crud

Все главы курса «Tarantool: in-memory СУБД и сервер приложений с нуля до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «Tarantool: СУБД и сервер приложений»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость