alpine в качестве базового образа для Python проектов - довольно таки плохая идея.
Используйте для прода multistage build - отдельный образ с установкой poetry+сборкой зависимостей+созданием venv, а в финальный образ добавляем только этот venv и код проекта. Сам poetry далеко не пушинка (он вместе с зависимостями весит 70-100Мб), а если какие-то из пакетов требуют компиляции из исходников, то gcc/autoconf/make и т.п. весят ещё больше.
Зачем собирать образ дважды? Первый раз в CI ддобе, второй на хосте через docker compose up --build. Соберите образ один раз, положите в Dockerhub/Github registry, и на хосте выполняйте docker pull. Ну и для сборки в Github есть специальные actions, с кэшированием, multiarch и т.п., гораздо лучше сборки через docker compose.
Можно не прописывать запуск ruff через pre-commit и на уровне CI разными командами. Например для Github есть pre-commit.ci
В Github Actions на каждый запуск джобы создаётся чистая виртуалка из шаблона, в которой прогоняются все необходимые команды, и после завершения она удаляется. Иначе любые изменения в пакетах, файловой системе и т.п. могли бы попасть в CI другого репозитория, и натворить там дел. В on-premise такого может и нет, не пользовался.
Очень не хватает технических деталей - примеров запросов, которые пришлось изменить, сравнение времени выполнения до и после, кода для функции хэширования (да хотя бы ее названия), что за "страшные недокументированные команды" пришлось запускать. Со всей этой информацией пост вполне мог стать кандидатом для добавления в закладки, и мог бы кому-нибудь пригодится в реальных задачах. А так пробежались по верхам, и все, "подписывайтесь на канал".
Сравнение производительности на запросах длительностью в несколько микросекунд, хотя на таких масштабах IO latency может нивелировать всю разницу промзводительности. На вряд ли кто-то из аналитиков и DS будет менять свой стек ради ускорения настолько коротких запросов. Тут нужно либо сравнивать больше данных, либо делать более сложные запросы, например join.
Не везде указано отличие в способе работы с данными. Pandas всегда работает с данными в памяти. Polars умеет и помещать данные в память, и работать в out-of-core режиме, обращаясь к файлам на диске и обрабатывая их небольшими чанками. DuckDB/Dask всегда используют out-of-core. Плюс многие либы сейчас используют под капотом Apache Arrow (в том числе Pandas 2.x), так что способ представления данных в памяти у них одинаковый, что позволяет легко передавать данные между ними без дополнительной конвертации.
Не указано различие в API - какие-то либы предлагают совместимый с pandas DataFrame API, у polars свой собственный DataFrame API (иммутабельный, в отличие от pandas) + есть поддержка SQL, у DuckDB только SQL.
Dask умеет в распределённый режим работы, когда воркеры могут запускаться на разных машинах, и обрабатывать отдельные кусочки данных, что даёт горизонтальное масштабирование. Pandas/Polars/DuckDB так не умеют, и их производительность ограничена ресурсами одного хоста. Dask имеет смысл сравнивать с Apache Spark, а не с ними.
Указаны последние версии либ, но не когда они были опубликованы. У Pydatatable последний коммит был год назад, выглядит не особо живым. Vaex как будто тоже остановился в развитии, релизов за последний год не было.
Есть и другие примеры компиляторов без зависимостей, которые предоставляются только в виде исходного кода
В мире C# это возможно и норма, с учётом того, что пользователи в конечном итоге скачивают программу в виде скомпилированного бинарника, и способ распространения исходного кода им не так важен. Но в питоне упаковка в бинарный файл это скорее исключение из правил - зачастую все распространяется в виде пакетов, и указывать в метаданных пакета зависимость на git репозиторий довольно неудобное решение.
Тем не менее, предполагается, что оно всё-таки где-то в стороне лежит. Не в service_1 , service_2 и service_3?
Описывать эту схему взаимодействия внутри каждого сервиса кодом или класть рядом .json/.yml - никакой разницы. Мы, например, экспортируем openapi.json в момент релиза, и прикладываем к остальным артефактам.
Иначе выглядит так, что сервис будет знать всех своих потребителей. Уж хорошо это или плохо, каждый сам решает.
OpenAPI описывает, в какой endpoint клиент должен отправить запрос и в каком формате, а также какой ответ он получит. В AsyncAPI тоже самое - в какие топики/очереди клиент должен положить сообщения и в каком формате, а также в каком топике/очереди можно получить результаты (если они есть). Плюс какие требования у этого инстанса Kafka/RabbitMQ/etc по аутентификации. Что дальше будет по цепочке с этими данными, ни AsyncAPI, ни OpenAPI не описывают, - это бизнес-логика тех сервисов, которые из него получают результаты, а не его собственная.
Позволяет ли acyncAPI описывать динамический роуминг, где мы, например, по разным топикам и routingKey сообщение в зависимости от типа распихивать? Или routingKey вычисляемый описать?
Есть подстановки для переменных вроде {userId}, по аналогии с path параметрами в OpenAPI. Либо я не понял вопроса.
А что даст такое хранение плана dataframe в Atlas? В реальных ETL процессах постоянно происходит какие-то изменения, план сложно назвать постоянным. Задача была в изучении того, как планы меняются во времени?
COPY будет передавать данные через мастер, он для таких объёмов не предназначен. Нужен CREATE EXTERNAL TABLE на стороне Greenplum + веб-сервер на стороне Spark executor'ов, реализующий gpfdist протокол, чтобы передавать данные напрямую на GP сегменты, минуя мастер.
Чтобы "продать" это разработчикам, нужно описать более понятные для них преимущества:
Т.к. UUIDv7 значения монотонно возрастают (по крайней мене первые 48 бит), БД при построении статистики по таблице могут увидеть корреляцию со значениями других столбцов (с датами, со другими числовыми значениями), и генерировать более оптимальные планы выполнения запросов.
Из-за все тех же возрастающих значений B-tree индексы перестает раздувать, и вставка выполняется быстрее.
UUIDv7 можно будет использовать как primary key для партиционированной таблицы - ключом партиционирования можно сделать вшитый в него timestamp. При доступе к конкретной записи вместо фуллскана всех партиций будет выполняться поиск только в той, которой принадлежит этот timestamp. С UUIDv4 же нужно дополнительное поле с timestamp, которое придется явно добавлять во все фильтры, и в случае PRIMARY KEY добавляет головной боли.
Выделение индексированных и неиндексированных столбцов в интерфейсе разными цветами и без UUID можно реализовать. Да и фича выглядит крайне сомнительной, давать пользователям в руки фильтрацию по полю без индекса - прямой путь к тормозящим запросам на стороне БД.
Слияние дубликатов - дубликаты же не по одному id определяются, а по комбинации других полей. UUID тут ничего нового не даст, дубликаты вообще не обязательно прилетают в один и тот же промежуток времени, чтобы его внутренний timestamp тут чем-то помогл.
Сквозной поиск объекта по его UUID во всей базе/API - без вшитого в идентификатор типа записи это довольно проблематично реализовать, нужен кастомный генератор значений + функция для извлечения типа из id. Возможно проще использовать идентификаторы вида {type}:{uuid}, как это делают например в GraphQL Relay.
Faster CPython вообще не про тесты, это про изменение внутренней работы интерпретатора, чтобы поднять его производительность
А можно ссылку на источник?
alpine в качестве базового образа для Python проектов - довольно таки плохая идея.
Используйте для прода multistage build - отдельный образ с установкой poetry+сборкой зависимостей+созданием venv, а в финальный образ добавляем только этот venv и код проекта. Сам poetry далеко не пушинка (он вместе с зависимостями весит 70-100Мб), а если какие-то из пакетов требуют компиляции из исходников, то gcc/autoconf/make и т.п. весят ещё больше.
Зачем собирать образ дважды? Первый раз в CI ддобе, второй на хосте через docker compose up --build. Соберите образ один раз, положите в Dockerhub/Github registry, и на хосте выполняйте docker pull. Ну и для сборки в Github есть специальные actions, с кэшированием, multiarch и т.п., гораздо лучше сборки через docker compose.
Можно не прописывать запуск ruff через pre-commit и на уровне CI разными командами. Например для Github есть pre-commit.ci
Нужно просто прикрутить dependabot/renovate/периодический poetry update к репозиторию. Обновлять зависимости без прогона тестов я бы уж точно не стал.
Вместо COPY poetry.lock он почему-то создаётся заново. Весь его смысл теряется
В Github Actions на каждый запуск джобы создаётся чистая виртуалка из шаблона, в которой прогоняются все необходимые команды, и после завершения она удаляется. Иначе любые изменения в пакетах, файловой системе и т.п. могли бы попасть в CI другого репозитория, и натворить там дел. В on-premise такого может и нет, не пользовался.
В CI runner Github Actions так можно. Иначе невозможно было бы устанавливать пакеты в ОС, например.
Очень не хватает технических деталей - примеров запросов, которые пришлось изменить, сравнение времени выполнения до и после, кода для функции хэширования (да хотя бы ее названия), что за "страшные недокументированные команды" пришлось запускать. Со всей этой информацией пост вполне мог стать кандидатом для добавления в закладки, и мог бы кому-нибудь пригодится в реальных задачах. А так пробежались по верхам, и все, "подписывайтесь на канал".
Ещё бы в сравнении указать размеры обоих индексов
Очень странная статья.
Сравнение производительности на запросах длительностью в несколько микросекунд, хотя на таких масштабах IO latency может нивелировать всю разницу промзводительности. На вряд ли кто-то из аналитиков и DS будет менять свой стек ради ускорения настолько коротких запросов. Тут нужно либо сравнивать больше данных, либо делать более сложные запросы, например join.
Не везде указано отличие в способе работы с данными. Pandas всегда работает с данными в памяти. Polars умеет и помещать данные в память, и работать в out-of-core режиме, обращаясь к файлам на диске и обрабатывая их небольшими чанками. DuckDB/Dask всегда используют out-of-core. Плюс многие либы сейчас используют под капотом Apache Arrow (в том числе Pandas 2.x), так что способ представления данных в памяти у них одинаковый, что позволяет легко передавать данные между ними без дополнительной конвертации.
Не указано различие в API - какие-то либы предлагают совместимый с pandas DataFrame API, у polars свой собственный DataFrame API (иммутабельный, в отличие от pandas) + есть поддержка SQL, у DuckDB только SQL.
Dask умеет в распределённый режим работы, когда воркеры могут запускаться на разных машинах, и обрабатывать отдельные кусочки данных, что даёт горизонтальное масштабирование. Pandas/Polars/DuckDB так не умеют, и их производительность ограничена ресурсами одного хоста. Dask имеет смысл сравнивать с Apache Spark, а не с ними.
Указаны последние версии либ, но не когда они были опубликованы. У Pydatatable последний коммит был год назад, выглядит не особо живым. Vaex как будто тоже остановился в развитии, релизов за последний год не было.
В мире C# это возможно и норма, с учётом того, что пользователи в конечном итоге скачивают программу в виде скомпилированного бинарника, и способ распространения исходного кода им не так важен. Но в питоне упаковка в бинарный файл это скорее исключение из правил - зачастую все распространяется в виде пакетов, и указывать в метаданных пакета зависимость на git репозиторий довольно неудобное решение.
Довольно странный способ установки. В Python обычно публикуют пакет на pypi.org, и тогда его можно установить любым пакетным менеджером.
Описывать эту схему взаимодействия внутри каждого сервиса кодом или класть рядом .json/.yml - никакой разницы. Мы, например, экспортируем openapi.json в момент релиза, и прикладываем к остальным артефактам.
OpenAPI описывает, в какой endpoint клиент должен отправить запрос и в каком формате, а также какой ответ он получит. В AsyncAPI тоже самое - в какие топики/очереди клиент должен положить сообщения и в каком формате, а также в каком топике/очереди можно получить результаты (если они есть). Плюс какие требования у этого инстанса Kafka/RabbitMQ/etc по аутентификации. Что дальше будет по цепочке с этими данными, ни AsyncAPI, ни OpenAPI не описывают, - это бизнес-логика тех сервисов, которые из него получают результаты, а не его собственная.
Есть подстановки для переменных вроде {userId}, по аналогии с path параметрами в OpenAPI. Либо я не понял вопроса.
UPD: промахнулся, это был ответ на https://habr.com/ru/articles/860604/comments/#comment_27590436
Зависит от инструмента. К примеру, FastStream умеет генерировать AsyncAPI схему из аннотаций типов в коде.
А что даст такое хранение плана dataframe в Atlas? В реальных ETL процессах постоянно происходит какие-то изменения, план сложно назвать постоянным. Задача была в изучении того, как планы меняются во времени?
Highscreen Boost 2, какой-нибудь
И как с производительностью?
COPY будет передавать данные через мастер, он для таких объёмов не предназначен. Нужен CREATE EXTERNAL TABLE на стороне Greenplum + веб-сервер на стороне Spark executor'ов, реализующий gpfdist протокол, чтобы передавать данные напрямую на GP сегменты, минуя мастер.
Так и не выложили коннектор в Maven :(
Чтобы "продать" это разработчикам, нужно описать более понятные для них преимущества:
Т.к. UUIDv7 значения монотонно возрастают (по крайней мене первые 48 бит), БД при построении статистики по таблице могут увидеть корреляцию со значениями других столбцов (с датами, со другими числовыми значениями), и генерировать более оптимальные планы выполнения запросов.
Из-за все тех же возрастающих значений B-tree индексы перестает раздувать, и вставка выполняется быстрее.
UUIDv7 можно будет использовать как primary key для партиционированной таблицы - ключом партиционирования можно сделать вшитый в него timestamp. При доступе к конкретной записи вместо фуллскана всех партиций будет выполняться поиск только в той, которой принадлежит этот timestamp. С UUIDv4 же нужно дополнительное поле с timestamp, которое придется явно добавлять во все фильтры, и в случае PRIMARY KEY добавляет головной боли.
Выделение индексированных и неиндексированных столбцов в интерфейсе разными цветами и без UUID можно реализовать. Да и фича выглядит крайне сомнительной, давать пользователям в руки фильтрацию по полю без индекса - прямой путь к тормозящим запросам на стороне БД.
Слияние дубликатов - дубликаты же не по одному id определяются, а по комбинации других полей. UUID тут ничего нового не даст, дубликаты вообще не обязательно прилетают в один и тот же промежуток времени, чтобы его внутренний timestamp тут чем-то помогл.
Сквозной поиск объекта по его UUID во всей базе/API - без вшитого в идентификатор типа записи это довольно проблематично реализовать, нужен кастомный генератор значений + функция для извлечения типа из id. Возможно проще использовать идентификаторы вида
{type}:{uuid}
, как это делают например в GraphQL Relay.