MongoDB від теорії до практики. Керівництво по установці кластера mongoDB

Доброго часу доби, шановні читачі. У цьому пості я хотів би описати кілька прикладів розгортки mongoDB, відмінності між ними, принципи їх роботи. Однак найбільше хотілося б поділитися з вами практичному досвідом шардірованія mongoDB. Якби цей пост мав план, він би виглядав швидше за все так:
 
 
     
  1. Вступ. Коротко про масштабування
  2.  
  3. Деякі приклади розгортки mongoDB та їх опис
  4.  
  5. Шардінг mongoDB
  6.  
Пункти 1 і 2 — теоретичні, а номер 3 претендує на практичне керівництво з підняття кластера mongoDB і найбільше підійде тим, хто зіткнувся з цим вперше.
 
 1. Вступ. Трохи про масштабування
Уявіть собі типовий випадок — є база даних, в яку здійснюється запис і читання даних. У динамічно зростаючих системах, обсяги даних, як правило, швидко збільшуються і рано чи пізно можна зіткнутися з проблемою, коли поточних ресурсів машини буде не вистачати для нормальної роботи.
Для вирішення цієї проблеми застосовують масштабування. Масштабування буває 2-х видів — горизонтальне і вертикальне. Вертикальне масштабування — нарощування потужностей однієї машини — додавання CPU, RAM, HDD. Горизонтальне масштабування — додавання нових машин до існуючих і розподіл даних між ними. Перший випадок найбільш простий, тому що не вимагає додаткових налаштувань програми та будь-якої додаткової конфігурації БД, але його недолік в тому, що потужності однієї машини теоретично рано чи пізно упрутся в глухий кут. Другий випадок складніший у конфігурації, але має ряд переваг:
 
     
  • Теоретично нескінченне масштабування (машин можна поставити скільки завгодно)
  •  
  • Велика безпеку даних (тільки при використанні реплікації) — машини можуть розташовуватися в різних дата центрах (при падінні однією з них, залишаться інші)
  •  
 
 2. Деякі приклади розгортки mongoDB
 
 

1. Найпростіша схема, без шардірованія

 
 
 
У даній схемі все просто — є додаток, який через драйвер спілкується з mongod. mongod — це основний процес mongoDB, завдання якого — прийом запитів, їх обробка і виконання. Дані в mongod зберігаються в так званих chunks (чанкі). Кожен chunk має розмір "chunksize", який за замовчуванням 64 MB. Фізично чанкі зберігаються у файлах dbName.n, де n — порядковий номер, починаючи з 0. При досягненні розмірів 64 MB (або іншого chunksize) chunk ділиться навпіл, виходить 2 чанка поменше — по 32 MB, ці 2 чанка починають наповнюватися, поки не досягнуть розміру chunksize, потім поділ відбувається знову і т.д. Розмір файлу dbName.0 дорівнює 64 MB, dbName.1 — 128 MB, dbName.2 — 256 і т.д. до 2Gb. У міру зростання кількості та розміру чанків ці файли наповнюються і коли є перший файл — dbName.5, розмір якого дорівнює 2 Gb, зростання розміру припиняється і mongoDB просто створює файли одного і того ж розміру. Так само слід зазначити, що mongoDB не просто створює ці файли в міру необхідності, а створює їх заздалегідь, щоб при настанні необхідності реально записати дані у файл, не витрачати час на створення файлу. Тому при відносно невеликому розмірі реальних даних, ви зможете виявити, що місця на жорсткому диску зайнято пристойно.
Дана схема застосовна найчастіше для локального тестування.
 
 

2. Шардірованная схема без репліка сетів

 
 
 
У даній схемі з'являються нові елементи. Схема називається шардірованной. Важлива відмінність шардірованной схеми — у неї дані не просто записуються в чанкі, які потім діляться навпіл, а потрапляють в них по певного діапазону заданого поля — shard key. Спочатку створюється всього один чанк і діапазон значень, які він приймає, лежить в межах (- ∞, + ∞):
 
 
 
Коли розмір цього чанка досягає chunksize, mongos оцінює значення всіх шардкеев всередині чанка і ділить чанк таким чином, щоб дані були розділені приблизно порівну. Для прикладу припустимо, що у нас є 4 документа з полями name, age і id. id — є shard key:
 
 
{“name”: “Max”, “age”: 23, “id”: 23}
{“name”: “John”, “age”: 28, “id”: 15}
{“name”: “Nick”, “age”: 19, “id”:56}
{“name”: “Carl”, “age”: 19, “id”: 78}

Припустимо розмір chunksize, вже досягнутий. У даному випадку Mongos розділить діапазон приблизно так (-, 45]; (45, +). У нас вийде 2 чанка:
 
 
 
При появі нових документів, вони будуть записуватися в чанк, який відповідає діапазону shardKey. По досягненню chunksize поділ відбудеться знову і діапазон буде ще вже і так далі. Всі чанкі зберігаються на Шардена.
Важливо зауважити, що при досягненні яких-небудь чанк неподільного діапазону, наприклад (44, 45], поділ відбуватися не буде і чанк зростатиме понад chunksize. Тому слід уважно вибирати shard key, щоб це була як можна найбільш випадкова величина. Наприклад, якщо нам треба заповнити БД усіма людьми на планеті, то вдалими виборами shard key були б номер телефону, ідентифікаційний податковий номер, поштовий індекс. Невдалими — ім'я, місто проживання.
У схемі ми можемо бачити config сервер, його відмінність від mongod в тому, що він не обробляє клієнтські запити, а є сховищем метаданих — він знає фізичні адреси всіх chunk-ів, знає який chunk, на якому Шарден шукати і який діапазон у того чи іншого шарда. Всі ці дані він зберігає в спеціально відведеному місці — config database.
У даній схемі також прісутвует роутер запитів — mongos, на нього покладаються такі завдання:
 
     
  1. Кешування даних, що зберігаються на config сервері
  2.  
  3. Роутінг запитів читання і запису від драйвера — маршрутизація запитів від додатків на потрібні Шардена, mongos точно знає де фізично знаходиться той чи інший чанк
  4.  
  5. Запуск фонового процесу "балансер"
  6.  
Функція балансер полягає в міграції чанків з одного шарда на інший. Процес відбувається приблизно так: балансер відсилає команду moveChunk на шард, з якого буде мігрувати chunk, шард отримуючи цю команду, запускає процес копіювання чанка на другий шард. Після того, як всі документи скопійовані, відбувається синхронізація документів між цими 2-ма чанк, т.к. поки відбувалася міграція, у вихідний чанк могли додатися нові дані. Після закінчення синхронізації шард, який прийняв новий чанк, відправляє його адресу config серверу, щоб той, у свою чергу, оновив його в кеші монгоса. По закінченню цього процесу, якщо на вихідному чанк немає відкритих курсорів, він видаляється.
Дана схема часто має місце в тестовій середовищі великих додатків, а при використанні 3-х config серверів, може підійти для невеликих продакшен додатків. 3 config сервера забезпечують надмірність даних і якщо впаде один, mongos все одно буде отримувати актуальні адреси чанків від інших config серверів.
 
 

3. Шардірованная схема з репліка сетами

 
 
 
У даній схемі крім шардірованія присутній реплікація Шардена. Кілька слів про це. Всі операції запису, видалення, оновлення, потрапляють у майстер (primary), а потім записуються в спеціальну колекцію oplog, звідки асинхронно потрапляють на репліки — repl.1 і repl.2 (secondary). Таким чином відбувається дублювання даних. Навіщо це потрібно?
 
     
  • Надмірність забезпечує безпеку даних — при падінні майстра, відбувається голосування між репліками і одна з них стає майстром
  •  
  • Майстер і репліки можуть располагаютя в різних дата центрах — це може виявитися корисним, якщо сервер пошкоджується фізично (пожежа в дата центрі)
  •  
  • Репліки можуть використовуватися для більш ефективного читання даних. Наприклад, є додаток, що має клієнтську аудиторію в Європі і в США. Одну з реплік можна помістити на території США і налаштувати так, щоб клієнти з США вичитували дані саме з неї. Варто відзначити, що документи на репліки потрапляють з запізненням і не завжди відразу вдається знайти на репліці знову записаний документ. Тому даний пункт є перевагою, тільки якщо читання з реплік дозволяє логіка додатки
  •  
Схема з репліка сетами найчастіше має місце в серйозних продакшен додатках, де важлива збереження даних або має місце велика кількість читань і логіка програми дозволяє читати з реплік.
Детальніше на даній схемі зупинятися не будемо, тому їй можна присвятити окремий пост.
 
 3. Шардірованіе
Отже, приступимо. Розгортати все це будемо локально на linux ubuntu 12.0. Для проведення всього цього нам знадобиться встановлена ​​mongoDB, у мене версія 2.4.9.
Шардіровать будемо схему № 2, тільки приберемо з неї елементи, що мають суто теоретичне значення:
 
 
 
Відкриємо всім нам звичний shell, бажано відразу кілька вкладок, т.к. mongos, mongod, config server — це все окремі процеси. Далі по пунктах:
 
 
     
  1. Створимо 2 порожні директорії, в яких будуть зберігатися дані:
     
    > sudo mkdir /data/instance1
    > sudo mkdir /data/instance2
    

     Піднімаємо 2 інстанси mongod командами:
     
    > sudo mongod --dbpath /data/instance1 --port 27000  //для первого инстанса
    

    А в наступному терміналі:
     
    > sudo mongod --dbpath /data/instance2 --port 27001  //для второго
    

    Параметр — dbpath вказує шлях, по якому будуть зберігатися файли .0, .1, .2 і. Ns. У файлах .0, .1, .2 і т.д. зберігаються самі дані даного інстанси в бінарному вигляді, а у файлі. ns — простір імен, необхідне для навігації по БД. — port — порт, по якому буде доступний об'єкт БД.
    Після першого пункту у нас є два інстанси:
     
     
  2.  
  3. Створимо порожню директорію, в якій зберігатимуться дані config сервера:
     
    > sudo mkdir /data/config
    

     Піднімаємо конфіг сервер, командою
     
    > sudo mongod --configsvr --dbpath /data/config --port 27002
    

    Параметр — configsvr вказує, що новий інстанси буде саме конфіг сервером, — dbpath — шлях, по якому будуть зберігатися дані. Після другого пункту картина виглядає так (зверну вашу увагу, що поки ці сутності нічого не знають один про одного):
     
     
  4.  
  5. Піднімаємо mongos, командою
     
    > sudo mongos --configdb 127.0.0.1:27002 --port 27100
    

    За цією командою піднімається mongos на порту 27100 , на вхід йому потрібно передати перелік конфіг серверів з їх хостами, на які він буде звертатися. Якщо ми при піднятті монгоса не вказали порт, то він використовує за замовчуванням 27017 (якщо він не зайнятий). Після підняття монгоса:
     
     
  6.  
  7. Пріконнектімся до монгосу, вказавши порт, на якому ми його піднімали, командою
     
    > mongo --port 27100
    

    Після цього отримаємо:
     
     
  8.  
  9. Залишився фінальний крок — додаємо наші Шарден у кластер
     
    > sh.addShard("127.0.0.1:27000")
    > sh.addShard("127.0.0.1:27001")
    

    Ці 2 команди потрібно виконати на монгосе, коннекшен до якого був відкритий в пункті 4. Командою db.printShardingStatus () можна переглянути статус шардінга. Переконаємося, що Шарден додані, в терміналі ми повинні побачити щось подібне:
     
     
    
    --- Sharding Status --- 
      sharding version: {
    	"_id" : 1,
    	"version" : 3,
    	"minCompatibleVersion" : 3,
    	"currentVersion" : 4,
    	"clusterId" : ObjectId("53317aefca1ba9ba232b949e")
    }
      shards:
    	{  "_id" : "shard0000",  "host" : "127.0.0.1:27000" }
    	{  "_id" : "shard0001",  "host" : "127.0.0.1:27001" }
      databases:
    	{  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    	{  "_id" : "test",  "partitioned" : false,  "primary" : "shard0000" }
    

     
    Маємо фінальну картину:
     
     
  10.  
 
Тепер давайте переконаємося, що все, що ми налаштували працює, а саме — дані формуються в чанкі, а балансер розкидає їх по Шардам. Щоб дані почали шардіроваться, необхідно дозволити шардірованіе на потрібній базі даних, а потім і на колекції.
Всі нижчезазначені команди необхідно виконувати під монгосом. Нехай наша БД називається bank , виконаємо команди, які дозволять її шардіровать:
 
> use admin
> sh.enableSharding("bank")

Ще раз виконаємо команду db.printShardingStatus () . Висновок повинен бути приблизно таким:
 
databases:
	{  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
	{  "_id" : "test",  "partitioned" : false,  "primary" : "shard0000" }
	{  "_id" : "bank",  "partitioned" : true,  "primary" : "shard0000" }

 
Як бачимо, навпаки partitioned з'явилося true , значить ми на вірному шляху.
 
Тепер давайте попрацюємо з нашої БД:
 
> use bank  //переключаемся в базу bank (если не существует, то будет создана)

> db.tickets.insert({name: “Max”, amount: Math.random()*100}) //инсертим первую запись в коллекцию tickets (если не существует, то будет создана при первом инсерте)

> for (var i=0; i<2100000; i++) { db.tickets.insert({name: “Max”, amount: Math.random()*100}) }  //генерим остальные записи при помощи javascript

> db.tickets.ensureIndex({amount: 1})  //устанавливаем индекс, который будет нам служить в качестве shard Key

> db.tickets.stats()  //проверяем, что записи успешно добавились. Следует отметить, что флаг sharded установлен в false, поэтому пока все записи добавлены в primaryShard

> use admin  //переключаемся в админ, чтобы разрешить шардирование коллекции tickets. До этого момента все данные хранятся на одном (primary) шарде

> db.runCommand({shardCollection: "bank.tickets", key: {amount: 1}})    //указываем шардируемую коллекцию, а вторым параметром - shard key для нее. 

 
Після останній команди ми повинні побачити приблизно таке:
 
 
{ "collectionsharded" : "bank.tickets", "ok" : 1 }

Після всього цього виконаємо команду sh.status (true) або db.printShardingStatus () , щоб переконатися, що все запрацювало і, якщо все зроблено вірно, ми повинні побачити наступну картину:
 
 
 
Як бачимо, дані розподілені нерівномірно, але якщо трохи почекати і повторити команду db.printShardingStatus () , то картина змінюється в бік рівномірного розподілу:
 
 
 
 
 
І фінальна картина:
 
 
 
Як ми побачили, спочатку чанкі зберігаються на primary шард, а потім мігрують на другий шард, поки кількість не вирівняється, причому їх діапазони при цьому теж можуть змінюватися.
 
У майбутньому хотів би розповісти про організацію пам'яті в mongoDB і про реплікацію. Спасибі за увагу.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.