РИТ++ 2017, HighLoad Junior
Зал Сингапур, 5 июня, 11:00
Тезисы:
http://junior.highload.ru/2017/abstracts/2683.html
Наш доклад описывает способ использования больших объемов памяти, которые стали доступны в последние годы. К сожалению, эта память обычно остается незадействованной в управляемых средах исполнения в связи с принудительной сборкой мусора. Разработчики прибегают к внешним хранилищам данных ( i.e Memcached), что несет дополнительные расходы.
...
BigMemory - работа с сотнями миллионов бизнес-объектов / Дмитрий Хмаладзе (Agnicore)
1. Managed Big Memory
Big Memory = использование больших объемов
RAM в прикладных целях
@agnicore, @itadapter
Dmitriy Khmaladze, Oleg Panagushin
2. О нас
● Мы - Agnicore, Нью-Йорк, США
● Всё началось с IT Adapter - занимались разработкой и поддержкой
систем высоконагруженных систем более 10 лет.
● Начинали в 90х на нативных решениях C++, Delphi; С начала
двухтысячных больше заказов систем в управляемых средах
исполнения
● Компания Agnicore сформировалась на базе разработок IT Adapter с
целью систематизации ноу-хау в виде готовых решений
2
3. Фокус
● 64 bit Managed Environments / Server Platforms
● Обработка данных в памяти используя нативную модель - миллионы domain
объектов в процессе - хранящихся длительное время
● Managed Code Runtimes - каждый имеет свою нативную модель объектов
(arrays, maps, properties/fields, value types, readonly etc.)
● Попытаемся остаться в рамках процесса - без IPC
● Опишем методологию off-heap с прозрачной сериализацией, результат
применения которой неочевиден без проведения детальных исследований
● То, о чем пойдет речь, можно использовать на практике 3
4. Stateless Systems?
● Какой state: session, application, биржевой, бизнес транзакции,
социальный (группа, пользователи), товары?
● Как часто меняется state: user session, биржа, социальная
группа/комната, ценовые политики товаров?
● State есть. Вопрос: где он хранится в долгосрочной и операционной
перспективе?
● Операционные данные лучше хранить в RAM, поскольку это очень
быстро и доступно - Big Memory
4
5. Применение Big Memory
Кэш
● Для хранения горячих
объектов
● Собираемых из разных
узлов системы
● Для более быстрого
отклика
5
6. Применение Big Memory
Обход графов
Объекты хранятся долго и
могут меняться часто
● Социальные сети
(изначальная задача)
● Построение связей
● Поиск на графе
● Построение
семантических сетей
6
7. Применение Big Memory
Поточная обработка
● Обработка данных из
множества источников
● Буферизация данных
● Сопоставление потока
данных с массивом
образцов
7
8. Применение Big Memory
Нужна детерминированная пропускная способность
Кэш
● Для хранения горячих
объектов
● Собираемых из разных
узлов кластера
● Для более быстрого
отклика
Обход графов
● Построение связей
● Поиск на графе
● Траверс больших графов
● Построение нейронных
сетей
Поточная обработка
● Обработка данных из
множества источников
● Буферизация данных
● Сопоставление поточных
данных с массивом
образцов
8
9. Обзор рынка - Системы In-Memory обработки
● Hazelcast (Java)
● Apache Ignite / GridGain (Java)
● GigaSpaces (Java)
● Terracotta (Java)
● Redis, MongoDB (MMF) (C/C++)
● NFX Pile (.NET/CLR) Native Objects
9
Systems provide: off-heap (in-process) and distributed (off-process) data
structures Lists, HashMaps, Queues + transactions/atomic:
distributed
in-Memory DB
+ data
structures
10. Локальная память не используется!
● Использовать много памяти в прикладных приложениях не принято
● Обычно полагаются на внешние хранилища
● Внешние = задержки, копирование данных, context switching, mapping
моделей
10
NIC
NIC
CLR/JVM
Serialization
13. Как столкнуться с проблемой сборки мусора?
● Не нужны никакие специальные тулзы
● Берем обычный reference-тип, например, User
● Берем List<User> и наполняем несколькими десятками миллионов экземпляров
● Можно в одном потоке, можно в разных
● Приложение начинает подтормаживать при любой последующей нагрузке,
которая аллокирует новые объекты в памяти - например, обрабатывает обычные
Web запросы
13
14. Managed Runtimes Big Memory GC Problems
● Несколько миллионов объектов…
… приводят к подтормаживаниям процесса
● Чем больше физической памяти…
… тем ситуация хуже
● Решения оптимизации GC…
… не являются панацеей
● Обработка событий сборки мусора…
… ведет к неравномерному распределению нагрузки
14
15. На заметку
● Задержки GC пропорциональны количеству и запутанности объектов и
количеству ссылок на каждый объект который двигается
● Дефрагментация дырок памяти - реальный убийца = stop all
● Чем больше доступной физической памяти -
тем реже задействуется полный GC
● Чем меньше объекты тем больше их количество в памяти - см. №1
15
16. На заметку - формат памяти
● Строка из 5 букв занимает > 20 байт на x64
● Каждый объект имеет заголовок 16 байт (16 байт на x64 CLR), т.е.
String[1000] пустых строк ~ 24 килобайта
● JIT производит выравнивание полей для увеличения скорости доступа
● Объект состоящий из нескольких строк и чисел может занять около 100 байт!
16
INT 64
INT 32 UNUSED
String reference
Object Header
17. Борьба с GC
● Встроенный механизм поколений GC неэффективен поскольку объекты могут
быть долгоживущими
● Pooling - предаллокация и переиспользование объектов
● Выход в неуправляемый код - hello C++
● Ручное распределение :
○ Выделение кусочков byte[]
○ Специализированные сериализаторы для конкретных классов/структур
● Переключение трафика между узлами по уведомлению о приближающемся GC
Разные режимы GC не универсальны для любых задач
17
19. Тестируем CLR
● https://github.com/aumcode/piledemo/tree/master/SocialTrading
● Более 40M объектов невозможно протестировать в относительно
реальном времени.
● Пробовали background, server, SustainedLatencyMode etc.
● Аллокация: 10 потоков из 12 - стабильно 100% CPU
● Аллокация: 6 потоков из 12 в пиках доходят до 90+% CPU
● Jitter - притормаживание процесса
○ На 10М объектов - 700ms раз в секунду
○ На 40М объектов - 2.4s раз в полсекунды
○ На 60М объектов - невозможно измерить в реальном времени
19
20. Проблемы Big Memory на примере .NET
20
Машина: I7 3.2ghz 6 core, 64 Gb DDR3; Server GC
23. In-process
Неуправляемый буфер - Ограничения типов
● требуется маршалинг
● требуются специально подготовленные модели данных / DTO
● нет полиморфизма и графов объектов
● трудно портировать
● тяжело поддерживать
23
CLR/JVM
UNSAFE
24. In-process
Сериализация
Будет ли решение
основанное на
сериализации работать
приемлемо быстро?
Следует заметить, что
решения out-process =
сериализация
Неуправляемый буфер
● требуется маршалинг
● требуются DTO
● нет полиморфизма и
графов объектов
● трудно портировать
● тяжело поддерживать
Ограничения типов
● Пример: пулы, value типы
● требуются DTO
● нет полиморфизма и
графов объектов
● тяжело поддерживать
24
25. Сериализация
● Protocol Buffers (Binary)
○ Большая скорость
○ Нет полиморфизма
○ Требует отдельной схемы или мета разметки
● Cap’n Proto (Zero-Copy Binary)
○ Zero-copy сериализатор применим к ограниченному классу задач
○ Большая скорость
○ Вообще не позволяет работать с нативным CLR объектами
○ Время сериализации переносится во время доступа к полям
● BinaryFormatter (.NET)
○ Поддерживает любой CLR объект, но медленно и большой перерасход памяти
● Сериализация в текст (JSON, XML, YAML) (Textual)
○ Очень медленно, большая нагрузка на сборщик мусора
25
29. Slim Сериализатор
+ В 5-10 раз быстрее
BinaryFormatter
+ 10-50% плотнее упаковывает
данные чем нативные CLR
+ Поддерживается полиморфизм,
readonly поля, ISerializable и т.д.
+ Не нужны мета атрибуты и
специализированные схемы
- Не поддерживается
версионность
- Не поддерживаются делегаты
29
Телепортация объектов сложных типов:
Dictionary<Patient, Policy<Medical>>
List<Policy<Employment>>
Policy<TDoc> : IEnumerable<TDoc>
etc..
30. Решение - Большая Куча (NFX.Pile)
● Диспетчер памяти
● На 100% реализован в управляемом коде (без C/C++)
● Внутренне хранит в массивах байт - сегментами - byte[]
● Опционально зеркалирование сегментов в MMF
● Храним: string, byte[], object
● Для сериализации object используется Slim
30
32. var data = new Person
{LastName= ”Wiseman”, Age=25 …};
var ptr = pile.Put(data);
…
var got = pile.Get( ptr) as Person;
…
got.LastName....
got.Age....
…
pile.Delete(ptr);
Использование Pile
32
Value Type - GC не видит
PilePointer[10123] = 1 reference, not 10K!
ID узла
распределенного
кластера (optional)
33. var obj1 = new Payload{ID = 1, Name = "1", Data = null};
var p1 = pile.Put(obj1, preallocateBlockSize: 4000);
pile.SizeOf(p1);// 4000
var obj2 = new Payload{ID = 2, Name = "2", Data =
new byte []{1,2,3,4,5,6,7,8}};
pile.Put(p1, obj2);
var got = pile.Get(p1) as Payload;
……
got.Name…
…
pile.Delete(p1);
Использование Pile - In-Place Mutation
33
Резервируем больше памяти
(необязательно)
Пишем объект по
существующему поинтеру
Удаляем поинтер и все, на что
он указывает
Если новый payload не помещается в существующий блок, то
Pile создаст внутренний alias на новый блок
35. Устройство Pile
● Сериализуем CLR объект в массив байт
○ Сериализатор динамически адаптируется и кэширует новые типы
● Ищем свободную область нужного размера
○ Перебираем сегменты и анализируем статистику - оптимизируя локинг многопоточности
○ Если есть место, записываем согласно дескриптору и обновляем его
○ Иначе выделяем новый сегмент
● Обеспечивается потоковая безопасность
○ Используется легковесный Reader/Writer Spin-Lock адаптивный к количеству
процессоров
○ В момент аллокации сегменты перебираются непересекающимися окнами с целью
предотвращения lock contention
35
36. О фрагментации
● Фрагментация появляется при удалении объектов.
● Pile не двигает задействованные блоки (no compaction). Compaction -
основная причина stop-all GC
● Pile периодически (в зависимости от статистики) проползает (crawl)
каждый сегмент, объединяя маленькие прилегающие блоки в большие.
● Фрагментация Pile незначительно влияет на общую производительность,
поскольку Pile - решение более высокого уровня, локальность доступа
важна в момент материализации byte[] в CLR объект.
● Pile обычно используется в random-access режиме (например, поиск по
ключам)
36
50. В итоге Pile:
+ Позволяет хранить больше объектов в том же объеме памяти
○ Объекты сжимаются Slim сериализатором
+ Обеспечивает детерминированную пропускную способность
○ Устраняются “паузы”, вызванные сборщиком мусора
+ После десериализации возвращает копию объекта
○ Это удобно в случае многопоточного программирования
+ На основе Pile реализуются более высокоуровневые абстракции,
например: Cache, Dictionary, Tree, LinkedList и т.д.
50
51. Pile Кэш - практическое применение BigMemory
● LocalCache - реализация in-process кэш сервера
○ В режиме Durable: работает как обычный dictionary
○ Поддерживает именованные таблицы
○ Приоритизацию объектов
○ Максимальный TTL или абсолютный expiration
○ Поддерживает детальную настройку значений емкости, LWM,
коэффициент расширения/сжатия для каждой таблицы
● Используется для мемоизации
доступа к данным БД
● Используется как обычный “большой” hashmap
51
52. Использование Pile - Cache
52
var userData = new SocialUser{.....};
var tbl = cache.GetOrCreateTable<SocialID>(“users”);
…
tbl.Put(userData.SocialID, userData);
…
var requestedId = new SocialID(IDType.Twitter, 42376472343);
var ageSec = 120;
var user = tbl.Get(requestedId, maxAgeSec: ageSec) as SocialUser;
…
var removed = tbl.Remove(requestedId);
…
Нет никаких поинтеров,
только предметная область
54. Заключение
● Мы исследовали подход к хранению большого количества объектов предметной
области in-process путем сериализации объектов в managed byte[], не задействуя
C++/внешние хранилища
● Ценность - не требуется коренного изменения бизнес модели данных
● Решение managed диспетчера памяти (Pile) позволяет:
○ Хранить резидентно в локальной памяти более миллиарда домен объектов, адресуясь к
ним по простым ключам через Кэш
○ Сжать managed footprint объектов на 10-50% (благодаря сериализации)
○ Гарантировать паузы GC < 50ms (при 128GB heap 1.5B объектов по 71 байту)
○ Обеспечить высокую скорость записи/чтения объектов (сотни тысяч простых
экземпляров в секунду на поток)
○ Проектировать высокоуровневые структуры данных для решения специализированных
задач: семантические сети, социальные графы, деревья и т.д.
54