Версия:

Модуль shard

Модуль shard

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

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

Для начала введем терминологию:

Консистентное хеширование
Модуль shard распределяет данные в соответствии с алгоритмом хеширования, то есть применяет хеш-функцию к значению первичного ключа кортежа, что определить к какому шарду относится кортеж. Хеш-функция является консистентной, поэтому изменение количества серверов не повлияет на результат для множества ключей. Модуль shard использует специальную хеш-функцию digest.guava из модуля digest.
Экземпляр
Запущенная in-memory копия Tarantool-сервера иногда называется экземпляром сервера. Как правило, каждый шард ассоциирован с одним экземпляром, или же, если выполняется и шардинг, и репликация, каждый шард ассоциирован с одним набором реплик.
Очередь
Временный список последних запросов обновления. Иногда называется «пакетная обработка». Поскольку обновления в базу данных с шардингом могут быть замедлены, ускорить выполнение можно путем отправки запросов в очередь вместо ожидания окончания обновления на каждом узле. В модуле shard присутствуют функции для добавления запросов в очередь, которые будут затем обработаны без дополнительных действий. Использование очереди необязательно.
Резервирование по принципу избыточности
Количество копий реплицируемых данных в каждом шарде.
Реплика
Экземпляр, который входит в набор реплик.
Набор реплик
Часто отдельный шард ассоциирован с отдельным экземпляром. Однако, часто шард реплицируется. Когда шард реплицируется, множество экземпляров («реплики»), которые обрабатывают реплицируемые данные шарда, составляют «набор реплик».
Реплицируемые данные
Полная копия данных. Модуль shard обрабатывает как шардинг, так и репликацию. Один шард может содержать одну или несколько копий реплицируемых данных. Попытки записи производятся по очереди на каждую копию реплицируемых данных. Модуль shard не использует встроенную функцию репликации.
Шард
Подмножество кортежей в базе данных, разделенное по значению, которое возвращает консистентная хеш-функция. Как правило, каждый шард находится на отдельном узле или отдельном наборе узлов (например, если резервирование = 3, то шард будет на трех узлах).
Зона
Физическое местоположение, где узлы тесно связаны, с одинаковыми точками безопасности, резервного копирования и доступа. Простейшим примером зоны является один компьютер с одним экземпляром Tarantool-сервера. Копии реплицируемых данных на шарде должны находиться в разных зонах.

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

  • либо на версии Tarantool’а 1.7.4+ выполните команду:

    $ tarantoolctl rocks install shard
    
  • либо установите с помощью yum или apt, например, на Ubuntu выполните команду:

    $ sudo apt-get install tarantool-shard
    
  • либо скачайте из GitHub tarantool/shard и используйте Lua-файлы, как описано в файле README.

Затем перед использованием модуля выполните команду shard = require('shard').

Самой необходимой функцией модуля является

shard.init(*настройка-шарда*)

Ее следует вызывать для каждого шарда.

Настройка шарда представляет собой таблицу со следующими полями:

  • servers – серверы, т.е. список URI узлов и зон, в которых находятся узлы
  • login – имя пользователя, которое используется для доступа по модулю shard
  • password – пароль для имени пользователя
  • redundancy – резервирование, число, минимум 1
  • binary – номер порта, на котором настроено прослушивание для текущего хоста (отличный от порта „listen“, который определяет box.cfg)

Возможные ошибки:

  • значение параметра redundancy (резервирование) не должно быть больше количества серверов;
  • серверы должны быть рабочими;
  • две копии реплицируемых данных одного шарда не должны находиться в одной зоне.

Пример: синтаксис shard.init для одного шарда

  • Количество копий реплицируемых данных на один шард (redundancy – резервирование) равно 3.
  • Количество экземпляров равно 3.
  • Модуль shard делает вывод, что существует только один шард.
tarantool> cfg = {
          >   servers = {
          >     { uri = 'localhost:33131', zone = '1' },
          >     { uri = 'localhost:33132', zone = '2' },
          >     { uri = 'localhost:33133', zone = '3' }
          >   },
          >   login = 'test_user',
          >   password = 'pass',
          >   redundancy = '3',
          >   binary = 33131,
          > }
 ---
 ...
 tarantool> shard.init(cfg)
 ---
 ...

Пример: синтаксис shard.init для трех шардов

Здесь описаны три шарда. Каждый шард содержит две копии реплицируемых данных. Поскольку количество серверов равно 7, количество копий реплицируемых данных на один шард равно 2, а деление 7 на 2 дает в остатке 1, – один из серверов не будет использоваться. Это необязательно должно быть ошибкой, поскольку один из серверов может быть нерабочим.

tarantool> cfg = {
          >   servers = {
          >     { uri = 'host1:33131', zone = '1' },
          >     { uri = 'host2:33131', zone = '2' },
          >     { uri = 'host3:33131', zone = '3' },
          >     { uri = 'host4:33131', zone = '4' },
          >     { uri = 'host5:33131', zone = '5' },
          >     { uri = 'host6:33131', zone = '6' },
          >     { uri = 'host7:33131', zone = '7' }
          >   },
          >   login = 'test_user',
          >   password = 'pass',
          >   redundancy = '2',
          >   binary = 33131,
          > }
 ---
 ...
 tarantool> shard.init(cfg)
 ---
 ...

Каждой функции взаимодействия с данными модуля box соответствует функция в модуле shard:

shard[*имя-спейса*].insert{...}
 shard[*имя-спейса*].replace{...}
 shard[*имя-спейса*].delete{...}
 shard[*имя-спейса*].select{...}
 shard[*имя-спейса*].update{...}
 shard[*имя-спейса*].auto_increment{...}

Например, чтобы выполнить вставку в таблицу T в базе данных с шардингом, просто выполните команду shard.T:insert{...} вместо box.space.T:insert{...}.

A shard.T:select{} request without a primary key will cause an error.

Каждой функции модуля box для взаимодействия с данными, поставленной в очередь, соответствует функция в модуле shard:

shard[*имя-спейса*].q_insert{...}
 shard[*имя-спейса*].q_replace{...}
 shard[*имя-спейса*].q_delete{...}
 shard[*имя-спейса*].q_select{...}
 shard[*имя-спейса*].q_update{...}
 shard[*имя-спейса*].q_auto_increment{...}

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

Пример: шард, минимальная настройка

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

$ mkdir ~/tarantool_sandbox_1
 $ cd ~/tarantool_sandbox_1
 $ rm -r *.snap
 $ rm -r *.xlog
 $ ~/tarantool-1.7/src/tarantool

 tarantool> box.cfg{listen = 3301}
 tarantool> box.schema.space.create('tester')
 tarantool> box.space.tester:create_index('primary', {})
 tarantool> box.schema.user.create('test_user', {password = 'pass'})
 tarantool> box.schema.user.grant('test_user', 'read,write,execute', 'universe')
 tarantool> cfg = {
          >   servers = {
          >       { uri = 'localhost:3301', zone = '1' },
          >   },
          >   login = 'test_user';
          >   password = 'pass';
          >   redundancy = 1;
          >   binary = 3301;
          > }
 tarantool> shard = require('shard')
 tarantool> shard.init(cfg)
 tarantool> -- Now put something in ...
 tarantool> shard.tester:insert{1,'Tuple #1'}

Если вырезать и вставить вышеуказанное, то результат с запросами и ответами только для shard.init и shard.tester должен выглядеть примерно так:

<...>
 tarantool> shard.init(cfg)
 2017-09-06 ... I> Sharding initialization started...
 2017-09-06 ... I> establishing connection to cluster servers...
 2017-09-06 ... I> connected to all servers
 2017-09-06 ... I> started
 2017-09-06 ... I> redundancy = 1
 2017-09-06 ... I> Adding localhost:3301 to shard 1
 2017-09-06 ... I> shards = 1
 2017-09-06 ... I> Done
 ---
 - true
 ...
 tarantool> -- Введите что-то...
 ---
 ...
 tarantool> shard.tester:insert{1,'Tuple #1'}
 ---
 - - [1, 'Tuple #1']
 ...

Пример: шард, горизонтальное масштабирование

Созданы два шарда, каждый из которых содержит одну копию реплицируемых данных. В реальной жизни два узла будут представлены двумя компьютерами, для примера же требуется использовать две оболочки, которые мы назовем «терминал №1» и «терминал №2».

В первом терминале введите:

$ mkdir ~/tarantool_sandbox_1
 $ cd ~/tarantool_sandbox_1
 $ rm -r *.snap
 $ rm -r *.xlog
 $ ~/tarantool-1.7/src/tarantool

 tarantool> box.cfg{listen = 3301}
 tarantool> box.schema.space.create('tester')
 tarantool> box.space.tester:create_index('primary', {})
 tarantool> box.schema.user.create('test_user', {password = 'pass'})
 tarantool> box.schema.user.grant('test_user', 'read,write,execute', 'universe')
 tarantool> console = require('console')
 tarantool> cfg = {
          >   servers = {
          >     { uri = 'localhost:3301', zone = '1' },
          >     { uri = 'localhost:3302', zone = '2' },
          >   },
          >   login = 'test_user',
          >   password = 'pass',
          >   redundancy = 1,
          >   binary = 3301,
          > }
 tarantool> shard = require('shard')
 tarantool> shard.init(cfg)
 tarantool> -- Введите что-нибудь ...
 tarantool> shard.tester:insert{1,'Tuple #1'}

Во втором терминале введите:

$ mkdir ~/tarantool_sandbox_2
 $ cd ~/tarantool_sandbox_2
 $ rm -r *.snap
 $ rm -r *.xlog
 $ ~/tarantool-1.7/src/tarantool

 tarantool> box.cfg{listen = 3302}
 tarantool> box.schema.space.create('tester')
 tarantool> box.space.tester:create_index('primary', {})
 tarantool> box.schema.user.create('test_user', {password = 'pass'})
 tarantool> box.schema.user.grant('test_user', 'read,write,execute', 'universe')
 tarantool> console = require('console')
 tarantool> cfg = {
          >   servers = {
          >     { uri = 'localhost:3301', zone = '1' };
          >     { uri = 'localhost:3302', zone = '2' };
          >   };
          >   login = 'test_user';
          >   password = 'pass';
          >   redundancy = 1;
          >   binary = 3302;
          > }
 tarantool> shard = require('shard')
 tarantool> shard.init(cfg)
 tarantool> -- Выведите что-нибудь ...
 tarantool> shard.tester:select{1}

На терминале №1 появится цикл сообщений с ошибками типа «Connection refused» (в подключении отказано) и «server check failure» (отказ проверки сервера). Это нормально. Сообщения будут появляться, пока не начнется процесс на терминале №2.

В конце, на терминале №2 появится примерно следующее:

tarantool> shard.tester:select{1}
 ---
 - - - [1, 'Tuple #1']
 ...

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

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