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
PRO

April 24, 2018
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. View Slide

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

    View Slide

  3. Аналитика в Яндексе
    Производительность, читаемость, скорость разработки

    View Slide

  4. По типу используемых данных для достижения результата:
    › базовые показатели
    › сложные системы
    Типы аналитики
    4

    View Slide

  5. › App Метрика
    › Web Метрика
    › Стандартные отчеты на внутренних сервисах
    Инструменты анализа базовых показателей
    5

    View Slide

  6. Анализ сложных систем
    6

    View Slide

  7. › Какой эффект от рекламы по телевизору?
    Анализ сложных систем
    7

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. Источником данных являются логи сервисов и приложений.
    Наши сервисы пишут более 700 различных логов.
    Объемами от нескольких МБ до сотен Тб в сутки.
    Источники данных
    12

    View Slide

  13. YT - система распределенных вычислений на основе
    модели Map-Reduce.
    Хранение и обработка данных
    13

    View Slide

  14. › Конструкторы расчета показателей в YT
    › SQL-like запросы к данным на YT
    › Написание кода на языках программирования
    Инструменты обработки данных
    14

    View Slide

  15. Умеет и любит работать с данными.
    Знает языки Python
    Аналитик
    15

    View Slide

  16. Аналитик
    16

    View Slide

  17. Плюсы:
    Аналитик
    17

    View Slide

  18. Плюсы:
    › Решение задач любой сложности
    Аналитик
    18

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. Минусы:
    Аналитик
    22

    View Slide

  23. Минусы:
    › Неоптимальный код
    Аналитик
    23

    View Slide

  24. Минусы:
    › Неоптимальный код
    › Медленный язык программирования
    Аналитик
    24

    View Slide

  25. Минусы:
    › Неоптимальный код
    › Медленный язык программирования
    › "Велосипеды" и различия в коде с одинаковой
    функциональностью у нескольких аналитиков
    Аналитик
    25

    View Slide

  26. › Производительность
    › Читаемость
    › Скорость разработки
    Промежуточный итог
    26

    View Slide

  27. Решение №1
    Производительность, читаемость, скорость разработки

    View Slide

  28. Аналитик + разработчик
    28

    View Slide

  29. Минусы:
    Аналитик + разработчик
    29

    View Slide

  30. Минусы:
    › Время решения задачи
    Аналитик + разработчик
    30

    View Slide

  31. Минусы:
    › Время решения задачи
    › Скучные задачи для разработчика
    Аналитик + разработчик
    31

    View Slide

  32. › Производительность
    › Читаемость
    › Скорость разработки
    Промежуточный итог
    32

    View Slide

  33. Анализ работы с
    данными
    Производительность, читаемость, скорость разработки

    View Slide

  34. Посчитать популярность запросов
    Задача
    34

    View Slide

  35. Входные данные
    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
    Логи доступа к сервисам поиска

    View Slide

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

    View Slide

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

    View Slide

  38. Деревья разбора
    Производительность, читаемость, скорость разработки

    View Slide

  39. Входные данные
    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
    Логи доступа к сервисам поиска

    View Slide

  40. Входные данные
    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
    Логи доступа к сервисам поиска

    View Slide

  41. Входные данные
    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
    Логи доступа к сервисам поиска

    View Slide

  42. Входные данные
    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
    Логи доступа к сервисам поиска

    View Slide

  43. Входные данные
    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
    Логи доступа к сервисам поиска

    View Slide

  44. Входные данные
    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
    Логи доступа к сервисам поиска

    View Slide

  45. Дерево разбора
    Log line
    45

    View Slide

  46. Дерево разбора
    Log line
    Url
    Server name
    46

    View Slide

  47. Дерево разбора
    Log line
    Url
    Server name
    Parsed url
    47

    View Slide

  48. Дерево разбора
    Log line
    Url
    Server name
    Parsed url
    Protocol Host Page Params
    48

    View Slide

  49. Дерево разбора
    Log line
    Url
    Server name
    Parsed url
    Protocol Host Page Params
    Parsed params
    49

    View Slide

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

    View Slide

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

    View Slide

  52. › Сохранение деревьев и их частей
    › Расширение существующих деревьев
    Дополнительные фичи деревьев
    52

    View Slide

  53. QB
    Производительность, читаемость, скорость разработки

    View Slide

  54. Вызов функции
    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

    View Slide

  55. Желаемая функция обработки
    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)

    View Slide

  56. Abstract Syntax Tree - дерево объектов языка Python
    Оно формируется путем анализа кода на Python, а используется
    компилятором для формирования байт-кода.
    AST
    56

    View Slide

  57. Инициализация объекта qb
    QB
    57
    Название дерева
    разбора
    Список полей для
    извлечения и
    описания
    дополнительных
    веток
    Список фильтраций
    QB AST Python AST
    Функция
    обработки

    View Slide

  58. Дерево разбора лога
    Log line
    Url
    Server name
    Parsed url
    Protocol Host Page Params
    Parsed params
    Text Other params…
    Other params…
    Other params…
    Decoded text
    58

    View Slide

  59. QB, работа с существующим деревом
    qb = QB(
    log='access-log',
    fields=['decoded_text', 'server_name', 'page'],
    filters=[qbf.defined('decoded_text')]
    )

    View Slide

  60. 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'),
    ]
    )

    View Slide

  61. [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=),
    Field(name='log_line')),
    else_=Const(value=None)),
    extractor=),
    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'…)]

    View Slide

  62. 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)

    View Slide

  63. Оптимизации
    Производительность, читаемость, скорость разработки

    View Slide

  64. Инициализация объекта qb
    QB
    64
    Название дерева
    разбора
    Список полей для
    извлечения и
    описания
    дополнительных
    веток
    Список фильтраций
    QB AST Python AST
    Функция
    обработки

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  68. 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)

    View Slide

  69. 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)

    View Slide

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

    View Slide

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

    View Slide

  72. 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)

    View Slide

  73. Объединение фильтраций
    filters=[
    qbf.defined('server_name'),
    qbf.equals('server_name', 'search_1'),
    ]

    View Slide

  74. Объединение фильтраций
    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
    . . .

    View Slide

  75. › Вынос расчета констант
    › Расчет одинаковых выражений в условиях один раз за
    функцию
    › Замена некоторых выражений на уже посчитанные ранее
    › Вынос из-под цикла независимых от него частей
    › И другие
    Остальные оптимизации
    75

    View Slide

  76. 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)

    View Slide

  77. Оптимизация функций
    Производительность, читаемость, скорость разработки

    View Slide

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

    View Slide

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

    View Slide

  80. Оптимизация функций
    qb = QB(
    log='access-log',
    fields=['decoded_text', 'server_name', 'page'],
    filters=[qbf.defined('decoded_text')]
    )

    View Slide

  81. [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=),
    Field(name='log_line')),
    else_=Const(value=None)),
    extractor=),
    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'…)]

    View Slide

  82. Итоги
    Производительность, читаемость, скорость разработки

    View Slide

  83. › Производительность
    › Читаемость
    › Скорость разработки
    Итоги
    83

    View Slide

  84. Смирнов Павел
    Разработчик-аналитик
    [email protected]

    View Slide