В прошлом уроке мы конфигурировали Tarantool "в коде" - через вызов box.cfg{} в Lua-скрипте. Это императивный подход: вы описываете, какие параметры применить к одному конкретному процессу. Начиная с версии 3.0, рекомендуемый способ другой - декларативный YAML-файл (обычно config.yaml), который описывает топологию ВСЕГО кластера сразу: какие есть группы, реплика-сеты, инстансы, и какие настройки к ним применяются. box.cfg-подход с 3.0 официально считается legacy, но никуда не делся: под капотом декларативная конфигурация в итоге всё равно превращается в вызовы box.cfg для каждого инстанса.
Ключевая идея урока: один config.yaml описывает много инстансов, а параметры в нём раскладываются по иерархии из четырёх уровней - global, group, replicaset, instance. Это позволяет писать общее один раз сверху и точечно переопределять снизу.
Механика и архитектура
Топология: три вложенных контейнера
YAML описывает кластер сверху вниз тремя вложенными секциями:
- groups - группа объединяет реплика-сеты. Типичный смысл - разделить роли: в шардированном кластере одна группа хранит данные (storages), другая держит роутеры.
- replicasets - реплика-сет это пачка инстансов, работающих над одними и теми же данными. Это единица репликации и отказоустойчивости.
- instances - один запущенный процесс Tarantool. Хранит данные или работает роутером.
Код: Выделить всё
groups:
group001:
replicasets:
replicaset001:
instances:
instance001:
# ...
instance002:
# ...
Самое важное: почти любой параметр можно задать на любом из четырёх уровней (скоупов). Уровень определяется тем, на какой глубине YAML вы написали ключ:
- global - параметр в корне файла, применяется ко всем инстансам кластера.
- group - параметр внутри конкретной группы, применяется ко всем её инстансам.
- replicaset - параметр внутри реплика-сета, применяется ко всем его инстансам.
- instance - параметр у конкретного инстанса.
Правило простое: общее пишем наверху (global/group), частное - внизу (instance). Tarantool сливает (merge) уровни, начиная с global, и каждый следующий, более узкий уровень переопределяет совпадающие ключи.

Иерархия скоупов config.yaml и приоритет
Где находятся настройки vs топология
Не путайте две вещи в файле. Имена групп/реплика-сетов/инстансов - это топология (кто есть кто). А ключи вроде iproto, replication, memtx, log, credentials - это настройки, которые можно положить на любом из четырёх уровней. Один и тот же ключ iproto.listen технически валиден и в корне (global), и внутри instance001.
Откуда вообще берётся конечное значение: precedence источников
Помимо иерархии уровней внутри файла, есть ещё приоритет источников. Итоговое значение опции собирается из нескольких мест, от высшего приоритета к низшему:
- переменные окружения TT_* (например TT_IPROTO_LISTEN) - перебивают файл;
- локальный YAML-файл;
- централизованное хранилище (etcd или Tarantool-based);
- переменные TT_*_DEFAULT - задают дефолты, если в файле опции нет.
После старта работающий инстанс знает свою конфигурацию через встроенный модуль config. Декларативная конфигурация - это модель "целевого состояния" (target state): вы описываете, КАК должно быть, а Tarantool сам вычисляет, что поменять. Это отличается от box.cfg{}, где вы императивно применяете изменения. Перечитать файл без рестарта - config:reload(). Динамические опции применятся на лету; не-динамические (например размер арены памяти, директории) будут пропущены с алертом и реально применятся только после рестарта.
Ключевые команды и код (оба трека)
Трек 1.x/2.x - box.cfg (legacy)
Один инстанс, всё императивно в init.lua:
Код: Выделить всё
box.cfg{
listen = '127.0.0.1:3301',
replication = {'127.0.0.1:3301', '127.0.0.1:3302'},
read_only = false,
memtx_memory = 256 * 1024 * 1024,
}
Трек 3.x - config.yaml (декларативно)
Тот же смысл, но как часть описанного кластера. Обратите внимание, как box.cfg-опции мигрируют в YAML-ключи:
Код: Выделить всё
credentials:
users:
replicator:
password: 'topsecret'
roles: [replication]
iproto:
advertise:
peer:
login: replicator
replication:
failover: manual
groups:
group001:
replicasets:
replicaset001:
leader: instance001
instances:
instance001:
iproto:
listen:
- uri: '127.0.0.1:3301'
instance002:
iproto:
listen:
- uri: '127.0.0.1:3302'
Код: Выделить всё
tarantool --name instance001 --config config.yaml
Код: Выделить всё
box.cfg (legacy) -> config.yaml (3.x)
------------------------------------------------
listen = 3301 -> iproto.listen: [{uri: '...'}]
replication = {...} -> (вычисляется из топологии replicaset)
read_only = true -> database.mode: ro (или через failover)
memtx_memory = N -> memtx.memory: N
wal_dir / memtx_dir = ... -> wal.dir / snapshot.dir
log_level = 5 -> log.level: 5
Код: Выделить всё
require('config'):get('iproto.listen') -- значение опции для ЭТОГО инстанса
require('config'):get('labels') -- метки инстанса
require('config'):info() -- статус, алерты, активный конфиг
require('config'):reload() -- перечитать файл без рестарта
- "Приоритет идёт сверху вниз по файлу". Нет. Приоритет определяется СКОУПОМ, а не порядком строк: instance всегда сильнее global, где бы они ни стояли.
- Путают global и group. global - это корень файла. group - это внутри groups.group001. Параметр, написанный в корне, видят все группы; в group001 - только её инстансы.
- Ждут, что любой reload применит всё. Не-динамические опции (память, директории) на горячую не применяются - только рестарт. Смотрите алерты в config:info().
- Массивы переопределяются, не сливаются. Для roles (тип array) роль инстанса полностью заменяет роли с верхнего уровня, а не добавляется к ним. А вот roles_cfg (map) сливается по ключам.
- labels наследуются и переопределяются по ключу из всех скоупов, к которым принадлежит инстанс - удобно, но легко получить не ту метку, забыв про верхний уровень.
- Имя инстанса обязано совпадать с ключом в YAML и с тем, что передано в --name. Иначе инстанс не найдёт свою ветку конфигурации.
- Шаблоны. В значениях можно использовать {{ instance_name }}, {{ replicaset_name }}, {{ group_name }} - например, чтобы развести пути снапшотов по инстансам.
Возьмите config.yaml из примера выше. Добавьте опцию log.level: 'info' в глобальный скоуп (корень файла), а для instance002 переопределите её на 'verbose' в его instance-скоупе. Запустите instance002, выполните в консоли require('config'):get('log.level') и убедитесь, что вернулось 'verbose'. Затем запустите instance001 и проверьте, что у него унаследовалось 'info'. Поясните себе, почему: instance > global.
Контрольные вопросы
- Перечислите четыре скоупа конфигурации и укажите порядок их приоритета при конфликте одного и того же ключа.
- Чем отличается global-скоуп от group-скоупа и где физически в YAML находится каждый из них?
- Во что в config.yaml мигрируют box.cfg-опции listen, memtx_memory и read_only?
- Что произойдёт при config:reload(), если в файле изменили не-динамическую опцию (например размер памяти)?