codefest 2014. Каплуновский Б. — Использование асинхронного...

44
Использование асинхронного I/O для снижения потребления ресурсов в движке aviasales Каплуновский Борис aviasales.ru facebook.com/boris.kaplounovsky @bskaplou

Upload: codefest

Post on 09-May-2015

1.398 views

Category:

Internet


2 download

TRANSCRIPT

Page 1: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Использование асинхронного I/O для снижения потребления ресурсов в

движке aviasalesКаплуновский Борис

aviasales.ru

facebook.com/boris.kaplounovsky@bskaplou

Page 2: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Agenda

● Скриптовые языки и ресурсы● Асинхронная модель выполнения● Оптимизации и отзывчивость● Странности Tornado● Странности Python● Tornado/Python в production● И ещё пару советов по повышению

производительности...

Page 3: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Что делает движок aviasales

Page 4: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Модель памяти нативной программы

process one

stack

data

process two

code

libdl

libc

data

stack

● Одна и та-же память с исполняемым кодом используется всеми процессами

● Разделяемые библиотеки грузятся в память один раз

● Не разделяются другими процессами только сегменты данных и стек

Page 5: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Модель памяти скрипта

runtime code

libdllibc

stackstack

data data

script libs script libs

● Нативные код и библиотеки разделяются

● AST и байткод скриптовых библиотек хранятся в сегменте данных и поэтому НЕ разделяются

● Скриптовый код не так компактен как нативный и обычно занимает в разы больше памяти

code code

Page 6: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Сферический CGI Сервер в вакууме

stack

data

native code

libc

libdl

datadatadatadata data

stack stackstack stack stack stack

Page 7: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Скриптовый CGI Сервер

stackstack

data data

script libs script libs

code code

stackstack

data data

script libs script libs

code code

stackstack

data data

script libs script libs

code code

native code

libclibdl

Page 8: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Оптимизации над CGI

● fastcgi - Не порождаем отдельный процесс для каждого запроса – экономим процессорного времени на загрузку скриптов j2ee/rails/etc

● process pool - запуск и инициализация процесса до прихода запроса – снижение времени отклика

● master -Запуск родительского процесса загружающего код и делающего инициализацию. Родительский процесс порождает обработчиков клонируя себя. Процесс обрабатывающий запрос уже имеет в памяти всё необходимое. unicorn/dalvik/etc

Page 9: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Copy on write

● После вызова fork() состояние памяти и родителя и потомка одинаковые

● Делать полную копию адресного пространства при fork() расточительно

● В момент вызова fork() страницы данных родителя и потомка метятся как read-only

parent childcode

libdl

libc

data

stack

fork()

Page 10: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Copy on write

● Как только один из процесс записывает данные – операционная система делает личную копию страницы в пространстве процесса

● Страницы памяти в которые не пишут могут разделяться вечно

parent child

data

stack stack

data

clone pages

Page 11: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

master process & copy on write● После старта мастер процесс

грузит библиотеки и подготавливает всё для исполнения скрипта

● По мере необходимости мастер порождает рабочие процессы клонируя себя

● Так как в мастере уже были загружены все библиотеки дочерний процесс готов к работе мгновенно

● COW позволяет не создавать собственную копию кода в памяти

master child

stackstack

data data

script libs

code

native code

libclibdl

Page 12: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке
Page 13: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Copy on writeНЕ РАБОТАЕТ!

Page 14: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

COW не работает потому что

● GC скриптовой среды меняют данные неиспользуемых обьектов в ходе своей работы

● Скриптовые языки со счётчиками ссылок модифицируют счётчики ссылок при создании новой ссылки на обьект, даже если сам обьект неизменен

master child

stackstack

data data

scriptlibs

code

native codelibclibdl

code

scriptlibs

Page 15: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

COW не просто заставить работать

● В ruby 2.0 обещали сделать cow friendly gc. Не получилось!

● COW работает у google в dalvik, но для этого им пришлось заменить jvm на dalvik

master child

stackstack

data data

scriptlibs

code

native codelibclibdl

code

scriptlibs

Page 16: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Типичное web приложение

Значительную часть времени веб приложения ждут ответов внешних сервисов таких как

– SQL сервер

– Внешний API

– Файловый ввод вывод

Всё это время ничего не происходит!

Но память занята...

запрос ответ

logic SQL logicAPI

Page 17: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Rails приложение aviasales

● Ожидание ответа внешних API до 30 секунд

● Работа с SQL ~1 секунда

● Потребляемая память ~300mb (одним процессом)

● Разделяемая память ~4mb (код интерпретатора)

● ~300 одновременных поисков

87GB RAM/6 серверов

И вся эта память простаивала!

запрос ответ

logic SQL logicAPI

Page 18: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Синхронная модель VS Асинхронная модель

cgi worker

stack

data

code

scriptlibs

cgi worker

stack

data

code

scriptlibs

async worker

stack

scriptlibs

code

native code

libc

threaddata

threaddata

threaddata

cgi worker

stack

data

code

scriptlibs

native code

libc

Page 19: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Асинхронная модель

Минусы● Кооперативная многозадачность● Если падает процесс падают все потоки● Не для всего есть библиотеки● Отсутствие изоляции● Примитивный планировщик● Нет готовых решений

Page 20: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Асинхронная модель

Плюсы● Эффективное использование памяти● Эффективное использование памяти● Эффективное использование памяти● Эффективное использование памяти● Эффективное использование памяти

Page 21: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Почему Python

– Большое и доброе community– Обилие библиотек– Tornado живёт в python– Реклама google– Хотелось попробовать

Page 22: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Почему Tornado

– Низкий порог вхождения– Асинхронный– @gen.coroutine – отличная альтернатива

колбекам– Казался зрелым

Page 23: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Приложение на python/tornado

● Один процесс:

– занимает 267mb памяти

– из них 162mb разделяемой

– обрабатывает до 10 одновременных запросов

– больше не ждёт SQL сервер, все данные в адресном пространстве процесса

– ~ 500 одновременных исходящих соединений

– 2 сервера/8GB памяти

async worker

stack

scriptlibs

code

native codelibc

data data data

Page 24: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

При работе с tornado помни!

● Как только вы начинаете использовать синхронный IO всё останавливается

● Переключение контекста происходит ТОЛЬКО на I/O и yield внутри @gen.coroutine

● Неделимый кусок кода не должен исполняться больше XXXms (мы выбрали 100ms)

Page 25: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

При работе с tornado помни!

● Декоратор @gen.coroutine не бесплатен

● Tornado/Python приложение может умирать

● У Tornado/Python приложения может течь память

● Только профилировщик точно покажет кто ест CPU

● Python используется как клей для нативных библиотек, сложные алгоритмы на python реализовывать не надо

Page 26: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Странности Tornado

● Из коробки нет способа остановить приложение без обрыва соединений

● Есть рецепты костылей на StackOverflow

● Но этого мало – пришлось изобретать ещё костылей

Page 27: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Резольвер

www.aviasales.ru → 194.87.255.204● “Родные” резольверы операционных систем

синхронны● Для асинхронный модели исполнения нужен

асинхронный резольвер

Page 28: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Странности Tornado – Резольвер● tornado.netutil.BlockingResolver

– Используется по умолчанию– Использует синхронный getaddrinfo– Не кеширует результаты– Обращение к DNS при каждом HTTP

запросе– Пока DNS сервер не ответил всё стоит

Page 29: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Странности Tornado – Резольвер

● tornado.netutil.ThreadedResolver

– Вызывает getaddrinfo в отдельном потоке python

– Overhead на потоки: память, cpu, GIL– Работает но выглядит как костыль

Page 30: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Странности Tornado – Резольвер

● Мы написали простой асинхронный резольвер для Tornado IOLoop

– Только TCP– Только записи A и CNAME– Кеширование ответов DNS по

TTL– Большинство

преобразований делается без системных вызовов

Page 31: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Странности Tornado – HTTPClient

● HTTPClient создаёт не больше 10 исходящих соединений по умолчанию

● HTTPClient умеет стримить ответ сервера только если ответ chunked

Page 32: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Странности Tornado

● Документация зачастую избегает описывать узкие места

● Будьте готовы читать исходный код tornado чтобы понять поведение системы

Page 33: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Странности Python

● Сторонние библиотеки с нативным кодом текут и валят приложение через одну

● Найти утечку памяти в нативном коде крайне сложно

● Встроенная библиотеку xml.etree может приводить к SEGFAULT, мы используем lxml

● Сложные регулярные выражения могут остановить приложение busy-wait

Page 34: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

tornado/python в productionMONIT

– Убивает рабочие процессы если они выедают CPU

– Убивает рабочие процессы если они превысили лимит по памяти

– Стартует рабочие процессы если те умерли сами или были убиты

– Простой и удобный web интерфейс

Page 35: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

tornado/python в productionHAPROXY

– Раскидывает приходящие запросы по доступным рабочим процессам

– Балансирует нагрузку отправляя запросы к процессам с наименьшим количеством активных соединений

– Адски быстрый и простой

– Простой и удобный веб интерфейс

Page 36: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

tornado/python в productionBENCHMARKS

– Тотальное логирование времени выполения участков кода

– Визуализация бенчмарков на видном месте

– Немедленная реакция на аномалии в скорости ответов сервера

Page 37: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Что делать с ожиданием ответов SQL сервера

Page 38: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Удалённый сервер DB

request

response

worker remote db

parse request

load value

build response

recv(syscall)

send(syscall)

Page 39: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Файловое key-value хранилище

● Содержимое файла должно быть смаплено в адресное пространство процесса mmap

● Рабочий обьём должен умещаться в оперативной памяти

● База должна позволять нескольким процессам одновременно читать данные без блокировок

● Мы используем kyoto cabinet и он прекрасен

Page 40: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Быстрее чем redis и memcached

worker

load value

Плюсы● Не нужен внешний

сервер● Непревзойдённая

скорость● Не нужно переключать

контекст и делать syscall

● Высокая отказоустойчивость

Page 41: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Быстрее чем redis и memcached

worker

load value

Минусы● Медленный update

данных● Избыточность при

работе в кластере● Работает только для

небольшого кол-ва данных

Page 42: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Q&Afacebook.com/boris.kaplounovsky

@bskaplou

Page 43: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

Используйте потоковую обработку для разбора XML

● Опция streaming_callback у AsyncHTTPClient fetch позволяет получать данные по мере поступления

● Метод lxml.etree.XMLParser.feed позволяет парсить xml по кускам

● Если и это не помогает, делаем IOLoop.instance().add_timeout(time()) чтобы разбить поток исполнения

Page 44: CodeFest 2014. Каплуновский Б. — Использование асинхронного I/O для снижения потребления ресурсов в движке

tornado/python в productionПриоритеты

● У разных запросов разные требования к скорости ответ

● Рабочие процессы привязываются к одной или нескольким группа приоритета

● Haproxy отправляет запросы в соответствующую группу рабочих процессов