Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Производительность, читаемость, скорость разработки. Как Python помог нам усидеть на трех стульях.

Производительность, читаемость, скорость разработки. Как Python помог нам усидеть на трех стульях.

Павел Смирнов (Яндекс) @ Moscow Python Meetup 55

"Мы расскажем о том, как построили продакшн процесс статистических расчетов над большими данными на языке python. Как мы смогли добиться хорошей производительности, читабельности и повысить скорость разработки новых метрик. Покажем, как происходила эволюция наших инструментов и как написание кода на С++ уступало лаконичным Python скриптам".

Видео: http://www.moscowpython.ru/meetup/55/productivity-readability-speed/

Moscow Python Meetup

April 24, 2018
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. › App Метрика › Web Метрика › Стандартные отчеты на

    внутренних сервисах Инструменты анализа базовых показателей 5
  2. › Какой эффект от рекламы по телевизору? › Есть два

    продукта с похожим контентом, мешает ли их реклама друг другу? Анализ сложных систем 8
  3. › Какой эффект от рекламы по телевизору? › Есть два

    продукта с похожим контентом, мешает ли их реклама друг другу? › Какие пользователи реже пользуются сервисом и почему? Анализ сложных систем 9
  4. › Какой эффект от рекламы по телевизору? › Есть два

    продукта с похожим контентом, мешает ли их реклама друг другу? › Какие пользователи реже пользуются сервисом и почему? › Какая объективная польза от разных приложений и сервисов? Какие из них надо развивать? Анализ сложных систем 10
  5. › Какой эффект от рекламы по телевизору? › Есть два

    продукта с похожим контентом, мешает ли их реклама друг другу? › Какие пользователи реже пользуются сервисом и почему? › Какая объективная польза от разных приложений и сервисов? Какие из них надо развивать? › И т.д. Анализ сложных систем 11
  6. Источником данных являются логи сервисов и приложений. Наши сервисы пишут

    более 700 различных логов. Объемами от нескольких МБ до сотен Тб в сутки. Источники данных 12
  7. › Конструкторы расчета показателей в YT › SQL-like запросы к

    данным на YT › Написание кода на языках программирования Инструменты обработки данных 14
  8. Плюсы: › Решение задач любой сложности › Скорость написания кода

    › Скорость решения всей задачи зависит только от одного человека Аналитик 20
  9. Плюсы: › Решение задач любой сложности › Скорость написания кода

    › Скорость решения всей задачи зависит только от одного человека › Читабельность кода Аналитик 21
  10. Минусы: › Неоптимальный код › Медленный язык программирования › "Велосипеды"

    и различия в коде с одинаковой функциональностью у нескольких аналитиков Аналитик 25
  11. Входные данные 35 Server name Url search_1 https://www.yandex.ru/search/?text=ускорение%20свободного%20падения search_2 https://www.yandex.ru/search/touch/?text=Котёнок%20милый%20фото

    search_1 https://www.yandex.ru/search/?text=Как%20приготовить%20сырники%20дома search_2 https://www.yandex.ru/tech/api/?utt=132 search_3 https://www.yandex.ru/tech/api/?utt=213 Логи доступа к сервисам поиска
  12. Подготовка данных для анализа Структура решения задачи 36 Фильтрация ненужных

    записей Извлечение нужных полей из записи Преобразование полей Бизнес логика задачи
  13. Подготовка данных для анализа Структура решения задачи 37 Фильтрация ненужных

    записей Извлечение нужных полей из записи Преобразование полей Бизнес логика задачи 75% времени расчета 25% времени расчета
  14. Входные данные 39 Server name Url search_1 https://www.yandex.ru/search/?text=ускорение%20свободного%20падения search_2 https://www.yandex.ru/search/touch/?text=Котёнок%20милый%20фото

    search_1 https://www.yandex.ru/search/?text=Как%20приготовить%20сырники%20дома search_2 https://www.yandex.ru/tech/api/?utt=132 search_3 https://www.yandex.ru/tech/api/?utt=213 Логи доступа к сервисам поиска
  15. Входные данные 40 Server name Url search_1 https://www.yandex.ru/search/?text=ускорение%20свободного%20падения search_2 https://www.yandex.ru/search/touch/?text=Котёнок%20милый%20фото

    search_1 https://www.yandex.ru/search/?text=Как%20приготовить%20сырники%20дома search_2 https://www.yandex.ru/tech/api/?utt=132 search_3 https://www.yandex.ru/tech/api/?utt=213 Логи доступа к сервисам поиска
  16. Входные данные 41 Server name Url search_1 https://www.yandex.ru/search/?text=ускорение%20свободного%20падения search_2 https://www.yandex.ru/search/touch/?text=Котёнок%20милый%20фото

    search_1 https://www.yandex.ru/search/?text=Как%20приготовить%20сырники%20дома search_2 https://www.yandex.ru/tech/api/?utt=132 search_3 https://www.yandex.ru/tech/api/?utt=213 Логи доступа к сервисам поиска
  17. Входные данные 42 Server name Url search_1 https://www.yandex.ru/search/?text=ускорение%20свободного%20падения search_2 https://www.yandex.ru/search/touch/?text=Котёнок%20милый%20фото

    search_1 https://www.yandex.ru/search/?text=Как%20приготовить%20сырники%20дома search_2 https://www.yandex.ru/tech/api/?utt=132 search_3 https://www.yandex.ru/tech/api/?utt=213 Логи доступа к сервисам поиска
  18. Входные данные 43 Server name Url search_1 https://www.yandex.ru/search/?text=ускорение%20свободного%20падения search_2 https://www.yandex.ru/search/touch/?text=Котёнок%20милый%20фото

    search_1 https://www.yandex.ru/search/?text=Как%20приготовить%20сырники%20дома search_2 https://www.yandex.ru/tech/api/?utt=132 search_3 https://www.yandex.ru/tech/api/?utt=213 Логи доступа к сервисам поиска
  19. Входные данные 44 Server name Url search_1 https://www.yandex.ru/search/?text=ускорение%20свободного%20падения search_2 https://www.yandex.ru/search/touch/?text=Котёнок%20милый%20фото

    search_1 https://www.yandex.ru/search/?text=Как%20приготовить%20сырники%20дома search_2 https://www.yandex.ru/tech/api/?utt=132 search_3 https://www.yandex.ru/tech/api/?utt=213 Логи доступа к сервисам поиска
  20. Дерево разбора Log line Url Server name Parsed url Protocol

    Host Page Params Parsed params Text Other params… Other params… Other params… 50
  21. Дерево разбора Log line Url Server name Parsed url Protocol

    Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 51
  22. Вызов функции qb = QB( log='access-log', fields=['decoded_text', 'server_name', 'page'], filters=[qbf.defined('decoded_text')]

    ) record = qb.prepare(log_line) if record not is None: yield record.decoded_text, record.server_name, record.page
  23. Желаемая функция обработки def prepare_one_line(log_line): url = get_log_field('url', log_line) server_name

    = get_log_field(‘server_name’, log_line) parsed_url = parse_url(url) page = get_from_dict('page', parsed_url) params = get_from_dict('params', parsed_url) parsed_params = parse_url_params(params) text = get_from_dict('text', parsed_params) decoded_text = url_decode(text) if decoded_text is None: return None return Record(decoded_text=text, server_name=server_name, page=page)
  24. Abstract Syntax Tree - дерево объектов языка Python Оно формируется

    путем анализа кода на Python, а используется компилятором для формирования байт-кода. AST 56
  25. Инициализация объекта qb QB 57 Название дерева разбора Список полей

    для извлечения и описания дополнительных веток Список фильтраций QB AST Python AST Функция обработки
  26. Дерево разбора лога Log line Url Server name Parsed url

    Protocol Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 58
  27. QB, создание дерева разбора qb.QB( log='default-log', fields=[ qbe.log_field('url'), qbe.log_field('server_name’), qbe.custom('parsed_url',

    parse_url, 'url'), qbe.dictitem('params', from_='parsed_url'), qbe.custom('parsed_params', parse_params, 'params'), qbe.dictitem('text', from_='parsed_params'), qbe.custom('decoded_text', url_decode, 'text'), ], filters=[ qbf.defined('decoded_text'), ] )
  28. [RootCommand(field='log_line'), ExtractCommand( field='parsed_log_line', ast=IfExpr( condition=IsNot(left=Field(name='log_line'), right=Const(value=None)), then=Apply( Const( value=<function _statbox_bindings2.string_utils.simple_parsers.parse_log_chomp>),

    Field(name='log_line')), else_=Const(value=None)), extractor=<Extractor parsed_log_line = parse_log_chomp(log_line)>), ExtractCommand( field='url'…), ExtractCommand( field='parsed_url'…), ExtractCommand( field='params'…), ExtractCommand( field='parsed_params'…), ExtractCommand( field='text'…), ExtractCommand( field='decoded_text'…), FilterCommand( ast=Is(left=Field(name='decoded_text'), right=Const(value=None))), ExtractCommand( field='server_name'…)]
  29. def map(__qb2_input_stream): """Generated by qb2.compiler.compile""" for log_line in __qb2_input_stream: if

    log_line is None: continue parsed_log_line = parse_log_chomp(log_line) if parsed_log_line is None: continue __qb2_inline_0 = parsed_log_line.get url = __qb2_inline_0('url') if url is None: continue parsed_url = parse_url(url) if parsed_url is None: continue params = parsed_url.get('params') if params is None: continue parsed_params = parse_params(params) if parsed_params is None: continue text = parsed_params.get('text') if text is None: continue decoded_text = url_decode(text) if decoded_text is None: continue server_name = __qb2_inline_0('server_name') yield Record(url=url, server_name=server_name, parsed_url=parsed_url, params=params, parsed_params=parsed_params, text=text, decoded_text=decoded_text)
  30. Инициализация объекта qb QB 64 Название дерева разбора Список полей

    для извлечения и описания дополнительных веток Список фильтраций QB AST Python AST Функция обработки
  31. Распространение фильтраций Log line Url Server name Parsed url Protocol

    Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 65
  32. Распространение фильтраций Log line Url Server name Parsed url Protocol

    Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 66
  33. Распространение фильтраций Log line Url Server name Parsed url Protocol

    Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 67
  34. def map(__qb2_input_stream): """Generated by qb2.compiler.compile""" for log_line in __qb2_input_stream: if

    log_line is None: continue parsed_log_line = parse_log_chomp(log_line) if parsed_log_line is None: continue __qb2_inline_0 = parsed_log_line.get url = __qb2_inline_0('url') if url is None: continue parsed_url = parse_url(url) if parsed_url is None: continue params = parsed_url.get('params') if params is None: continue parsed_params = parse_params(params) if parsed_params is None: continue text = parsed_params.get('text') if text is None: continue decoded_text = url_decode(text) if decoded_text is None: continue server_name = __qb2_inline_0('server_name') yield Record(url=url, server_name=server_name, parsed_url=parsed_url, params=params, parsed_params=parsed_params, text=text, decoded_text=decoded_text)
  35. def map(__qb2_input_stream): """Generated by qb2.compiler.compile""" for log_line in __qb2_input_stream: if

    log_line is None: continue parsed_log_line = parse_log_chomp(log_line) if parsed_log_line is None: continue __qb2_inline_0 = parsed_log_line.get url = __qb2_inline_0('url') if url is None: continue parsed_url = parse_url(url) if parsed_url is None: continue params = parsed_url.get('params') if params is None: continue parsed_params = parse_params(params) if parsed_params is None: continue text = parsed_params.get('text') if text is None: continue decoded_text = url_decode(text) if decoded_text is None: continue server_name = __qb2_inline_0('server_name') yield Record(url=url, server_name=server_name, parsed_url=parsed_url, params=params, parsed_params=parsed_params, text=text, decoded_text=decoded_text)
  36. Порядок разрешения ветвей Log line Url Server name Parsed url

    Protocol Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 70
  37. Порядок разрешения ветвей Log line Url Server name Parsed url

    Protocol Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 71
  38. def map(__qb2_input_stream): """Generated by qb2.compiler.compile""" for log_line in __qb2_input_stream: if

    log_line is None: continue parsed_log_line = parse_log_chomp(log_line) if parsed_log_line is None: continue __qb2_inline_0 = parsed_log_line.get url = __qb2_inline_0('url') if url is None: continue parsed_url = parse_url(url) if parsed_url is None: continue params = parsed_url.get('params') if params is None: continue parsed_params = parse_params(params) if parsed_params is None: continue text = parsed_params.get('text') if text is None: continue decoded_text = url_decode(text) if decoded_text is None: continue server_name = __qb2_inline_0('server_name') yield Record(url=url, server_name=server_name, parsed_url=parsed_url, params=params, parsed_params=parsed_params, text=text, decoded_text=decoded_text)
  39. Объединение фильтраций def map(__qb2_input_stream): """Generated by qb2.compiler.compile""" for log_line in

    __qb2_input_stream: if log_line is None: continue parsed_log_line = parse_log_chomp(log_line) if parsed_log_line is None: continue __qb2_inline_0 = parsed_log_line.get server_name = __qb2_inline_0(‘server_name') if server_name is None or server_name != 'search_1': сontinue . . .
  40. › Вынос расчета констант › Расчет одинаковых выражений в условиях

    один раз за функцию › Замена некоторых выражений на уже посчитанные ранее › Вынос из-под цикла независимых от него частей › И другие Остальные оптимизации 75
  41. def map(__qb2_input_stream): """Generated by qb2.compiler.compile""" for log_line in __qb2_input_stream: if

    log_line is None: continue parsed_log_line = parse_log_chomp(log_line) if parsed_log_line is None: continue __qb2_inline_0 = parsed_log_line.get url = __qb2_inline_0('url') if url is None: continue parsed_url = parse_url(url) if parsed_url is None: continue params = parsed_url.get('params') if params is None: continue parsed_params = parse_params(params) if parsed_params is None: continue text = parsed_params.get('text') if text is None: continue decoded_text = url_decode(text) if decoded_text is None: continue server_name = __qb2_inline_0('server_name') yield Record(url=url, server_name=server_name, parsed_url=parsed_url, params=params, parsed_params=parsed_params, text=text, decoded_text=decoded_text)
  42. Оптимизация функций Log line Url Server name Parsed url Protocol

    Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 78
  43. Оптимизация функций Log line Url Server name Parsed url Protocol

    Host Page Params Parsed params Text Other params… Other params… Other params… Decoded text 79
  44. [RootCommand(field='log_line'), ExtractCommand( field='parsed_log_line', ast=IfExpr( condition=IsNot(left=Field(name='log_line'), right=Const(value=None)), then=Apply( Const( value=<function _statbox_bindings2.string_utils.simple_parsers.parse_log_chomp>),

    Field(name='log_line')), else_=Const(value=None)), extractor=<Extractor parsed_log_line = parse_log_chomp(log_line)>), ExtractCommand( field='url'…), ExtractCommand( field='parsed_url'…), ExtractCommand( field='params'…), ExtractCommand( field='parsed_params'…), ExtractCommand( field='text'…), ExtractCommand( field='decoded_text'…), FilterCommand( ast=Is(left=Field(name='decoded_text'), right=Const(value=None))), ExtractCommand( field='server_name'…)]