mongodb в продакшен - миф или реальность?
TRANSCRIPT
Алексей Токарь
руководитель группы разработки в направлении медиасервисов
MongoDB в продакшене – миф или реальность?
2
Наш самый обычный сервис – это…
• 1 500 000 000 хитов в месяц• 2 000 000 уникальных пользователей в сутки• 4 000 RPS в пике• <70мс среднее время ответа• 15 обслуживающих серверов
3
Выбор СУБД для веб-сервисов
4
Как представляют себе MongoDB
Уиииии!!!
elca
raco
l.kie
v.ua
5
А так MongoDB обычно работает…
Эмм?
care
2.co
m
6
Иногда получается даже так
:’(
redd
it.co
m
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 f03f 02 76 61 6c 75 65 00 05 00 00 00 74 65 78 7400 04 61 72 72 61 79 00 1b 00 00 00 03 30 00 1300 00 00 01 64 61 74 61 00 00 00 00 00 00 00 2c40 00 00 00
12
Структура BSON документа
документ
размер поля [] терминатор
тип ключ терминатор значение1byte
\x01 (double)1byte\x00
1byte\x00
4byte18
3byte_id
8byte1
{ _id : 1 }
13
Поиск документа на диске (skip : 2)re
ad si
ze
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; }}
17 Класс для изменения названия объекта
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 ); }}
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 ); }}
19 Простейшая реализация репозитория
public class MongoMyObjectDao { @Autowired private MongoOperations mongo;
public void save( MyObject o ) { mongo.save( o ); }}
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 ); }}
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 ); }}
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 ); }}
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
Монотонный шардирующий ключ
t
Шардируем по date, userId или orderNumВсе записи попадут на самый новый шард
40
Монотонный шардирующий ключ
t
• hash-ключ или равномерно распределенное значение
• Каждая запись на произвольный шард
проблема рассеивания (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
latencyscatter / gather problem
shard 3
{ user : 1, friend : 2 }{ user : 1, friend : 3 }{ user : 1, friend : 4 }
{ user : 2, friend : 1 }{ user : 2, friend : 4 }
{ user : 3, friend : 1 }
shard 2
shard 1
mongos 5 ms
10 ms
20 ms
• запросить данные• объединить• отсортировать
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 }Fo
llowe
rsFriends
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