Реализация шардирования

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

Способы реализации шардирования

Существует три популярных способа реализовать шардирование:
  • Средствами базы данных Некоторые базы данных, такие как MongoDB, Elasticsearch, ClickHouse и другие, могут автоматически распределять данные между своими экземплярами. Для этого достаточно настроить конфигурацию. Этот способ самый простой.
  • С использованием надстроек к базе данных Можно применять различные сторонние утилиты, которые выполняют шардирование, например для PostgreSQL (Citus). Однако здесь есть риск потери данных и снижения производительности.
  • С применением клиентских средств В этом случае экземпляры базы данных не знают о существовании друг друга, а шардированием управляет ваш сервис.

Шардирование в Redis

Redis использует шардирование по умолчанию для распределения данных, специально ничего настраивать не придётся. Всё пространство ключей в кластерах Redis разделено на 16384 слота (называемых слотами хэша), и эти слоты назначены нескольким узлам Redis. Данный ключ сопоставляется одному из этих слотов. Хэш-слот для ключа вычисляется как: HASH_SLOT = CRC16 (ключ) mod 16384, подробнее об этом можно прочитать на сайте Redis.

Шардирование в PostgreSQL: практика

В PostgreSQL для шардирования используется сторонний инструмент Citus. Для начала работы с Citus необходимо установить его расширение:
sudo apt-get install postgresql-13-citus 
Затем инициализировать Citus:
CREATE EXTENSION citus; 
Создать распределённую таблицу:
CREATE TABLE distributed_table (
 id SERIAL,
 data TEXT
);
SELECT create_distributed_table('distributed_table', 'id'); 
Добавить рабочие узлы:
SELECT citus_add_node('worker_node_address', 5432); 
Вставить данные в распределённую таблицу:
INSERT INTO distributed_table (data) VALUES ('example_data'); 
Всё готово, шардирование настроено!

Шардирование в MongoDB

MongoDB поддерживает две стратегии сегментирования для распределения данных по разделённым кластерам: диапазонному и хэшированному.
Самый простой способ активировать шардирование — использовать MongoAtlas или облачную инсталляцию. Более сложными вариантами будут использовать облако, а также настроить шардирование в Docker или на ВМ.
Для запуска шардированного варианта MongoDB понадобится ещё два сервиса — роутер и сервис конфигурации.
Для реализации high-avalability с шардированием рекомендуется:
  • Три инстанса роутера Роутер определяет, на какой шард отправить запрос.
  • Три инстанса с конфигурацией Конфигурационный сервер хранит метаданные кластера, которые содержат информацию о маппинге данных кластера на шарды.
  • Три шарда и три реплики для каждого шарда Шарды используются для хранения данных, каждый шард представляет собой отдельную Replicaset.
Приведём сокращённый пример запуска MongoDB в режиме шардинга: 1 роутер, 1 сервер конфигурации и 2 шарда. Обратите внимание на комментарии в docker-compose.yaml.
BASH
version: '3'
services:

  //сервер конфигурации
  configSrv:
    image: mongo:latest # docker образ
    container_name: configSrv 
    restart: always
    ports:
      - "27017:27017"
    networks:
      app-network:
        ipv4_address: 173.17.0.10
    volumes:
      - config-data:/data/db
    command:
      [
        "--configsvr",  //запуск в режиме конфигурации
        "--replSet",
        "config_server",
        "--bind_ip_all",
        "--port",
        "27017"
      ] 
    healthcheck:
      test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
      interval: 5s
      start_period: 10s

  //1-й шард 
  shard1:
    image: mongo:latest
    container_name: shard1
    restart: always
    ports:
      - "27018:27018"
    networks:
      app-network:
        ipv4_address: 173.17.0.9
    volumes:
      - shard1-data:/data/db
    command:
      [
        "--shardsvr", //запуск в режиме шардинга
        "--replSet",
        "shard1", //название реплики
        "--bind_ip_all",
        "--port",
        "27018"
      ]
    healthcheck:
      test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
      interval: 5s
      start_period: 10s

  //2-й шард 
  shard2:
    image: mongo:latest
    container_name: shard2
    restart: always
    ports:
      - "27019:27019"
    networks:
      app-network:
        ipv4_address: 173.17.0.8
    volumes:
      - shard2-data:/data/db
    command:
      [
        "--shardsvr", //запуск в режиме шардинга
        "--replSet",
        "shard2", //название реплик
        "--bind_ip_all", // обратите внимание - она отличается от реплики 1-го шарда
        "--port",
        "27019"
      ]
    healthcheck:
      test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
      interval: 5s
      start_period: 10s
  
  //роутер
  mongos_router:
    image: mongo:latest
    container_name: mongos_router
    restart: always
    ports:
      - "27020:27020"
    networks:
      app-network:
        ipv4_address: 173.17.0.7
    command:
      [
        "mongos", //обычная mongo в режиме роутера
        **"--configdb", 
        "config_server/configSrv:27017",** //передача данных сервера конфигурации
        "--bind_ip_all",
        "--port",
        "27020"
      ]
    healthcheck:
      test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
      interval: 5s
      start_period: 10s

networks:
  app-network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 173.17.0.0/16

volumes:
  config-data:
  shard1-data:
  shard2-data:
 
Фактически запустится четыре сервера MongoDB в различных режимах — роутера, шардов, сервера конфигурации. Запрос попадает сначала на роутер, где будет определён шард, на котором хранится информация.
Чтобы всё полноценно заработало, нужно выполнить ещё ряд команд после запуска.
Подключитесь к серверу конфигурации и сделайте инициализацию:
BASH
docker exec -it configSrv mongosh --port 27017

> rs.initiate(
  {
    _id : "config_server",
       configsvr: true,
    members: [
      { _id : 0, host : "configSrv:27017" }
    ]
  }
);
> exit(); 
Инициализируйте шарды:
BASH
docker exec -it shard1 mongosh --port 27018

> rs.initiate(
    {
      _id : "shard1",
      members: [
        { _id : 0, host : "shard1:27018" },
       // { _id : 1, host : "shard2:27019" }
      ]
    }
);
> exit();

docker exec -it shard2 mongosh --port 27019

> rs.initiate(
    {
      _id : "shard2",
      members: [
       // { _id : 0, host : "shard1:27018" },
        { _id : 1, host : "shard2:27019" }
      ]
    }
  );
> exit(); 
Инцициализируйте роутер и наполните его тестовыми данными:
BASH
docker exec -it mongos_router mongosh --port 27020

> sh.addShard( "shard1/shard1:27018");
> sh.addShard( "shard2/shard2:27019");

> sh.enableSharding("somedb");
> sh.shardCollection("somedb.helloDoc", { "name" : "hashed" } )

> use somedb

> for(var i = 0; i < 1000; i++) db.helloDoc.insert({age:i, name:"ly"+i})

> db.helloDoc.countDocuments() 
> exit(); 
Получится результат — 1000 документов.
Сделайте проверку на шардах:
BASH
 docker exec -it shard1 mongosh --port 27018
 > use somedb;
 > db.helloDoc.countDocuments();
 > exit(); 
Получится результат — 492 документа.
Сделайте проверку на втором шарде:
BASH
docker exec -it shard2 mongosh --port 27019
 > use somedb;
 > db.helloDoc.countDocuments();
 > exit(); 
Получится результат — 508 документов.
Поздравляем! Документы распределились.

Задание 1

Какой из методов шардирования используется в Redis?
Стандартный подход для распределения записей между серверами — хэширование. Для каждого ключа вычисляется хэш-функция, полученное значение берётся по модулю числа узлов.

Задание 2

Какой элемент НЕ нужен в шардированной конфигурации MongoDB?
Не обязательный, но возможный элемент. Он нужен скорее для мониторинга и аналитики и выглядит как доработка к основной схеме.

Итоги

  • Есть три способа реализовать шардирование: средствами БД, с использованием надстроек к БД и с применением клиентского сервиса.
  • Redis использует шардирование по умолчанию для распределения данных.
  • В PostgreSQL шардирование можно реализовать с помощью стороннего инструмента Citus.
  • В MongoDB можно реализовать диапазонное и хэшированное шардирование. Для запуска шардированного варианта MongoDB понадобится ещё два сервиса — роутер и сервис конфигурации. Вы рассмотрели практику с упрощённым шардированием — с меньшим количеством роутеров и серверов.
Следующий урок про то, как применить повысить устойчивость к нагрузкам уже не базы данных, а приложения. Там расскажем про два способа хранения состояния приложения — сравним stateless и statefull. Покажем примеры использования балансировщиков нагрузки. Ещё разберём механизм работы Service Discovery, API Gateway и API-шлюза для распределения нагрузки.

Что почитать по теме

Готовая реализация примера шардирования Mongo с использованием docker-compose на 15 инстансов. Запустить локально её может быть проблематично из-за ресурсов.