Версия:

Модуль vshard

Модуль vshard

Обзор

В модуле vshard реализована новая функция шардинга (сегментирования), которая позволяет осуществлять горизонтальное масштабирование в Tarantool’е.

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

Есть два различных подхода к масштабированию данных: вертикальное и горизонтальное масштабирование:

  • Вертикальное масштабирование подразумевает увеличение производительности системы отдельного сервера.
  • Горизонтальное масштабирование подразумевает секционирование набора данных и распределение данных по множеству серверов. При добавлении новых серверов набор данных повторно равномерно распределяется по всем серверам, новым и старым.

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

С помощью модуля vshard кортежи набора данных распределяются по множеству узлов, на каждом из которых находится экземпляр сервера базы данных Tarantool’а. Каждый экземпляр обрабатывает лишь подмножество от общего количества данных, поэтому увеличение нагрузки можно компенсировать добавлением новых серверов. Первоначальный набор данных секционируется на множество частей, таким образом, что каждая часть хранится на отдельном сервере. Секционирование набора данных осуществляется с помощью сегментных ключей.

Модуль vshard основан на концепции виртуальных сегментов: набор кортежей распределяется на большое количество абстрактных виртуальных узлов (виртуальных сегментов, или сегментов), а не на малое количество физических узлов.

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

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

Модуль vshard предоставляет функции, аналогичные функциям по управлению данными библиотеки Tarantool’а box (select, insert, replace, update, delete).

Установка

Пакет vshard распространяется отдельно от основного пакета Tarantool’а. Для работы с ним выполните установку отдельно:

$ tarantoolctl rocks install vshard

Примечание

Для работы с модулем vshard необходима версия Tarantool’а 1.9+.

Краткое руководство

В директории vshard/example/ находится предварительно настроенный кластер из 1 роутера и 2 наборов реплик из 2 узлов (2 хранилища) в каждом, что составляет всего 5 экземпляров Tarantool’а в целом:

  • router_1 – экземпляр роутера
  • storage_1_a – экземпляр хранилища, мастер первого набора реплик
  • storage_1_b – экземпляр хранилища, реплика из первого набора реплик
  • storage_2_a – экземпляр хранилища, мастер второго набора реплик
  • storage_2_b – экземпляр хранилища, реплика из второго набора реплик

Управление всеми экземплярами осуществляется с помощью утилиты tarantoolctl из корневой директории проекта.

Измените директорию example/ и используйте команду make для запуска кластера:

$ cd example/
$ make
tarantoolctl stop storage_1_a  # stop the first storage instance
Stopping instance storage_1_a...
tarantoolctl stop storage_1_b
<...>
rm -rf data/
tarantoolctl start storage_1_a # start the first storage instance
Starting instance storage_1_a...
Starting configuration of replica 8a274925-a26d-47fc-9e1b-af88ce939412
I am master
Taking on replicaset master role...
Run console at unix/:./data/storage_1_a.control
started
mkdir ./data/storage_1_a
<...>
tarantoolctl start router_1 # start the router
Starting instance router_1...
Starting router configuration
Calling box.cfg()...
<...>
Run console at unix/:./data/router_1.control
started
mkdir ./data/router_1
Waiting cluster to start
echo "vshard.router.bootstrap()" | tarantoolctl enter router_1
connected to unix/:./data/router_1.control
unix/:./data/router_1.control> vshard.router.bootstrap()
---
- true
...
unix/:./data/router_1.control>
tarantoolctl enter router_1 # enter the admin console
connected to unix/:./data/router_1.control
unix/:./data/router_1.control>

Некоторые команды tarantoolctl:

  • tarantoolctl start router_1 – запуск экземпляра роутера
  • tarantoolctl enter router_1 – вход в административную консоль

Полный список команд tarantoolctl для управления экземплярами Tarantool’а можно найти в справочнике по tarantoolctl.

Необходимо знать следующие команды make:

  • make start – запуск всех экземпляров Tarantool’а
  • make stop – остановка всех экземпляров Tarantool’а
  • make logcat – вывод журналов всех экземпляров
  • make enter – вход в административную консоль на роутере router_1
  • make clean – очистка всех персистентных данных
  • make test – запуск набора тестов (можно также выполнить test-run.py в директории с тестами)
  • make – выполнить make stop, make clean, make start и make enter

Например, для запуска всех экземпляров используйте make start:

$ make start
$ ps x|grep tarantool
46564   ??  Ss     0:00.34 tarantool storage_1_a.lua <running>
46566   ??  Ss     0:00.19 tarantool storage_1_b.lua <running>
46568   ??  Ss     0:00.35 tarantool storage_2_a.lua <running>
46570   ??  Ss     0:00.20 tarantool storage_2_b.lua <running>
46572   ??  Ss     0:00.25 tarantool router_1.lua <running>

Для выполнения команд в административной консоли, используйте API router:

unix/:./data/router_1.control> vshard.router.info()
---
- replicasets:
    ac522f65-aa94-4134-9f64-51ee384f1a54:
      replica: &0
        network_timeout: 0.5
        status: available
        uri: storage@127.0.0.1:3303
        uuid: 1e02ae8a-afc0-4e91-ba34-843a356b8ed7
      uuid: ac522f65-aa94-4134-9f64-51ee384f1a54
      master: *0
    cbf06940-0790-498b-948d-042b62cf3d29:
      replica: &1
        network_timeout: 0.5
        status: available
        uri: storage@127.0.0.1:3301
        uuid: 8a274925-a26d-47fc-9e1b-af88ce939412
      uuid: cbf06940-0790-498b-948d-042b62cf3d29
      master: *1
  bucket:
    unreachable: 0
    available_ro: 0
    unknown: 0
    available_rw: 3000
  status: 0
  alerts: []
...

Архитектура

Сегментированный кластер в Tarantool’е состоит из хранилищ, роутеров и балансировщика.

Хранилище (storage) – это узел, который хранит подмножество набора данных. Развертывание нескольких реплицируемых хранилищ осуществляется в виде наборов реплик, чтобы обеспечить резерв (набор реплик также можно называть шардом или сегментом).

Роутер (router) – это автономный компонент ПО, который обеспечивает маршрутизацию запросов чтения и записи от клиентского приложения к шардам.

Балансировщик (rebalancer) – это внутренний компонент, который равномерно распределяет набор данных между всеми шардами в случае добавления или удаления серверов. Он также занимается выравниванием нагрузки с учетом производительности существующих наборов реплик.

../../../_images/schema.svg

Хранилище

Хранилище (storage) – это узел, который хранит подмножество набора данных. Несколько реплицируемых хранилищ составляют набор реплик. У каждого хранилища в наборе реплик есть роль: мастер или реплика. Мастер обрабатывает запросы на чтение и запись. Реплики обрабатывают запросы на чтение, но не могут обрабатывать запросы на запись.

../../../_images/master_replica.svg

Виртуальные сегменты

Набор данных при шардинге распределяется на большое количество абстрактных узлов, которые называются виртуальные сегменты (далее по тексту сегменты).

Секционирование набора данных происходит с помощью сегментного ключа (или идентификатора сегмента (bucket id) в терминах Tarantool’а). Идентификатор сегмента – это число от 1 до N, где N – это общее количество сегментов.

../../../_images/buckets.svg

В каждом наборе реплик есть уникальное подмножество сегментов. Один сегмент не может относиться к нескольким наборам реплик одновременно.

../../../_images/vbuckets.svg

Общее количество сегментов определяет администратор, который настраивает первоначальную конфигурацию кластера.

Каждый спейс Tarantool’а, который планируется сегментировать, должен включать в себя проиндексированное поле с идентификатором сегмента. Спейсы без индексов идентификаторов сегментов не участвуют в шардинге, но могут использоваться в качестве обычных спейсов. По умолчанию, имя индекса совпадает с идентификатором сегмента.

Миграция сегментов

Балансировщик представляет собой фоновый процесс балансировки, который обеспечивает равномерное распределение сегментов по шардам. Во время балансировки происходит миграция сегментов по наборам реплик.

Набор реплик, из которого переносится сегмент, называется исходный (source); а набор реплик, куда переносится сегмент, называется целевой (destination).

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

Во время миграции у сегмента могут быть разные статусы:

  • ACTIVE (активный) – сегмент доступен для запросов чтения и записи.
  • PINNED (закрепленный) – сегмент заблокирован для миграции в другой набор реплик. Во всем остальном закрепленные сегменты аналогичны активным сегментам.
  • SENDING (отправляемый) – в настоящий момент сегмент копируется в целевой набор реплик; запросы на чтение в исходный набор реплик обрабатываются.
  • RECEIVING (принимающий) – происходит наполнение сегмента; все запросы отклоняются.
  • SENT (отправленный) – произошла миграция сегмента в целевой набор реплик. Роутер использует такой статус, чтобы рассчитать новое местоположение сегмента. Отправленный сегмент в статусе SENT автоматически переходит в статус мусора GARBAGE через BUCKET_SENT_GARBAGE_DELAY секунд после миграции (по умолчанию задано 0.5 секунды 0.5 секунды).
  • GARBAGE (мусор) – произошла миграция сегмента в целевой набор реплик во время балансировки; или же принимающий сегмент был в статусе RECEIVING, но произошла ошибка во время миграции.

Сегменты в статусе мусора GARBAGE удаляются сборщиком мусора.

../../../_images/states.svg

В целом, миграция происходит следующим образом:

  1. В целевом наборе реплик создается новый сегмент, который получает статус RECEIVING (принимающий), начинается копирование данных, и сегмент отклоняет все запросы.
  2. Отправляемый сегмент в исходном наборе реплик получает статус SENDING и продолжает обрабатывать запросы на чтение.
  3. После копирования данных сегмент в исходном наборе реплик помечается отправленным в статусе SENT и перестает принимать запросы.
  4. Сегмент в целевом наборе реплик переходит в активный статус ACTIVE и начинает принимать все запросы.

Системный спейс _bucket

Системный спейс _bucket в каждом наборе реплик хранит идентификаторы сегментов данного набора реплик. Спейс содержит следующие поля:

  • bucket – идентификатор сегмента
  • status – статус сегмента
  • destination – UUID целевого набора реплик

Пример _bucket.select{}:

---
- - [1, ACTIVE, abfe2ef6-9d11-4756-b668-7f5bc5108e2a]
  - [2, SENT, 19f83dcb-9a01-45bc-a0cf-b0c5060ff82c]
...

После миграции сегмента UUID целевого набора реплик вносится в таблицу. Пока сегмент еще находится в исходном наборе реплик, значение UUID целевого набора реплик равно NULL.

Роутер

Все запросы из приложения приходят в сегментированный кластер через роутер. Роутер сохраняет топологию сегментированного кластера прозрачной для приложения, не сообщая приложению:

  • номер и местоположение шардов,
  • процесс балансировки данных,
  • наличие отказа и восстановление после отказа реплики.

У роутера нет постоянного статуса, он не хранит топологию кластера и не выполняет балансировку данных. Роутер – это автономный компонент ПО, который может работать на уровне хранилища или на уровне приложения в зависимости от функций приложения.

Таблица маршрутизации

Таблица маршрутизации роутера отображает все идентификаторы сегментов с соответствующими наборами реплик. Она обеспечивает консистентность шардинга в случае отказа.

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

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

Обработка запросов

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

Сначала все запросы направляются в роутер. Роутер поддерживает только операцию вызова. Операция выполняется с помощью функции vshard.router.call():

result = vshard.router.call(<идентификатор_сегмента>, <режим(read:write)>, <имя_функции>, {<список_аргументов>}, {<опции>})

Запросы обрабатываются следующим образом:

  1. Роутер использует идентификатор сегмента для поиска набора реплик с соответствующим сегментом в таблице маршрутизации.

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

  2. После обнаружения сегмента шард проверяет:

    • хранится ли сегмент в системном спейсе _bucket набора реплик;
    • находится ли сегмент в статусе ACTIVE (активный) или PINNED (закрепленный) (если выполняется запрос на чтение, то сегмент может находиться в состоянии отправки SENDING).
  3. Если проверка пройдена, запрос выполняется. В противном случае, выполнение запроса прекращается с ошибкой: “wrong bucket” (несоответствующий сегмент).

Администрирование

Конфигурация сегментированного кластера

Минимальный рабочий сегментированный кластер должен состоять из:

  • одного или нескольких наборов реплик с двумя или несколькими хранилищами в каждом
  • одного или нескольких роутеров

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

vshard поддерживает работу с несколькими роутерами в отдельном экземпляре Tarantool’а. Каждый роутер может подключиться к любому кластеру vshard. Несколько роутеров могут быть подключены к одному кластеру.

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

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

Самоопределение в настоящий момент осуществляется с помощью tarantoolctl:

$ tarantoolctl имя_экземпляра

Все роутеры также можно развернуть, используя один набор файлов экземпляра (конфигурационных файлов).

Топология всех узлов кластера должна быть одинаковой. Администратор должен убедиться, что конфигурации совпадают. Рекомендуем использовать инструмент управления конфигурациями, такой как Ansible или Puppet, во время развертывания кластера.

Шардинг не интегрирован ни в одну систему для централизованного управления конфигурациями. Подразумевается, что само приложение отвечает за взаимодействие с такой системой и передачу параметров шардинга.

Образец конфигурации

Конфигурация простого сегментированного кластера может выглядеть следующим образом:

local cfg = {
    memtx_memory = 100 * 1024 * 1024,
    replication_connect_quorum = 0,
    bucket_count = 10000,
    rebalancer_disbalance_threshold = 10,
    rebalancer_max_receiving = 100,
    sharding = {
        ['cbf06940-0790-498b-948d-042b62cf3d29'] = {
            replicas = {
                ['8a274925-a26d-47fc-9e1b-af88ce939412'] = {
                    uri = 'storage:storage@127.0.0.1:3301',
                    name = 'storage_1_a',
                    master = true
                },
                ['3de2e3e1-9ebe-4d0d-abb1-26d301b84633'] = {
                    uri = 'storage:storage@127.0.0.1:3302',
                    name = 'storage_1_b'
                }
            },
        },
        ['ac522f65-aa94-4134-9f64-51ee384f1a54'] = {
            replicas = {
                ['1e02ae8a-afc0-4e91-ba34-843a356b8ed7'] = {
                    uri = 'storage:storage@127.0.0.1:3303',
                    name = 'storage_2_a',
                    master = true
                },
                ['001688c3-66f8-4a31-8e19-036c17d489c2'] = {
                    uri = 'storage:storage@127.0.0.1:3304',
                    name = 'storage_2_b'
                }
            },
        },
    },
}

Данный кластер включает в себя один роутер и два хранилища. Каждое хранилище включает в себя один мастер и одну реплику.

Поле sharding (шардинг) определяет логическую топологию сегментированного кластера Tarantool’а. Все остальные поля передаются в box.cfg() в неизменном виде. Для получения подробной информации см. раздел Справочник по настройке.

На роутерах вызовите vshard.router.cfg(cfg):

cfg.listen = 3300

-- Запуск базы данных с шардингом
vshard = require('vshard')
vshard.router.cfg(cfg)

На хранилищах вызовите vshard.storage.cfg(cfg, uuid_экземпляра):

-- Получение имени экземпляра
local MY_UUID = "de0ea826-e71d-4a82-bbf3-b04a6413e417"

-- Вызов поставщика конфигурации
local cfg = require('localcfg')

-- Запуск базы данных с шардингом
vshard = require('vshard')
vshard.storage.cfg(cfg, MY_UUID)

vshard.storage.cfg() автоматически вызывает box.cfg() и настраивает порт для прослушивания и параметры репликации.

Образец конфигурации можно посмотреть в файлах router.lua и storage.lua в директории vshard/example.

Вес реплики

Роутер отправляет все запросы только на мастер-экземпляр. Задав вес реплики, можно разрешить отправку запросов на чтение не только на мастер-экземпляр, но и на доступную реплику, которая находится „ближе всего“ к роутеру. Вес используется для определения расстояния между репликами в наборе реплик.

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

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

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

Чтобы задать вес, используйте атрибут zone (зона) для каждой реплики в конфигурации:

local cfg = {
   sharding = {
      ['...uuid_набора_реплик...'] = {
         replicas = {
            ['...uuid_реплики...'] = {
                 ...,
                 zone = <число или строка>
            }
         }
      }
   }
}

Затем укажите относительный вес для каждой пары зон в параметре weights (вес) в vshard.router.cfg. Например:

weights = {
    [1] = {
        [2] = 1, -- роутеры 1 зоны видят вес 2 зоны = 1
        [3] = 2, -- роутеры 1 зоны видят вес 3 зоны = 2


   [4] = 3, -- ...
    },
    [2] = {
        [1] = 10,
        [2] = 0,
        [3] = 10,
        [4] = 20,
    },
    [3] = {
        [1] = 100,
        [2] = 200, -- роутеры 3 зоны видят вес 2 зоны = 200. Обратите внимание, что этот вес не равен весу 2 зоны (= 2), который видят роутеры 1 зоны
        [4] = 1000,
    }
}

local cfg = vshard.router.cfg({weights = weights, sharding = ...})

Вес набора реплик

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

Вес набора реплик можно рассматривать как относительный объем данных в наборе реплик. Например, если replicaset_1 = 100, и replicaset_2 = 200, второй набор реплик хранит в два раза больше сегментов, чем первый. По умолчанию веса всех наборов реплик равны.

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

Процесс балансировки

Существует эталонное число сегментов в наборе реплик. Если во всех наборах реплик это число остается неизменным, то сегменты распределяются равномерно.

Эталонное число рассчитывается автоматически с учетом количества сегментов в кластере и веса наборов реплик.

Например: Пользователь указал количество сегментов = 3000, а вес 3 наборов реплик равен 1, 0,5 и 1,5. В результате получаем следующее эталонное число сегментов для наборов реплик: 1 набор реплик – 1000, 2 набор реплик – 500, 3 набор реплик – 1500.

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

Примечание

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

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

Предел дисбаланса рассчитывается следующим образом:

|эталонное_число_сегментов - текущее_число_сегментов| / эталонное_число_сегментов * 100

При добавлении нового шарда конфигурацию можно обновить динамически:

  1. Конфигурацию следует сначала обновить на всех роутерах, а затем на всех хранилищах.
  2. Новый шард становится доступен для балансирования на уровне хранилища.
  3. В результате балансировки происходит миграция сегментов на новый шард.
  4. Если происходит запрос к перемещенному сегменту, роутер получает код ошибки с информацией о новом местонахождении сегмента.

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

Блокировка набора реплик и закрепление корзины

Блокировка набора реплик делает набор реплик невидимым для балансировщика: заблокированный набор реплик не может ни принимать новые сегменты, ни мигрировать собственные сегменты.

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

Закрепление всех сегментов в наборе реплик не равноценно блокированию набора реплик. Даже после закрепления всех сегментов незаблокированный набор реплик может принимать новые сегменты.

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

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

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

Это нетривиальная задача, поскольку пользователь может закрепить слишком много сегментов в наборе реплик, так что становится невозможным достижение идеального баланса. Например, взгляните на следующий кластер (предположим, что все веса наборов реплик равны 1).

Начальная конфигурация:

rs1: bucket_count = 150 -- число сегментов
rs2: bucket_count = 150, pinned_count = 120 -- число сегментов, число закрепленных сегментов

Добавление нового набора реплик:

rs1: bucket_count = 150
rs2: bucket_count = 150, pinned_count = 120
rs3: bucket_count = 0

Идеальным балансом было бы 100 - 100 - 100, чего невозможно достичь, поскольку набор реплик rs2 содержит 120 закрепленных сегментов. The best possible balance here is the following:

rs1: bucket_count = 90
rs2: bucket_count = 120, pinned_count 120
rs3: bucket_count = 90

Балансировщик переместил максимально возможное количество сегментов из rs2, чтобы уменьшить дисбаланс. В то же время он учел одинаковый вес respected rs1 и rs3.

Алгоритмы учета блокировок и закрепления совершенно разные, хотя с точки зрения функциональности они похожи.

Заблокированный набор реплик и балансировка

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

Закрепленный набор реплик и балансировка

Балансировка наборов реплик с закрепленными сегментами требует более сложного алгоритма. Здесь pinned_count[o] – это число закрепленных сегментов, а etalon_count – это эталонное число сегментов на набор реплик:

  1. Балансировщик рассчитывает эталонное число сегментов, как если бы все сегменты не были закреплены. Затем балансировщик проверяет каждый набор реплик и сопоставляет эталонное число сегментов с числом закрепленных сегментов в наборе реплик. Если pinned_count < etalon_count, незаблокированные наборы реплик (на этом шаге все заблокированные наборы реплик уже отфильтрованы) с закрепленными сегментами могут получать новые сегменты.
  2. Если же pinned_count > etalon_count, дисбаланс исправить нельзя, так как балансировщик не может вывести закрепленные сегменты из этого набора реплик. В таком случае эталонное число обновляется как равное числу закрепленных сегментов. Наборы реплик с pinned_count > etalon_count не обрабатываются балансировщиком, а число закрепленных сегментов вычитается из общего числа сегментов. Балансировщик пытается вывести как можно больше сегментов из таких наборов реплик.
  3. Описанная процедура перезапускается с шага 1 для наборов реплик с pinned_count >= etalon_count до тех пор, пока не будет выполнено условие pinned_count <= etalon_count для всех наборов реплик. Процедура также перезапускается при изменении общего числа сегментов.

Псевдокод для данного алгоритма будет следующим:

function cluster_calculate_perfect_balance(replicasets, bucket_count)
        -- балансировка сегментов с использованием веса рабочих наборов реплик --
end;

cluster = <all of the non-locked replica sets>;
bucket_count = <the total number of buckets in the cluster>;
can_reach_balance = false
while not can_reach_balance do
        can_reach_balance = true
        cluster_calculate_perfect_balance(cluster, bucket_count);
        foreach replicaset in cluster do
                if replicaset.perfect_bucket_count <
                   replicaset.pinned_bucket_count then
                        can_reach_balance = false
                        bucket_count -= replicaset.pinned_bucket_count;
                        replicaset.perfect_bucket_count =
                                replicaset.pinned_bucket_count;
                end;
        end;
end;
cluster_calculate_perfect_balance(cluster, bucket_count);

Сложность алгоритма составляет O(N^2), где N – количество наборов реплик. На каждом шаге алгоритм либо завершает вычисление, либо игнорирует хотя бы один новый набор реплик, перегруженный закрепленными сегментами, и обновляет эталонное число сегментов в других наборах реплик.

Ссылка в сегменте

Ссылка в сегменте – это счетчик в оперативной памяти, который похож на закрепление сегмента со следующими отличиями:

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

  2. Есть 2 типа ссылок в сегменте: только чтение (RO) и чтение-запись (RW).

    Если в сегменте есть ссылки типа RW, его нельзя перемещать. Однако, если балансировщику требуется отправка этого сегмента, он блокирует его для новых запросов на запись, ожидает завершения всех текущих запросов, а затем отправляет сегмент.

    Если в сегменте есть ссылки типа RO, его можно отправить, но нельзя удалить. Такой сегмент может даже перейти в статус мусора GARBAGE или отправки SENT, но его данные сохраняются до тех пор, пока не уйдет последний читатель.

    В одном сегменте могут быть ссылки как типа RO, так и типа RW.

  3. Ссылки в сегменте исчисляются.

Методы vshard.storage.bucket_ref/unref() вызываются автоматически при использовании vshard.router.call() или vshard.storage.call(). При использовании API, например r = vshard.router.route() r:callro/callrw, следует дополнительно вызвать метод bucket_ref() в рамках функции. Кроме того, следует убедиться, что после bucket_ref() вызывается bucket_unref(), иначе сегмент будет закреплен в хранилище до перезапуска экземпляра.

Чтобы узнать количество ссылок в сегменте, используйте vshard.storage.buckets_info([идентификатор_сегмента]) (параметр идентификатор_сегмента необязателен).

Например:

vshard.storage.buckets_info(1)
---
- 1:
    status: active
    ref_rw: 1
    ref_ro: 1
    ro_lock: true
    rw_lock: true
    id: 1

Определение спейса

В приложении хранилища следует определить спейсы с помощью box.once(). Например:

box.once("testapp:schema:1", function()
    local customer = box.schema.space.create('customer')
    customer:format({
        {'customer_id', 'unsigned'},
        {'bucket_id', 'unsigned'},
        {'name', 'string'},
    })
    customer:create_index('customer_id', {parts = {'customer_id'}})
    customer:create_index('bucket_id', {parts = {'bucket_id'}, unique = false})

    local account = box.schema.space.create('account')
    account:format({
        {'account_id', 'unsigned'},
        {'customer_id', 'unsigned'},
        {'bucket_id', 'unsigned'},
        {'balance', 'unsigned'},
        {'name', 'string'},
    })
    account:create_index('account_id', {parts = {'account_id'}})
    account:create_index('customer_id', {parts = {'customer_id'}, unique = false})
    account:create_index('bucket_id', {parts = {'bucket_id'}, unique = false})
    box.snapshot()

    box.schema.func.create('customer_lookup')
    box.schema.role.grant('public', 'execute', 'function', 'customer_lookup')
    box.schema.func.create('customer_add')
end)

Настройка и перезапуск хранилища

В случае отказа мастера в наборе реплик рекомендуется:

  1. Переключить одну из реплик в режим мастера, что позволит новому мастеру обрабатывать все входящие запросы.
  2. Обновить конфигурацию всех членов кластера, в результате чего все запросы будут перенаправлены на новый мастер.

Мониторинг состояния мастера и переключение режимов экземпляров можно осуществлять с помощью внешней утилиты.

Для проведения запланированного остановки мастера в наборе реплик рекомендуется:

  1. Обновить конфигурацию мастера и подождать синхронизации всех реплик, в результате чего все запросы будут перенаправлены на новый мастер.
  2. Переключить другой экземпляр в режим мастера.
  3. Обновить конфигурацию всех узлов.
  4. Отключить старый мастер.

Для проведения запланированной остановки набора реплик рекомендуется:

  1. Произвести миграцию всех сегментов в другие хранилища кластера.
  2. Обновить конфигурацию всех узлов.
  3. Отключить набор реплик.

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

Файберы

Поиск сегментов, восстановление сегментов и балансировка сегментов выполняются автоматически и не требуют вмешательства человека.

С технической точки зрения есть несколько файберов, которые отвечают за различные типы действий:

  • файбер обнаружения на роутере выполняет поиск сегментов в фоновом режиме

  • файбер восстановления после отказа на роутере поддерживает соединения с репликами

  • файбер сборки мусора на каждом мастер-хранилище удаляет содержимое перемещенных сегментов

  • файбер восстановления сегмента на каждом мастер-хранилище восстанавливает сегменты в статусах отправки SENDING и получения RECEIVING в случае перезагрузки

  • балансировщик на отдельном мастер-хранилище среди множества наборов реплик выполняет процесс балансировки.

    Для получения подробной информации см. раздел Процесс балансировки.

Сборщик мусора

Файбер сборки мусора работает в фоновом режим на мастер-хранилищах в каждом наборе реплик. Он начинает удалять содержимое сегмента в состоянии мусора GARBAGE по частям. Когда сегмент пуст, запись о нем удаляется из системного спейса _bucket.

Восстановление сегмента

Файбер для восстановления сегмента работает на мастер-хранилищах. Он помогает восстановить сегменты в статусах отправки SENDING и получения RECEIVING в случае перезагрузки.

Сегменты в статусе SENDING восстанавливаются следующим образом:

  1. Сначала система ищет сегменты в статусе SENDING.
  2. Если такой сегмент обнаружен, система отправляет запрос в целевой набор реплик.
  3. Если сегмент в целевом наборе реплик находится в активном статусе ACTIVE, исходный сегмент удаляется из исходного узла.

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

Восстановление после отказа

Файбер восстановления после отказа работает на каждом роутере. Если мастер набора реплик становится недоступным, файбер перенаправляет запросы на чтение к репликам. Запросы на запись отклоняются с ошибкой до тех пор, пока мастер не будет доступен.

Справочник по настройке

Базовые параметры

sharding

Поле, которое определяет логическую топологию сегментированного кластера Tarantool’а.

Тип: таблица
По умолчанию: false (ложь)
Динамический: да
weights

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

Тип: таблица
По умолчанию: false (ложь)
Динамический: да
shard_index

Индекс по идентификатору сегмента.

Тип: непустая строка или неотрицательное целое число
По умолчанию: совпадает с числом идентификатора сегмента
Динамический: нет
bucket_count

Общее число сегментов в кластере.

Это число должно быть на несколько порядков больше, чем потенциальное число узлов кластера, учитывая потенциальное масштабирование в обозримом будущем.

Пример:

Если предполагаемое количество узлов равно M, тогда набор данных должен быть разделен на 100M или даже 1000M сегментов, в зависимости от запланированного масштабирования. Это число, безусловно, больше потенциального числа узлов кластера в проектируемой системе.

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

Тип: число
По умолчанию: 3000
Динамический: нет
collect_bucket_garbage_interval

Интервал между действия сборщика мусора в секундах.

Тип: число
По умолчанию: 0.5
Динамический: да
collect_lua_garbage

Если задано значение true (правда), периодически вызывается Lua-функция collectgarbage().

Тип: логический
По умолчанию: нет
Динамический: да
sync_timeout

Время ожидания синхронизации старого мастера с репликами перед сменой мастера. Используется при переключении мастера или при вызове функции sync() вручную.

Тип: число
По умолчанию: 1
Динамический: да
rebalancer_disbalance_threshold

Максимальная предел дисбаланса сегментов в процентах. Предел вычисляется для каждого набора реплик по следующей формуле:

|эталонное_число_сегментов - фактическое_число_сегментов| / эталонное_число_сегментов * 100
Тип: число
По умолчанию: 1
Динамический: да
rebalancer_max_receiving

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

Пример:

Предположим, rebalancer_max_receiving = 100, число сегментов в bucket_count = 1000. Есть 3 набора реплик с 333, 333 и 334 сегментами соответственно. При добавлении нового набора реплик эталонное_число_сегментов становится равным 250. Вместо того, чтобы сразу получить все 250 сегментов, новый набор реплик получит последовательно 100, 100 и 50 сегментов.

Тип: число
По умолчанию: 100
Динамический: да

Функции набора реплик

uuid

Уникальный идентификатор набора реплик.

Тип:
По умолчанию:
Динамическое:
weight

Вес набора реплик. Для получения подробной информации см. раздел Вес набора реплик.

Тип:
По умолчанию: 1
Динамическое:

Справочник по API

Общедоступные API роутера

vshard.router.bootstrap()

Выполнение первоначальной настройки кластера и распределение всех сегментов по наборам реплик.

vshard.router.cfg(cfg)

Настройка базы данных и начало шардинга указанного роутера. См. образец конфигурации выше.

Параметры:
  • cfg – конфигурационная таблица
vshard.router.new(name, cfg)

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

A router created via vshard.router.new() works in the same way as a static router, but the method name is preceded by a colon (vshard.router:method_name(...)), while for a static router the method name is preceded by a period (vshard.router.method_name(...)).

Статичный роутер можно получить при помощи метода vshard.router.static(), а затем использовать его как роутер, созданный с помощью метода vshard.router.new().

Примечание

box.cfg используется всеми роутерами одного экземпляра.

Параметры:
  • name – имя экземпляра роутера. Используется в качестве префикса в журналах роутера, должно быть уникальным в пределах экземпляра
  • cfg – конфигурационная таблица. Образец конфигурации описан выше.
Возвращается:

экземпляр роутера, если он создан; в противном случае, nil и ошибка

vshard.router.call(bucket_id, mode, function_name, {argument_list}, {options})

Вызов пользовательской функции на шарде, где хранится сегмент с указанным идентификатором. Для получения подробной информации о работе функции см. раздел Обработка запросов.

Параметры:
  • bucket_id – идентификатор сегмента
  • mode – either a string = „read“|“write“, or a map with mode=“read“|“write“ and/or prefer_replica=true|false and/or balance=true|false.
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
  • options
    • timeout – время ожидания запроса в секундах. Если роутер не может определить шард с идентификатором сегмента, операция повторяется до истечения времени ожидания.

The mode parameter has two possible forms: a string or a map. Examples of the string form are: „read“, „write“. Examples of the map form are: {mode=“read“}, {mode=“write“}, {mode=“read“, prefer_replica=true}, {mode=“read“, balance=true}, {mode=“read“, prefer_replica=true, balance=true}. If „write“ is specified then the target is the master. If prefer_replica=true is specified then the preferred target is one of the replicas, but the target is the master if there is no conveniently available replica. It may be good to specify prefer_replica=true for functions which are expensive in terms of resource use, to avoid slowing down the master. If balance=true then there is load balancing – reads are distributed over all the nodes in the replica set in round-robin fashion, with a preference for replicas if prefer_replica=true is also set.

Возвращается:

Исходное возвращаемое значение выполняемой функции или nil и ошибка. Объект ошибки содержит атрибут типа, который равен ShardingError или одной из стандартных ошибок Tarantool’а(ClientError, OutOfMemory, SocketError и т.д.).

ShardingError возвращается в случае ошибок шардинга: набор реплик недоступен, отсутствует мастер, неверный идентификатор сегмента и т.д. Такая ошибка содержит код с одним из значений из Lua-таблицы vshard.error.code.*, необязательный атрибут сообщения с удобным для восприятия описанием ошибки и другие атрибуты, специфичные для данного кода ошибки.

Примеры:

Для вызова функции customer_add из vshard/example выполните команду:

vshard.router.call(100, 'write', 'customer_add', {{customer_id = 2, bucket_id = 100, name = 'name2', accounts = {}}}, {timeout = 100})
-- or, the same thing but with a map for the second argument
vshard.router.call(100, {mode='write'}, 'customer_add', {{customer_id = 2, bucket_id = 100, name = 'name2', accounts = {}}}, {timeout = 100})
vshard.router.callro(bucket_id, function_name, {argument_list}, {options})

Call the function identified by function-name on the shard storing the bucket identified by bucket_id, in read-only mode (similar to calling vshard.router.call with mode=“read“). See the Processing requests section for details on function operation.

Параметры:
  • bucket_id – идентификатор сегмента
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
  • options
    • timeout – время ожидания запроса в секундах. Если роутер не может определить шард с идентификатором сегмента, операция повторяется до истечения времени ожидания.
Возвращается:

Исходное возвращаемое значение выполняемой функции или nil и ошибка. Объект ошибки содержит атрибут типа, который равен ShardingError или одной из стандартных ошибок Tarantool’а(ClientError, OutOfMemory, SocketError и т.д.).

ShardingError возвращается в случае ошибок шардинга: набор реплик недоступен, отсутствует мастер, неверный идентификатор сегмента и т.д. Такая ошибка содержит код с одним из значений из Lua-таблицы vshard.error.code.*, необязательный атрибут сообщения с удобным для восприятия описанием ошибки и другие атрибуты, специфичные для данного кода ошибки.

vshard.router.callrw(bucket_id, function_name, {argument_list}, {options})

Call the function identified by function-name on the shard storing the bucket identified by bucket_id, in read-write mode (similar to calling vshard.router.call with mode=“write“). See the Processing requests section for details on function operation.

Параметры:
  • bucket_id – идентификатор сегмента
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
  • options
    • timeout – время ожидания запроса в секундах. Если роутер не может определить шард с идентификатором сегмента, операция повторяется до истечения времени ожидания.
Возвращается:

Исходное возвращаемое значение выполняемой функции или nil и ошибка. Объект ошибки содержит атрибут типа, который равен ShardingError или одной из стандартных ошибок Tarantool’а(ClientError, OutOfMemory, SocketError и т.д.).

ShardingError возвращается в случае ошибок шардинга: набор реплик недоступен, отсутствует мастер, неверный идентификатор сегмента и т.д. Такая ошибка содержит код с одним из значений из Lua-таблицы vshard.error.code.*, необязательный атрибут сообщения с удобным для восприятия описанием ошибки и другие атрибуты, специфичные для данного кода ошибки.

vshard.router.callre(bucket_id, function_name, {argument_list}, {options})

Call the function identified by function-name on the shard storing the bucket identified by bucket_id, in read-only mode (similar to calling vshard.router.call with mode=“read“), with preference for a replica rather than a master (similar to calling vshard.router.call with prefer_replica = true). See the Processing requests section for details on function operation.

Параметры:
  • bucket_id – идентификатор сегмента
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
  • options
    • timeout – время ожидания запроса в секундах. Если роутер не может определить шард с идентификатором сегмента, операция повторяется до истечения времени ожидания.
Возвращается:

Исходное возвращаемое значение выполняемой функции или nil и ошибка. Объект ошибки содержит атрибут типа, который равен ShardingError или одной из стандартных ошибок Tarantool’а(ClientError, OutOfMemory, SocketError и т.д.).

ShardingError возвращается в случае ошибок шардинга: набор реплик недоступен, отсутствует мастер, неверный идентификатор сегмента и т.д. Такая ошибка содержит код с одним из значений из Lua-таблицы vshard.error.code.*, необязательный атрибут сообщения с удобным для восприятия описанием ошибки и другие атрибуты, специфичные для данного кода ошибки.

vshard.router.callbro(bucket_id, function_name, {argument_list}, {options})

This has the same effect as vshard.router.call() with mode parameter = {mode=“read“, balance=true}.

vshard.router.callbre(bucket_id, function_name, {argument_list}, {options})

This has the same effect as vshard.router.call() with mode parameter = {mode=“read“, balance=true, prefer_replica=true}.

vshard.router.route(bucket_id)

Возврат объекта набора реплик для сегмента с указанным идентификатором.

Параметры:
  • bucket_id – идентификатор сегмента
Возвращается:

объект набора реплик

Пример:

replicaset = vshard.router.route(123)
vshard.router.routeall()

Возврат всех доступных объектов наборов реплик.

Возвращается:ассоциативный массив следующего вида: {UUID = replicaset}
Тип возвращаемого значения:
 объект набора реплик

Пример:

replicaset = vshard.router.routeall()
vshard.router.bucket_id(key)

Вычисление идентификатора сегмента с помощью простой встроенной хеш-функции.

Параметры:
  • key – хеш-ключ. Это может быть любой Lua-объект (число, таблица, строка).
Возвращается:

идентификатор сегмента

Тип возвращаемого значения:
 

число

Пример:

bucket_id = vshard.router.bucket_id(18374927634039)
vshard.router.bucket_count()

Возврат общего количества сегментов, указанных в vshard.router.cfg()`.

Возвращается:общее количество сегментов
Тип возвращаемого значения:
 число
vshard.router.sync(timeout)

Ожидание синхронизации набора данных на репликах.

Параметры:
  • timeout – время ожидания в секундах
возвращается:

true (правда), если выполнено открепление сегмента; или же nil и ошибка err с объяснением причины невозможности открепления сегмента

vshard.router.discovery_wakeup()

Принудительный запуск файбера обнаружения сегментов.

vshard.router.info()

Возврат информации по каждому экземпляру.

Возвращается:

Параметры набора реплик:

  • UUID набора реплик
  • параметры мастер-экземпляра
  • параметры реплики

Параметры экземпляра:

  • uri – URI экземпляра
  • uuid – UUID экземпляра
  • status – статус экземпляра: available (доступный), unreachable (недоступный), missing (отсутствующий)
  • network_timeout – время ожидания запроса. Данное значение обновляется автоматически на каждом 10 выполненном запросе и на каждом 2 невыполненном запросе.

Параметры сегмента:

  • available_ro – количество сегментов, известных роутеру и доступных для запросов чтения
  • available_rw – количество сегментов, известных роутеру и доступных для запросов чтения и записи
  • unavailable – количество сегментов, известных роутеру, но недоступных для любых запросов
  • unreachable – the number of buckets which replica sets are not known to the router

Пример:

tarantool> vshard.router.info()
---
- replicasets:
    ac522f65-aa94-4134-9f64-51ee384f1a54:
      replica: &0
        network_timeout: 0.5
        status: available
        uri: storage@127.0.0.1:3303
        uuid: 1e02ae8a-afc0-4e91-ba34-843a356b8ed7
      uuid: ac522f65-aa94-4134-9f64-51ee384f1a54
      master: *0
    cbf06940-0790-498b-948d-042b62cf3d29:
      replica: &1
        network_timeout: 0.5
        status: available
        uri: storage@127.0.0.1:3301
        uuid: 8a274925-a26d-47fc-9e1b-af88ce939412
      uuid: cbf06940-0790-498b-948d-042b62cf3d29
      master: *1
  bucket:
    unreachable: 0
    available_ro: 0
    unknown: 0
    available_rw: 3000
  status: 0
  alerts: []
...
vshard.router.buckets_info()

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

Параметры:
  • offset – начальное значение выборки сегментов
  • limit – максимальное количество показываемых сегментов
Возвращается:

ассоциативный массив следующего вида: {bucket_id = 'unknown'/replicaset_uuid}

replicaset.call(replicaset, function_name, {argument_list}, {options})

Вызов функции с указанными аргументами на ближайшем доступном мастере (расстояние определяется с помощью матрицы replica.zone и cfg.weights).

Примечание

Метод replicaset.call аналогичен replicaset.callrw.

Параметры:
  • replicaset – UUID набора реплик
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
  • options
    • timeout – время ожидания запроса в секундах. Если роутер не может определить шард с идентификатором сегмента, операция повторяется до истечения времени ожидания.
replicaset.callrw(replicaset, function_name, {argument_list}, {options})

Вызов функции с указанными аргументами на ближайшем доступном мастере (расстояние определяется с помощью матрицы replica.zone и cfg.weights).

Примечание

Метод replicaset.callrw аналогичен replicaset.call.

Параметры:
  • replicaset – UUID набора реплик
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
  • options
    • timeout – время ожидания запроса в секундах. Если роутер не может определить шард с идентификатором сегмента, операция повторяется до истечения времени ожидания.
replicaset.callro(function_name, {argument_list}, {options})

Вызов функции с указанными аргументами на ближайшей доступной реплике (расстояние определяется с помощью матрицы replica.zone и cfg.weights). С помощью replicaset.callro() рекомендуется вызывать исключительно функции, доступные только для чтения. поскольку такие функции можно выполнять не только на мастере, но и на репликах.

Параметры:
  • replicaset – UUID набора реплик
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
  • options
    • timeout – время ожидания запроса в секундах. Если роутер не может определить шард с идентификатором сегмента, операция повторяется до истечения времени ожидания.
replicaset.callre(function_name, {argument_list}, {options})

Вызов функции с указанными аргументами на ближайшей доступной реплике (расстояние определяется с помощью матрицы replica.zone и cfg.weights). С помощью replicaset.callro() рекомендуется вызывать исключительно функции, доступные только для чтения. поскольку такие функции можно выполнять не только на мастере, но и на репликах.

Параметры:
  • replicaset – UUID набора реплик
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
  • options
    • timeout – время ожидания запроса в секундах. Если роутер не может определить шард с идентификатором сегмента, операция повторяется до истечения времени ожидания.

Внутренние API роутера

vshard.router.bucket_discovery(bucket_id)

Поиск сегмента по всему кластеру. Если сегмент не обнаружен, скорее всего, он не существует. Также сегмент также может быть перемещен во время балансировки и в данный момент находится в статусе получения RECEIVING.

Параметры:
  • bucket_id – идентификатор сегмента

Общедоступные API хранилища

vshard.storage.cfg(cfg, name)

Конфигурация базы данных и начало шардинга на указанном экземпляре хранилища.

Параметры:
  • cfg – конфигурация хранилища
  • instance_uuid – UUID экземпляра
vshard.storage.info()

Возврат информации по экземпляру хранилища в следующем формате:

tarantool> vshard.storage.info()
---
- buckets:
    2995:
      status: active
      id: 2995
    2997:
      status: active
      id: 2997
    2999:
      status: active
      id: 2999
  replicasets:
    2dd0a343-624e-4d3a-861d-f45efc571cd3:
      uuid: 2dd0a343-624e-4d3a-861d-f45efc571cd3
      master:
        state: active
        uri: storage:storage@127.0.0.1:3301
        uuid: 2ec29309-17b6-43df-ab07-b528e1243a79
    c7ad642f-2cd8-4a8c-bb4e-4999ac70bba1:
      uuid: c7ad642f-2cd8-4a8c-bb4e-4999ac70bba1
      master:
        state: active
        uri: storage:storage@127.0.0.1:3303
        uuid: 810d85ef-4ce4-4066-9896-3c352fec9e64
...
vshard.storage.call(bucket_id, mode, function_name, {argument_list})

Вызов пользовательской функции на текущем экземпляре «хранилища».

Параметры:
  • bucket_id – идентификатор сегмента
  • mode – тип функции: чтение или запись
  • function_name – выполняемая функция
  • argument_list – массив аргументов функции
Возвращается:

Исходное возвращаемое значение выполняемой функции или nil и ошибка.

vshard.storage.sync(timeout)

Ожидание синхронизации набора данных на репликах.

Параметры:
  • timeout – время ожидания в секундах
возвращается:

true (правда), если выполнено открепление сегмента; или же nil и ошибка err с объяснением причины невозможности открепления сегмента

vshard.storage.bucket_pin(bucket_id)

Закрепление сегмента в наборе реплик. Закрепленный сегмент нельзя перемещать, даже если это нарушает баланс в кластере.

Параметры:
  • bucket_id – идентификатор сегмента
возвращается:

true (правда), если выполнено закрепление сегмента; или же nil и ошибка err с объяснением причины невозможности закрепления сегмента

vshard.storage.bucket_unpin(bucket_id)

Возврат закрепленного сегмента в активное состояние.

Параметры:
  • bucket_id – идентификатор сегмента
возвращается:

true (правда), если выполнено открепление сегмента; или же nil и ошибка err с объяснением причины невозможности открепления сегмента

vshard.storage.bucket_ref(bucket_id, mode)

Создание ссылки типа RO/RW.

Параметры:
  • bucket_id – идентификатор сегмента
  • mode – чтение или запись
возвращается:

true (правда), если выполнение создание ссылки; или же nil и ошибка err с объяснением причины невозможности создания ссылки

vshard.storage.bucket_refro()

Псевдоним для vshard.storage.bucket_ref в режиме только чтения.

vshard.storage.bucket_refrw()

Псевдоним для vshard.storage.bucket_ref в режиме чтения и записи.

vshard.storage.bucket_unref(bucket_id, mode)

Удаление ссылки RO/RW.

Параметры:
  • bucket_id – идентификатор сегмента
  • mode – чтение или запись
возвращается:

true (правда), если выполнено удаление ссылки; или же nil и ошибка err с объяснением причины невозможности удаления ссылки

vshard.storage.bucket_unrefro()

Псевдоним для vshard.storage.bucket_unref в режиме только чтения.

vshard.storage.bucket_unrefrw()

Псевдоним для vshard.storage.bucket_unref в режиме чтения и записи.

vshard.storage.find_garbage_bucket(bucket_index, control)

Поиск сегмента, который хранит данные в спейсе, но не указан в спейсе _bucket, или сегмента в статусе мусора.

Параметры:
  • bucket_index – индекс спейса с частью идентификатора спейса
  • control – контроллер сборщика мусора. Если увеличивается масштаб создания сегментов, поиск следует прервать.
возвращается:

идентификатор сегмента в статусе мусора, если таковой обнаружен; в противном случае, nil

vshard.storage.buckets_info()

Возврат информации по каждому сегменту, расположенному в хранилище. Например:

vshard.storage.buckets_info(1)
---
- 1:
    status: active
    ref_rw: 1
    ref_ro: 1
    ro_lock: true
    rw_lock: true
    id: 1
vshard.storage.buckets_count()

Возврат количества сегментов, расположенных в хранилище.

vshard.storage.recovery_wakeup()

Немедленный запуск файбера восстановления, если такой есть.

vshard.storage.rebalancing_is_in_progress()

Флаг, указывающий на ход процесса балансировки. Его значение будет true (правда), если в настоящий момент узел применять маршруты, полученные от узла балансировки.

vshard.storage.is_locked()

Флаг, указывающий на блокировку балансировщика.

vshard.storage.rebalancer_disable()

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

vshard.storage.rebalancer_enable()

Запуск балансировки.

vshard.storage.sharded_spaces()

Отображение спейсов, которые доступны балансировщику и сборщику мусора.

Внутренние API хранилища

vshard.storage.bucket_recv(bucket_id, from, data)

Получение идентификатора сегмента из удаленного набора реплик.

Параметры:
  • bucket_id – идентификатор сегмента
  • from – UUID исходного набора реплик
  • data – данные, хранящиеся логически в идентификаторе сегмента в том же формате, что и возвращаемое значение метода bucket_collect() <storage_api-bucket_collect>
vshard.storage.bucket_stat(bucket_id)

Возврат информации об идентификаторе сегмента:

tarantool> vshard.storage.bucket_stat(1)
---
- 0
- status: active
  id: 1
...
Параметры:
  • bucket_id – идентификатор сегмента
vshard.storage.bucket_delete_garbage(bucket_id)

Принудительная сборка мусора для идентификатора сегмента, если сегмент был перемещен в другой набор реплик.

Параметры:
  • bucket_id – идентификатор сегмента
vshard.storage.bucket_collect(bucket_id)

Сбор всех данных, которые хранятся логически в идентификаторе сегмента:

tarantool> vshard.storage.bucket_collect(1)
---
- 0
- - - 514
    - - [10, 1, 1, 100, 'Account 10']
      - [11, 1, 1, 100, 'Account 11']
      - [12, 1, 1, 100, 'Account 12']
      - [50, 5, 1, 100, 'Account 50']
      - [51, 5, 1, 100, 'Account 51']
      - [52, 5, 1, 100, 'Account 52']
  - - 513
    - - [1, 1, 'Customer 1']
      - [5, 1, 'Customer 5']
...
Параметры:
  • bucket_id – идентификатор сегмента
vshard.storage.bucket_force_create(first_bucket_id, count)

Принудительное создание сегментов (одного или нескольких) в текущем наборе реплик. Используется только для ручного аварийного восстановления или начальной настройки.

Параметры:
  • first_bucket_id – идентификатор первого сегмента в диапазоне
  • count – количество вставляемых сегментов (по умолчанию, 1)
vshard.storage.bucket_force_drop(bucket_id)

Удаление сегмента вручную для тестирования или в аварийной ситуации.

Параметры:
  • bucket_id – идентификатор сегмента
vshard.storage.bucket_send(bucket_id, to)

Перемещение идентификатора сегмента из текущего набора реплик в удаленный набор реплик.

Параметры:
  • bucket_id – идентификатор сегмента
  • to – UUID удаленного набора реплик
vshard.storage.rebalancer_request_state()

Проверка всех сегментов хост-хранилища в статусе отправки SENT или активном статусе ACTIVE, возврат количества активных сегментов.

возвращается:количество сегментов в активном статусе, если таковые обнаружены; в противном случае, nil
vshard.storage.buckets_discovery()

Сбор массива идентификаторов активных сегментов для обнаружения.

Глоссарий

Вертикальное масштабирование
Добавление мощности в отдельный сервер: использование более мощного процессора, добавление оперативной памяти, добавление хранилищ и т.д.
Горизонтальное масштабирование
Добавление дополнительных серверов в пул ресурсов, последующее секционирование и распределение набора данных по серверам.
Шардинг
Архитектура базы данных, которая допускает секционирование набора данных по сегментному ключу и распределение набора данных по нескольким серверам. Шардинг представляет собой частный случай горизонтального масштабирования.
Узел
Виртуальный или физический экземпляр сервера.
Кластер
Набор узлов, которые составляют отдельную группу.
Хранилище
Узел, который хранит подмножество данных из набора.
Набор реплик
Ряд узлов, на которых хранятся копии набора данных. У каждого хранилища в наборе реплик есть роль: мастер или реплика.
Мастер
Хранилище в наборе реплик, которое обрабатывает запросы на чтение и запись.
Реплика
Хранилище в наборе реплик, которое обрабатывает только запросы на чтение.
Запросы на чтение
Read-only requests, that is, select requests.
Запросы на запись
Операции по изменению данных, то есть запросы на создание, замену, обновление и удаление данных.
Сегменты (виртуальные сегменты)
Абстрактные виртуальные узлы, на которые производится секционирование набора данных по сегментному ключу (идентификатору сегмента).
Идентификатор сегмента
Сегментный ключ, который опеределяет принадлежность сегмента к определенному набору реплик. Идентификатор сегмента может быть вычислен из хэш ключа.
Роутер
Прокси-сервер, который отвечает за запросы маршрутизации от приложения к узлам в кластере.