MongoDB в 
продакшене – миф 
или реальность? 
Алексей Токарь 
руководитель группы разработки в 
направлении медиасервисов
2 
Наш самый обычный сервис – это… 
• 1 500 000 000 хитов в месяц 
• 2 000 000 уникальных пользователей в сутки 
• 4 000 RPS в пике 
• <70мс среднее время ответа 
• 15 обслуживающих серверов
3 
Выбор СУБД для веб-сервисов
4 
Как представляют себе MongoDB 
Уиииии!!! 
elcaracol.kiev.ua
5 
А так MongoDB обычно работает… 
Эмм? 
care2.com
6 
Иногда получается даже так 
:’( 
reddit.com
7 
Как увеличить производительность? 
помоги маленькой ламе повысить 
производительность 
bestclipartblog.com
8 
Можно вырасти вертикально 
если есть запас процессора/памяти/дисков 
bestclipartblog.com
9 
Можно вырасти горизонтально 
если в ДЦ еще остались свободные сервера 
bestclipartblog.com
Рецепты на данных 
структуры данных
11 
JSON и BSON документы 
{ 
"_id" : 1, 
"value" : "text", 
"array" : [ { 
"data" : 14 
} ] 
} 
44 00 00 00 01 5f 69 64 00 00 00 00 00 00 00 f0 
3f 02 76 61 6c 75 65 00 05 00 00 00 74 65 78 74 
00 04 61 72 72 61 79 00 1b 00 00 00 03 30 00 13 
00 00 00 01 64 61 74 61 00 00 00 00 00 00 00 2c 
40 00 00 00
12 
Структура BSON документа 
документ 
размер поля [] терминатор 
тип ключ терминатор значение 
1byte 
x01 (double) 
1byte 
x00 
1byte 
x00 
4byte 
18 
3byte 
_id 
8byte 
1 
{ _id : 1 }
13 
Поиск документа на диске (skip : 2) 
read size 
skip 
n = 3 
O(n)
14 
Индексное дерево. B-tree 
O(log n) 
wikipedia.org
Рецепты на данных 
сохранение или изменение?
16 
Доменный объект 
public class MyObject { 
private long id; 
private String title; 
... 
public long getId() { 
return id; 
} 
public String getTitle() { 
return title; 
} 
public void setTitle( String newTitle ) { 
title = newTitle; 
} 
}
public class TitleCommand extends Command { 
private String newTitle; 
private String oldTitle; 
public TitleCommand( MyObject o, String newTitle ) { 
super( o ); 
this.newTitle = newTitle; 
} 
@Override 
public void apply() { 
oldTitle = o.getTitle(); 
o.setTitle( newTitle ); 
} 
@Override 
public void undo() { 
o.setTitle( oldTitle ); 
} 
} 
17 Класс для изменения названия объекта
public class MyObjectService { 
@Autowired 
private MongoCommandsDao commandsDao; 
@Autowired 
private MongoMyObjectDao objectDao; 
public void updateTitle( MyObject o, String title ) { 
Command c = new TitleCommand( o, title ); 
c.apply(); 
commandsDao.save( c ); 
objectDao.save( o ); 
} 
} 
18 Сервис изменения заголовка объекта
public class MongoMyObjectDao { 
@Autowired 
private MongoOperations mongo; 
public void save( MyObject o ) { 
mongo.save( o ); 
} 
} 
19 Простейшая реализация репозитория
20 
Обновление объекта целиком 
• изменение всех деревьев индексов 
• блокировка всей БД на время записи 
документа 
• размещение объекта целиком на диске 
• добавление документа в oplog целиком 
– а значит еще раз запись на диск 
– передача объекта по сети для репликации
21 
public class TitleCommand extends Command { 
private String newTitle; 
private String oldTitle; 
public TitleCommand( MyObject o, String newTitle ) { 
super( o ); 
this.newTitle = newTitle; 
} 
@Override 
public Update getUpdate() { 
return Update.update( "title", newTitle ); 
} 
@Override 
public void apply() { 
oldTitle = o.getTitle(); 
o.setTitle( newTitle ); 
} 
@Override 
public void undo() { 
o.setTitle( oldTitle ); 
} 
}
public class MyObjectService { 
@Autowired 
private MongoCommandsDao commandsDao; 
@Autowired 
private MongoMyObjectDao objectDao; 
public void updateTitle( MyObject o, String title ) { 
Command c = new TitleCommand( o, title ); 
c.apply(); 
commandsDao.save( c ); 
objectDao.updateFromCommand( c ); 
} 
} 
22 Рефакторинг сервиса под новую идею
public class MongoMyObjectDao { 
@Autowired 
private MongoOperations mongo; 
public void updateFromCommand( Command c ) { 
Query query = new Query( 
Criteria.where( "_id" ) 
.is( c.getObject().getId() ) 
); 
mongo.updateFirst( 
query, 
c.getUpdate(), 
MyObject.class 
); 
} 
} 
23 Рефакторинг репозитория под новую идею
24 
Изменение поля объекта 
• изменение только одного дерева индекса 
(если поле было проиндексировано) 
• запись небольшого объема данных на диск 
• передача лишь изменяемой части 
документа по сети
25 
Результаты для 10К документов 
.save() .update() 
----------------------- 
ms Task name 
----------------------- 
47768 create 
10653 read 
40214 save 
----------------------- 
ms Task name 
----------------------- 
44667 create 
11199 read 
02472 update
Рецепты на данных 
выбор между вложенными и 
самостоятельными документами
27 
Коллекция отдельных документов 
{ 
"eventId" : 1, 
"author" : "Alice", 
"text" : "I like JavaDay 2014” 
"date" : "2014-10-17" 
} 
{ 
"eventId" : 1, 
"author" : "Bob", 
"text" : "I enjoy JavaDay 2014” 
"date" : "2014-10-18" 
}
28 
Коллекция отдельных документов 
• Плюсы: 
– если нужно вывести один документ, то поиск 
займет O(log n) времени 
– время записи документов O(1), так как он 
полностью размещается в конце файла данных
29 
Список вложенных документов 
{ 
"eventId" : 1, 
"comments" : [ { 
"author" : "Alice", 
"text" : "I like JavaDay 2014", 
"date" : "2014-10-17” 
}, { 
"author" : "Bob", 
"text" : "I enjoy JavaDay 2014", 
"date" : "2014-10-18” 
} ] 
}
30 
Список вложенных документов 
• Плюсы: 
– маленькое индексное дерево 
– меньше чтений с диска, если нужно вывести сразу 
все 
• Минусы: 
– можно достигнуть лимита документа в 16МиБ и 
все перестанет работать. Сразу. 
– …
31 
Добавление элемента в список 
• появление фрагментированного участка 
• запись объекта целиком в конец 
• перестроение индекса
Растём вертикально 
• обновляйте поля, а не документы, когда это 
возможно 
• храните документы в массивах, если это 
допустимо и они не будут значительно 
расти
Рецепты на кластерах 
репликация или шардирование?
34 
Репликация данных 
throughput 
= 400%? 
master 
replica 
replica 
replica
35 
Репликация данных 
• Плюсы: 
– растем линейно на чтение 
• Минусы: 
– нет выигрыша для операций записи 
– неизвестно какие данные получаем
36 
Шардирование 
shard1 
shard2 
. . . . . . 
shardN
37 
Шардирование 
• Плюсы: 
– растем линейно на чтение* 
– растем линейно на запись* 
– всегда актуальные данные 
• Минусы: 
– нужен правильный индекс и схема
Рецепты на кластерах 
выбор ключа и схемы для 
шардирования
39 
Монотонный шардирующий ключ 
Шардируем по date, userId или orderNum 
Все записи попадут на самый новый шард 
t
40 
Монотонный шардирующий ключ 
• hash-ключ или равномерно распределенное 
значение 
• Каждая запись на произвольный шард 
t
Рецепты на кластерах 
проблема рассеивания (scatter/gather)
42 
Граф друзей в приложении 
1 
2 
4 
3
43 
Очевидное решение 
{ user : 1, friend : 2 } 
{ user : 1, friend : 3 } 
{ user : 1, friend : 4 } 
{ user : 2, friend : 1 } 
{ user : 2, friend : 4 } 
{ user : 3, friend : 1 } 
Мои друзья: 
db.friends.find( { user : 1 } ); 
Мои фолловеры: 
db.friends.find( { friend : 1 } );
44 
Очевидное решение 
{ user : 1, friend : 2 } 
{ user : 1, friend : 3 } 
{ user : 1, friend : 4 } 
{ user : 2, friend : 1 } 
{ user : 2, friend : 4 } 
{ user : 3, friend : 1 } 
Мои друзья: 
db.friends.find( { user : 1 } ); 
Мои фолловеры: 
db.friends.find( { friend : 1 } ); 
shard 1 
shard 2 
shard 3
45 
scatter / gather problem 
latency 
shard 3 
{ user : 3, friend : 1 } 
shard 2 
{ user : 2, friend : 1 } 
{ user : 2, friend : 4 } 
shard 1 
{ user : 1, friend : 2 } 
{ user : 1, friend : 3 } 
{ user : 1, friend : 4 } 
mongos 
• запросить данные 
• объединить 
• отсортировать 
1ms 
3ms 
0ms 
total query
46 
Производительное решение 
{ user : 1, friend : 2 } 
{ user : 1, friend : 3 } 
{ user : 1, friend : 4 } 
{ user : 2, friend : 1 } 
{ user : 2, friend : 4 } 
{ user : 3, friend : 1 } 
{ user : 1, follower : 2 } 
{ user : 1, follower : 3 } 
{ user : 2, follower : 1 } 
{ user : 3, follower : 1 } 
{ user : 4, follower : 1 } 
{ user : 4, follower : 2 } 
Followers 
Friends
47 
Хороший ключ 
• высокое разнообразие значений 
• равномерное распределение по всему 
допустимому диапазону 
• попадание читающих запросов на один 
сервер
Растём горизонтально 
• для масштабирования – шардирование 
• хороший ключ для шардов не монотонен и 
разнообразен 
• дублирование может значительно повысить 
производительность
Бонус-трек
50 
writeConcern и readPreference – сила в 
балансе 
• writeConcern: 
– unacknowledged { w : 0 } 
– acknowledged { w : 1 } 
– replica-safe { w : >1 } 
– majority { w : “majority” } (n/2+1) 
• readPreference: 
– primary 
– primaryPreferred 
– secondary 
– secondaryPreferred 
– nearest
Алексей Токарь 
руководитель группы 
разработки 
azazeltap@yandex-team.ru 
Спасибо :)

MongoDB в продакшен - миф или реальность?

  • 1.
    MongoDB в продакшене– миф или реальность? Алексей Токарь руководитель группы разработки в направлении медиасервисов
  • 2.
    2 Наш самыйобычный сервис – это… • 1 500 000 000 хитов в месяц • 2 000 000 уникальных пользователей в сутки • 4 000 RPS в пике • <70мс среднее время ответа • 15 обслуживающих серверов
  • 3.
    3 Выбор СУБДдля веб-сервисов
  • 4.
    4 Как представляютсебе MongoDB Уиииии!!! elcaracol.kiev.ua
  • 5.
    5 А такMongoDB обычно работает… Эмм? care2.com
  • 6.
    6 Иногда получаетсядаже так :’( reddit.com
  • 7.
    7 Как увеличитьпроизводительность? помоги маленькой ламе повысить производительность bestclipartblog.com
  • 8.
    8 Можно вырастивертикально если есть запас процессора/памяти/дисков bestclipartblog.com
  • 9.
    9 Можно вырастигоризонтально если в ДЦ еще остались свободные сервера bestclipartblog.com
  • 10.
    Рецепты на данных структуры данных
  • 11.
    11 JSON иBSON документы { "_id" : 1, "value" : "text", "array" : [ { "data" : 14 } ] } 44 00 00 00 01 5f 69 64 00 00 00 00 00 00 00 f0 3f 02 76 61 6c 75 65 00 05 00 00 00 74 65 78 74 00 04 61 72 72 61 79 00 1b 00 00 00 03 30 00 13 00 00 00 01 64 61 74 61 00 00 00 00 00 00 00 2c 40 00 00 00
  • 12.
    12 Структура BSONдокумента документ размер поля [] терминатор тип ключ терминатор значение 1byte x01 (double) 1byte x00 1byte x00 4byte 18 3byte _id 8byte 1 { _id : 1 }
  • 13.
    13 Поиск документана диске (skip : 2) read size skip n = 3 O(n)
  • 14.
    14 Индексное дерево.B-tree O(log n) wikipedia.org
  • 15.
    Рецепты на данных сохранение или изменение?
  • 16.
    16 Доменный объект public class MyObject { private long id; private String title; ... public long getId() { return id; } public String getTitle() { return title; } public void setTitle( String newTitle ) { title = newTitle; } }
  • 17.
    public class TitleCommandextends Command { private String newTitle; private String oldTitle; public TitleCommand( MyObject o, String newTitle ) { super( o ); this.newTitle = newTitle; } @Override public void apply() { oldTitle = o.getTitle(); o.setTitle( newTitle ); } @Override public void undo() { o.setTitle( oldTitle ); } } 17 Класс для изменения названия объекта
  • 18.
    public class MyObjectService{ @Autowired private MongoCommandsDao commandsDao; @Autowired private MongoMyObjectDao objectDao; public void updateTitle( MyObject o, String title ) { Command c = new TitleCommand( o, title ); c.apply(); commandsDao.save( c ); objectDao.save( o ); } } 18 Сервис изменения заголовка объекта
  • 19.
    public class MongoMyObjectDao{ @Autowired private MongoOperations mongo; public void save( MyObject o ) { mongo.save( o ); } } 19 Простейшая реализация репозитория
  • 20.
    20 Обновление объектацеликом • изменение всех деревьев индексов • блокировка всей БД на время записи документа • размещение объекта целиком на диске • добавление документа в oplog целиком – а значит еще раз запись на диск – передача объекта по сети для репликации
  • 21.
    21 public classTitleCommand extends Command { private String newTitle; private String oldTitle; public TitleCommand( MyObject o, String newTitle ) { super( o ); this.newTitle = newTitle; } @Override public Update getUpdate() { return Update.update( "title", newTitle ); } @Override public void apply() { oldTitle = o.getTitle(); o.setTitle( newTitle ); } @Override public void undo() { o.setTitle( oldTitle ); } }
  • 22.
    public class MyObjectService{ @Autowired private MongoCommandsDao commandsDao; @Autowired private MongoMyObjectDao objectDao; public void updateTitle( MyObject o, String title ) { Command c = new TitleCommand( o, title ); c.apply(); commandsDao.save( c ); objectDao.updateFromCommand( c ); } } 22 Рефакторинг сервиса под новую идею
  • 23.
    public class MongoMyObjectDao{ @Autowired private MongoOperations mongo; public void updateFromCommand( Command c ) { Query query = new Query( Criteria.where( "_id" ) .is( c.getObject().getId() ) ); mongo.updateFirst( query, c.getUpdate(), MyObject.class ); } } 23 Рефакторинг репозитория под новую идею
  • 24.
    24 Изменение поляобъекта • изменение только одного дерева индекса (если поле было проиндексировано) • запись небольшого объема данных на диск • передача лишь изменяемой части документа по сети
  • 25.
    25 Результаты для10К документов .save() .update() ----------------------- ms Task name ----------------------- 47768 create 10653 read 40214 save ----------------------- ms Task name ----------------------- 44667 create 11199 read 02472 update
  • 26.
    Рецепты на данных выбор между вложенными и самостоятельными документами
  • 27.
    27 Коллекция отдельныхдокументов { "eventId" : 1, "author" : "Alice", "text" : "I like JavaDay 2014” "date" : "2014-10-17" } { "eventId" : 1, "author" : "Bob", "text" : "I enjoy JavaDay 2014” "date" : "2014-10-18" }
  • 28.
    28 Коллекция отдельныхдокументов • Плюсы: – если нужно вывести один документ, то поиск займет O(log n) времени – время записи документов O(1), так как он полностью размещается в конце файла данных
  • 29.
    29 Список вложенныхдокументов { "eventId" : 1, "comments" : [ { "author" : "Alice", "text" : "I like JavaDay 2014", "date" : "2014-10-17” }, { "author" : "Bob", "text" : "I enjoy JavaDay 2014", "date" : "2014-10-18” } ] }
  • 30.
    30 Список вложенныхдокументов • Плюсы: – маленькое индексное дерево – меньше чтений с диска, если нужно вывести сразу все • Минусы: – можно достигнуть лимита документа в 16МиБ и все перестанет работать. Сразу. – …
  • 31.
    31 Добавление элементав список • появление фрагментированного участка • запись объекта целиком в конец • перестроение индекса
  • 32.
    Растём вертикально •обновляйте поля, а не документы, когда это возможно • храните документы в массивах, если это допустимо и они не будут значительно расти
  • 33.
    Рецепты на кластерах репликация или шардирование?
  • 34.
    34 Репликация данных throughput = 400%? master replica replica replica
  • 35.
    35 Репликация данных • Плюсы: – растем линейно на чтение • Минусы: – нет выигрыша для операций записи – неизвестно какие данные получаем
  • 36.
    36 Шардирование shard1 shard2 . . . . . . shardN
  • 37.
    37 Шардирование •Плюсы: – растем линейно на чтение* – растем линейно на запись* – всегда актуальные данные • Минусы: – нужен правильный индекс и схема
  • 38.
    Рецепты на кластерах выбор ключа и схемы для шардирования
  • 39.
    39 Монотонный шардирующийключ Шардируем по date, userId или orderNum Все записи попадут на самый новый шард t
  • 40.
    40 Монотонный шардирующийключ • hash-ключ или равномерно распределенное значение • Каждая запись на произвольный шард t
  • 41.
    Рецепты на кластерах проблема рассеивания (scatter/gather)
  • 42.
    42 Граф друзейв приложении 1 2 4 3
  • 43.
    43 Очевидное решение { user : 1, friend : 2 } { user : 1, friend : 3 } { user : 1, friend : 4 } { user : 2, friend : 1 } { user : 2, friend : 4 } { user : 3, friend : 1 } Мои друзья: db.friends.find( { user : 1 } ); Мои фолловеры: db.friends.find( { friend : 1 } );
  • 44.
    44 Очевидное решение { user : 1, friend : 2 } { user : 1, friend : 3 } { user : 1, friend : 4 } { user : 2, friend : 1 } { user : 2, friend : 4 } { user : 3, friend : 1 } Мои друзья: db.friends.find( { user : 1 } ); Мои фолловеры: db.friends.find( { friend : 1 } ); shard 1 shard 2 shard 3
  • 45.
    45 scatter /gather problem latency shard 3 { user : 3, friend : 1 } shard 2 { user : 2, friend : 1 } { user : 2, friend : 4 } shard 1 { user : 1, friend : 2 } { user : 1, friend : 3 } { user : 1, friend : 4 } mongos • запросить данные • объединить • отсортировать 1ms 3ms 0ms total query
  • 46.
    46 Производительное решение { user : 1, friend : 2 } { user : 1, friend : 3 } { user : 1, friend : 4 } { user : 2, friend : 1 } { user : 2, friend : 4 } { user : 3, friend : 1 } { user : 1, follower : 2 } { user : 1, follower : 3 } { user : 2, follower : 1 } { user : 3, follower : 1 } { user : 4, follower : 1 } { user : 4, follower : 2 } Followers Friends
  • 47.
    47 Хороший ключ • высокое разнообразие значений • равномерное распределение по всему допустимому диапазону • попадание читающих запросов на один сервер
  • 48.
    Растём горизонтально •для масштабирования – шардирование • хороший ключ для шардов не монотонен и разнообразен • дублирование может значительно повысить производительность
  • 49.
  • 50.
    50 writeConcern иreadPreference – сила в балансе • writeConcern: – unacknowledged { w : 0 } – acknowledged { w : 1 } – replica-safe { w : >1 } – majority { w : “majority” } (n/2+1) • readPreference: – primary – primaryPreferred – secondary – secondaryPreferred – nearest
  • 51.
    Алексей Токарь руководительгруппы разработки azazeltap@yandex-team.ru Спасибо :)

Editor's Notes

  • #4 стабильность данных операции записи операции чтения
  • #21 размер нашего документа film около 10КиБ
  • #31 …но есть два существенных минуса
  • #32 дефрагментировать - дорого