MongoDB Sharding: покроковий практичний посібник

Розподіл даних (шардинг) – це процес поділу великих обсягів інформації на менші, керовані частини, які розміщуються на різних екземплярах MongoDB у розподіленому середовищі.

Що таке шардинг?

Шардинг у MongoDB є масштабованим рішенням, яке дозволяє зберігати значні масиви даних на багатьох серверах, а не обмежуватися одним.

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

Існує два основних підходи до масштабування систем для збільшення обсягу оброблюваних даних:

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

Горизонтальне масштабування, навпаки, полягає в додаванні нових серверів і розподілі навантаження між ними. Оскільки кожен сервер обробляє лише частину даних, це забезпечує вищу ефективність та є економічно вигіднішим, ніж використання потужного дорогого обладнання. Проте, це створює потребу в додатковому обслуговуванні складної інфраструктури.

Шардинг у MongoDB використовує принцип горизонтального масштабування.

Компоненти шардингу

Для реалізації шардингу в MongoDB необхідні наступні компоненти:

Шард (Shard) – це екземпляр MongoDB, який обробляє частину вихідних даних. Шарди повинні бути розгорнуті як репліки.

Mongos – це екземпляр MongoDB, який діє як посередник між клієнтським застосунком та шардованим кластером. Він виконує роль маршрутизатора запитів до відповідних шардів.

Сервер конфігурації (Config Server) – це екземпляр MongoDB, який зберігає метадані та конфігураційні параметри кластера. MongoDB вимагає, щоб сервер конфігурації був розгорнутий як реплікаційний набір.

Архітектура шардингу

Кластер MongoDB складається з декількох реплікаційних наборів.

Кожен реплікаційний набір містить щонайменше 3 або більше екземплярів mongo. Шардований кластер може включати кілька шардів, і кожен шард працює в межах свого реплікаційного набору. Застосунок взаємодіє з Mongos, який, у свою чергу, комунікує з шардами. Тому, застосунки не взаємодіють безпосередньо з шардами. Маршрутизатор запитів розподіляє підмножини даних між вузлами на основі ключа шардингу.

Реалізація шардингу

Для налаштування шардингу виконайте наступні кроки:

Крок 1

  • Запустіть сервер конфігурації в режимі реплікації та активуйте реплікацію між ними.

mongod --configsvr --port 27019 --replSet rs0 --dbpath C:datadata1 --bind_ip localhost

mongod --configsvr --port 27018 --replSet rs0 --dbpath C:datadata2 --bind_ip localhost

mongod --configsvr --port 27017 --replSet rs0 --dbpath C:datadata3 --bind_ip localhost

Крок 2

  • Ініціалізуйте реплікаційний набір на одному з серверів конфігурації.

rs.initiate( { _id : "rs0", configsvr: true, members: [ { _id: 0, host: "IP:27017" }, { _id: 1, host: "IP:27018" }, { _id: 2, host: "IP:27019" } ] })

rs.initiate( { _id : "rs0", configsvr: true, members: [ { _id: 0, host: "IP:27017" }, { _id: 1, host: "IP:27018" }, { _id: 2, host: "IP:27019" } ] })
{
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1593569257, 1),
                "electionId" : ObjectId("000000000000000000000000")
        },
        "lastCommittedOpTime" : Timestamp(0, 0),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593569257, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1593569257, 1)
}

Крок 3

  • Запустіть сервери шардів в режимі реплікації та ввімкніть реплікацію між ними.

mongod --shardsvr --port 27020 --replSet rs1 --dbpath C:datadata4 --bind_ip localhost

mongod --shardsvr --port 27021 --replSet rs1 --dbpath C:datadata5 --bind_ip localhost

mongod --shardsvr --port 27022 --replSet rs1 --dbpath C:datadata6 --bind_ip localhost

MongoDB ініціалізує перший сервер шардів як основний. Для зміни основного сервера шардів використовуйте метод movePrimary.

Крок 4

  • Ініціалізуйте реплікаційний набір на одному з серверів шардів.

rs.initiate( { _id : "rs0", members: [ { _id: 0, host: "IP:27020" }, { _id: 1, host: "IP:27021" }, { _id: 2, host: "IP:27022" } ] })

rs.initiate( { _id : "rs0", members: [ { _id: 0, host: "IP:27020" }, { _id: 1, host: "IP:27021" }, { _id: 2, host: "IP:27022" } ] })
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593569748, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1593569748, 1)
}

Крок 5

  • Запустіть Mongos для шардованого кластера

mongos --port 40000 --configdb rs0/localhost:27019,localhost:27018,localhost:27017

Крок 6

  • Підключіться до сервера Mongos

mongo --port 40000

  • Додайте сервери шардів.

sh.addShard( "rs1/localhost:27020,localhost:27021,localhost:27022")

sh.addShard( "rs1/localhost:27020,localhost:27021,localhost:27022")
{
        "shardAdded" : "rs1",
        "ok" : 1,
        "operationTime" : Timestamp(1593570212, 2),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570212, 2),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Крок 7

  • В оболонці mongo увімкніть шардинг для бази даних та колекцій.
  • Увімкніть шардинг для бази даних

sh.enableSharding("geekFlareDB")

sh.enableSharding("geekFlareDB")
{
        "ok" : 1,
        "operationTime" : Timestamp(1591630612, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1591630612, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Крок 8

  • Для розділення колекції необхідний ключ шардингу (описано далі в статті).

Синтаксис: sh.shardCollection("dbName.collectionName", { "key" : 1 } )

sh.shardCollection("geekFlareDB.geekFlareCollection", { "key" : 1 } )
{
        "collectionsharded" : "geekFlareDB.geekFlareCollection",
        "collectionUUID" : UUID("0d024925-e46c-472a-bf1a-13a8967e97c1"),
        "ok" : 1,
        "operationTime" : Timestamp(1593570389, 3),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570389, 3),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Якщо колекція не існує, створіть її таким чином.

db.createCollection("geekFlareCollection")
{
        "ok" : 1,
        "operationTime" : Timestamp(1593570344, 4),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570344, 5),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Крок 9

Вставте дані до колекції. Журнали MongoDB почнуть відображати активність балансувальника, що свідчить про його роботу з розподілу даних між шардами.

Крок 10

Останнім кроком є перевірка стану шардингу. Це можна зробити за допомогою команди, виконаної на вузлі Mongos.

Статус шардингу

Перевірте статус шардингу, виконавши команду на вузлі маршрутизації mongo.

sh.status()

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("5ede66c22c3262378c706d21")
  }
  shards:
        {  "_id" : "rs1",  "host" : "rs1/localhost:27020,localhost:27021,localhost:27022",  "state" : 1 }
  active mongoses:
        "4.2.7" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  5
        Last reported error:  Could not find host matching read preference { mode: "primary" } for set rs1
        Time of Reported error:  Tue Jun 09 2020 15:25:03 GMT+0530 (India Standard Time)
        Migration Results for the last 24 hours:
                No recent migrations
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1024
                        too many chunks to print, use verbose if you want to force print
        {  "_id" : "geekFlareDB",  "primary" : "rs1",  "partitioned" : true,  "version" : {  "uuid" : UUID("a770da01-1900-401e-9f34-35ce595a5d54"),  "lastMod" : 1 } }
                geekFlareDB.geekFlareCol
                        shard key: { "key" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1
                        { "key" : { "$minKey" : 1 } } -->> { "key" : { "$maxKey" : 1 } } on : rs1 Timestamp(1, 0)
                geekFlareDB.geekFlareCollection
                        shard key: { "product" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1
                        { "product" : { "$minKey" : 1 } } -->> { "product" : { "$maxKey" : 1 } } on : rs1 Timestamp(1, 0)
        {  "_id" : "test",  "primary" : "rs1",  "partitioned" : false,  "version" : {  "uuid" : UUID("fbc00f03-b5b5-4d13-9d09-259d7fdb7289"),  "lastMod" : 1 } }

mongos>

Розподіл даних

Маршрутизатор Mongos розподіляє навантаження між шардами на основі ключа шардингу. Для рівномірного розподілу даних використовується балансувальник.

Ключовим компонентом для розподілу даних між шардами є:

  • Балансувальник відіграє важливу роль у розподілі даних між шардами. Він запускається, коли Mongos починає розподіляти навантаження. Після запуску балансувальник прагне забезпечити більш рівномірний розподіл даних. Для перевірки стану балансувальника використовуйте sh.status(), sh.getBalancerState() або sh.isBalancerRunning().
mongos> sh.isBalancerRunning()
true
mongos>

АБО

mongos> sh.getBalancerState()
true
mongos>

Після вставки даних, демон Mongos відображатиме активність, пов’язану з переміщенням фрагментів даних між шардами. Це означає, що балансувальник працює для забезпечення рівномірного розподілу даних. Активний балансувальник може впливати на продуктивність, тому рекомендується його запускати у визначений проміжок часу.

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("5efbeff98a8bbb2d27231674")
  }
  shards:
        {  "_id" : "rs1",  "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022",  "state" : 1 }
        {  "_id" : "rs2",  "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",  "state" : 1 }
  active mongoses:
        "4.2.7" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  yes
        Failed balancer rounds in last 5 attempts:  5
        Last reported error:  Could not find host matching read preference { mode: "primary" } for set rs2
        Time of Reported error:  Wed Jul 01 2020 14:39:59 GMT+0530 (India Standard Time)
        Migration Results for the last 24 hours:
                1024 : Success
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs2     1024
                        too many chunks to print, use verbose if you want to force print
        {  "_id" : "geekFlareDB",  "primary" : "rs2",  "partitioned" : true,  "version" : {  "uuid" : UUID("a8b8dc5c-85b0-4481-bda1-00e53f6f35cd"),  "lastMod" : 1 } }
                geekFlareDB.geekFlareCollection
                        shard key: { "key" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs2     1
                        { "key" : { "$minKey" : 1 } } -->> { "key" : { "$maxKey" : 1 } } on : rs2 Timestamp(1, 0)
        {  "_id" : "test",  "primary" : "rs2",  "partitioned" : false,  "version" : {  "uuid" : UUID("a28d7504-1596-460e-9e09-0bdc6450028f"),  "lastMod" : 1 } }

mongos>
  • Ключ шардингу визначає логіку розподілу документів шардованої колекції між шардами. Це може бути індексоване поле або складене індексоване поле, яке повинно бути присутнє в кожному документі колекції. Дані розділяються на частини, і кожна частина пов’язується з ключем шардингу на основі діапазону. Маршрутизатор, на основі діапазону ключа, визначає, на якому шарді зберігати фрагмент даних.

Ключ шардингу обирається з огляду на п’ять основних характеристик:

  • Кардинальність
  • Розподіл записів
  • Розподіл читання
  • Ціль читання
  • Локальність читання

Оптимальний ключ шардингу дозволяє MongoDB рівномірно розподіляти навантаження між усіма шардами. Вибір правильного ключа шардингу є надзвичайно важливим.

Зображення: MongoDB

Видалення вузла шарда

Перед видаленням шарда з кластера необхідно забезпечити безпечну міграцію даних на інші шарди. MongoDB автоматично переміщує дані на інші вузли перед видаленням потрібного вузла.

Виконайте наведені нижче кроки для видалення шарда.

Крок 1

Спочатку потрібно визначити ім’я хоста шарда, який необхідно видалити. Команда нижче покаже список усіх шардів в кластері та їхній стан.

db.adminCommand( { listShards: 1 } )

mongos> db.adminCommand( { listShards: 1 } )
{
        "shards" : [
                {
                        "_id" : "rs1",
                        "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022",
                        "state" : 1
                },
                {
                        "_id" : "rs2",
                        "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1593572866, 15),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593572866, 15),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Крок 2

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

db.adminCommand( { removeShard: "shardedReplicaNodes" } )

mongos> db.adminCommand( { removeShard: "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022" } )
{
        "msg" : "draining started successfully",
        "state" : "started",
        "shard" : "rs1",
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [ ],
        "ok" : 1,
        "operationTime" : Timestamp(1593572385, 2),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593572385, 2),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Крок 3

Щоб перевірити стан видалення шарда, виконайте ту саму команду ще раз.

db.adminCommand( { removeShard: "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022" } )

Необхідно дочекатися завершення процесу зливу даних. Поля msg та state вказують на статус процесу, як показано нижче:

"msg" : "draining ongoing",
"state" : "ongoing",

Статус можна також перевірити за допомогою команди sh.status(). Після успішного видалення, шард не буде відображатись у виводі. Під час процесу зливу даних, його стан буде “draining: true”.

Крок 4

Продовжуйте перевірку стану зливу до повного видалення шарда.

Після завершення процесу, команда відобразить повідомлення та статус “completed”.

"msg" : "removeshard completed successfully",
"state" : "completed",
"shard" : "rs1",
"ok" : 1,

Крок 5

Нарешті, необхідно перевірити список шардів, що залишились в кластері, за допомогою sh.status() або db.adminCommand( { listShards: 1 } )

mongos> db.adminCommand( { listShards: 1 } )
{
        "shards" : [
                {
                        "_id" : "rs2",
                        "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1593575215, 3),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593575215, 3),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Як видно, видалений шард відсутній у списку.

Переваги шардингу над реплікацією

  • В реплікації основний вузол обробляє усі операції запису, тоді як вторинні вузли використовуються для резервного копіювання або для операцій читання. В шардингу, навантаження рівномірно розподіляється між кількома серверами, включно з реплікаційними наборами.
  • Реплікаційний набір обмежений 12 вузлами, тоді як у шардингу немає обмежень щодо кількості шардів.
  • Реплікація вимагає високопродуктивного обладнання або вертикального масштабування для обробки великих обсягів даних, що є дорогим. Шардинг дозволяє розширювати можливості системи шляхом додавання додаткових серверів.
  • Реплікація дозволяє покращити продуктивність читання шляхом додавання додаткових вторинних вузлів, тоді як у шардингу, продуктивність як читання, так і запису зростає за рахунок додавання нових вузлів.

Обмеження шардингу

  • Шардований кластер не підтримує унікальне індексування шардів, якщо ключ шардингу не є частиною унікального індексу.
  • Всі операції оновлення в шардованій колекції повинні включати ключ шардингу або поле _id у запиті.
  • Колекції можуть бути шардовані, якщо їх розмір не перевищує визначеного порогу. Цей поріг розраховується на основі середнього розміру всіх ключів шардингу та налаштованого розміру фрагментів.
  • Шардинг має обмеження щодо максимального розміру колекції або кількості розділень.
  • Неправильний вибір ключів шардингу може призвести до зниження продуктивності.

Висновок

MongoDB надає вбудовану підтримку шардингу для створення великих баз даних без зниження продуктивності. Сподіваємося, вищенаведена інформація допоможе вам налаштувати шардинг у MongoDB. Для подальшого вивчення можна ознайомитись з переліком часто використовуваних команд MongoDB.