Как стать автором
Обновить

Как компилировать json или история оптимизации python сервиса

Уровень сложностиСредний
Время на прочтение10 мин
Количество просмотров6.7K
Всего голосов 23: ↑22 и ↓1+29
Комментарии19

Комментарии 19

Любопытно :)

Кажется, что раз уж вы всё равно взялись свою либу пилить, то оптимальный вариант — это либа на компилируемом языке, по аналогии с каким-нибудь numpy, только помельче.

Думаю, в этом нет большой необходимости.

  • json-logic с GitHub по сути самостоятельная виртуальная машина, которая исполняет выражения как есть. Скорость расчета сегментов зависит прямо и от кода в либе, и от скорости Python

  • реализация, которую мы сделали в команде - не виртуальная машина, а скорее компилятор для Python VM. В этом случае на время расчета сегмента может повлиять только скорость самого Python

Однако можно попробовать из этой схемы убрать Python в целом, оставить только вызов чего-то написанного на другом языке. В статье не говорится, но подобные исследования мы проводили до того, как начать пилить свой велосипед :)

В частности сравнивали с либой на rust, и наше решение оказывается на порядки быстрее.

На картинках ниже сравнение:

выражение и данные о пользователе для тестов
выражение и данные о пользователе для тестов
сравнение работы двух реализаций json-logic
сравнение работы двух реализаций json-logic

Интересный результат, а почему так, не разбирались? 80 микросекунд против 300 наносекунд — это не похоже на время исполнения.

Исследование проводили. Была гипотеза, что требуется много времени на конвертацию объектов для передачи из python в rust и обратно. (Это, кстати, и правда занимает существенное время)

Если json-logic на rust быстрее python, то при очень больших выражениях, требующих много вычислений, rust должен начать обгонять python.

Для проверки этой гипотезы, провели замеры времени для выражений разной "жирности".

График отношения времени работы варианта python к варианту rust
График отношения времени работы варианта python к варианту rust

На небольших выражениях разница в скорости небольшая, в 1k раз, а на больших выражениях уже в 2k раз и продолжает расти.

Вывод, к которому мы пришли, что операторы для выполнения json-logic, реализованные на rust, сами по себе требуют времени больше, чем аналогичные операции в CPython.

Почитал по диагонали код rust-либы — похоже основная причина выигрыша в том, что код на python и код на rust делают разные вещи.

Rust на каждый вызов полноценно разбирает json, а Python результаты разбора кэширует и дальше просто выполняет операции :)

Хорошая оптимизация!

Вместо json можно это выражение сразу записывать в виде кода например js, lua или питона или любого скриптового языка. Недавно кодил нечто похожее, фильтры для json типа select(fields).where(condition)and(condition)filter и тд Получилось 450 000 json строк/сек или 450 строк/мс

И запускать через eval?

Пожалуйста, расскажите подробнее про реализацию.

Вставлять код картинками, в 2024 году? 0_о

Любопытно, только тюпл глаз режет, есть же название на русском - кортеж

Порежу еще: и произноситься должно как "тапл". Это по-немецки "тупель".

Может я что-то не понял, но почему нельзя было загнать всё это, например, в ClickHouse, и вытаскивать оттуда обычными SQL запросами? Он из сотен миллионов записей весьма сложные выборки за единицы миллисекунд у нас выдает.

После прочтения вступления возникли точно такие же мысли, но вместо ClickHouse подумал про MongoDB. Даже в примере когнитивная нагрузка большая, а это очень простая выборка

Тут типичная OLAP нагрузка, где MongoDB - не лучший выбор. Я не уверен, что Монга сможет даже из десятков миллионов, а не сотен, как ClickHouse, выбрать тысячу записей по десятку фильтров за 5 мс.

Честно не представляю, как использовать какую-либо БД в данной задаче:
Если считаем, что каждый сегмент - отдельная запись, то в этой записи как-то будет описано правило, по которому пользователь попадет в этот сегмент.

А в самом запросе соответственно будет информация об этом пользователе.

Как проверить, что сотни разных таких правил, не прибегая к eval - не тривиальная задача.

Окэй, опустим, что eval - это зло, и мы сделали такое решение, но это не все тех. требования.

Насколько решение будет масштабируемым?
Получится держать несколько тысяч RPS в real-time?
Будет гарантированное время ответа в до 5 мс с учетом сети от сервиса до базы?

Лично у меня есть сомнения, что от КХ легко можно такого добиться.

Если считаем, что каждый сегмент - отдельная запись, то в этой записи как-то будет описано правило, по которому пользователь попадет в этот сегмент.

Даже если там будет храниться строка SQL запроса, это уже решение. Но лучше, конечно, использовать placeholders и держать уже prepared statements на клиенте.

Насколько решение будет масштабируемым?

ClickHouse замечательно масштабируется горизонтально.

Получится держать несколько тысяч RPS в real-time?

На Raspberry PI не уверен, а на нормальном железе, даже в примерах CH RPS свыше 60 миллионов. И это далеко не предел.

Будет гарантированное время ответа в до 5 мс с учетом сети от сервиса до базы?

Бессмысленный вопрос. Например, если сервис с БД общается через спутниковый интернет, то и секунда задержки возможна. А если через 40-гигабитку в одном ЦОД, то больше 1 мс получите, разве что, на массивах свыше 100 миллионов записей на каждом хосте в кластере.

Нет, вопрос не бессмысленный. Если есть требования к сервису, то они фиксируются для него в целом, а не на часть компонентов. Потому весь выбираемый набор инструментов и их конфигураций должно работать достаточно слажено, чтобы выполнять требования.

Нельзя сказать, что API работает быстро, когда код в сервисе супер быстрый, база отвечает мгновенно, а сеть между ними едва существует.

А для low-latency сервисов, как сервис сплитования, шумы в сети и малозаметные задержки на балансерах / ингрессах могут повлиять на времени ответа из API в процентилях 99.9% и даже 99% катастрофически.

Хороший поинт задумываться о таких мелочах до реализации важных и сложных решений.
В одной из следующих статьей, мой коллега расскажет, как мы столкнулись с такого рода проблемой, и как ее решали :)

Нет, вопрос не бессмысленный. Если есть требования к сервису, то они фиксируются для него в целом, а не на часть компонентов.

Вы можете хоть об фиксироваться, но если сервис связывается с БД через геостационарный спутник, то пинг меньше 600 мс не получите физически. Так как не сможете превысить скорость света. Потому, без указания расстояния между сервисом и БД, а так же технических возможностях канала связи, говорить о "гарантированном времени ответа с учетом сети от сервиса до базы" - бессмысленно.

Тем более о 5 мс, которые даже от Москвы до Камчатки никогда не получите. 43 мс теоретический минимум.

Фактически, когда Вы приплели к теме сравнения Вашего решения с ClickHouse сетевые задержки, Вы занялись откровенной демагогией. Так как в любом случае наличие или отсутствие CH на сетевые задержки ну никак повлиять не может.

Я для подобного класса задач написал расширение для PHP, которое ускорило больше чем в 1000 раз обработку…

А еще оно хранит в бинарном виде кэш для предрасчитанных сегментов пользователей, позволяет строить аналитику по AB-тестам и много еще чего.

Быстрее вашего решения примерно в 100 раз на одного пользователя

Зарегистрируйтесь на Хабре, чтобы оставить комментарий