#prog #article
Shrinking your code review
Набор практических советов на тему того, как уменьшать размер кода для ревью.
Shrinking your code review
It's an unfortunate reality, but one of the few things we know about software quality is that lines of code is positive correlated with bugs, <...>. Code review faces a similar challenge: the larger a patch you're reviewing, the less effective your code review is
<...>
Fundamentally there's only two ways to get smaller reviews: write less code or split your changes up across multiple reviews. I'm not going to focus on writing less code, a great much about has been written about this topic. Instead I'm going to focus on how to split up your changes.
Набор практических советов на тему того, как уменьшать размер кода для ревью.
👍9🤔1
Блог*
#prog #rust #rustasync #suckassstory Ask not what the compiler can do for you TL;DR: из-за того, что async_trait боксит футуры и стирает типы, в асинхронном коде, использующем этот крейт, можно организовать безусловную рекурсию, и ни компилятор, ни clippy…
This media is not supported in your browser
VIEW IN TELEGRAM
😁18❤3🤡1
Forwarded from Сергей Озеранский
🧠 Как 3 вечера анализа и оптимизаций дали минус 1 CPU и +40% к скорости ответов API
👀 Всё началось с того, что я случайно (ну как случайно, проблема была всегда, просто я первый задался вопросом - "почему?") заметил, что один Python-сервис на staging потребляет до 2.5 CPU.
Для сравнения — весь namespace потребляет около 6 CPU. То есть один сервис ест почти половину ресурсов. И это при том, что это не какой-то нагруженные сервис, это синхронный API-сервис, да еще и без нагрузки, это же staging.
Стало интересно — а что там внутри вообще так жрёт?
📦 Запустил профайлинг CPU через Pyroscope и… понеслось.
UI у Pyroscope меня не устроил — флеймграфы красивые, но неудобные для глубокого анализа.
📥 Поэтому я выгрузил дамп как
Так мне удобнее, быстрее и информативнее.
📊 Профилирование показало несколько важных узких мест:
🧱 Проблема №1: конвертация данных из MySQL
Самое "жирное" место —
Это код, который конвертирует строки из БД в Python-объекты на каждый запрос.
Наиболее затратные конвертации:
-
-
-
-
-
✅ Решение:
📌 Использовал C extension у mysql-connector-python
Официальная документация: https://dev.mysql.com/doc/connector-python/en/connector-python-cext.html
📉 Результат:
- Минус 1 CPU
- До +40% ускорение некоторых endpoint’ов
🧠 Проблема №2: неэффективный Python-код
Пример: функция
- делает кучу проверок в логике
- использует неэффективные структуры данных, например, list для поиска вместо
- обрабатывает сразу множество возможных вариантов логики, в зависимости от входных данных
✅ Решение:
📌 Переписал участок без изменения бизнес-логики:
- заменил структуры данных
- добавил ранние выходы
- убрал дублирующиеся проверки
📌 Cognitive complexity снизилась, временная сложность — тоже. Производительность выросла.
⚠️ Проблема №3: код, который не нужен, но работает
Профайлинг показал, что куча ресурсов уходит на код, который вообще не должен уже как год использоваться. Но код активно выполняется. WTF!?
🤷♂️ Функции вызываются, результат — пустой, но код исполняется. Причём часто и тяжело.
✅ Решение:
📌 Удалил мёртвый код, обновил импорты, подчистил зависимости.
📌 Поднял вопрос о полной деактивации этого кода и мы таки это сделали — ресурсы можно использовать лучше.
🐘 Проблема №4: Импорты. Много. Дорого.
Во время анализа я наткнулся на ещё одну тихую, но дорогую проблему — огромные ресурсы уходят на фазу импорта модулей в Python.
Конкретно — на
📌 Почему это важно?
- Импорты выполняются на каждый старт сервиса.
- Чем жирнее и зависимее ваши модули, тем дольше и тяжелее проходит импорт.
- Это не всегда очевидно, но можно видеть в профайлинге:
📊 У нас в сервисе модулей реально много.
Многое из них – просто "свалены в кучу", где-то грузятся тяжёлые зависимости. Да и сложная структура проекта вынуждает иметь большое количество импортов.
И, как итог, CPU тратится на то, что можно было бы стремиться избежать.
✅ Что с этим делать:
- Разделять модули по функциональности.
- Отложенные импорты (lazy import) – практика может применяться (но есть нюансы), если модуль нужен только в конкретной функции.
- Минимизировать зависимости и импорт только того, что действительно нужно.
- Следить за импортами в
Следующий шаг: memory профайлинг. Pyroscope в нашей инфрастуктуре такое не умеет и нужно приседать, но надеюсь дойдут руки и до RAM.
👀 Всё началось с того, что я случайно (ну как случайно, проблема была всегда, просто я первый задался вопросом - "почему?") заметил, что один Python-сервис на staging потребляет до 2.5 CPU.
Для сравнения — весь namespace потребляет около 6 CPU. То есть один сервис ест почти половину ресурсов. И это при том, что это не какой-то нагруженные сервис, это синхронный API-сервис, да еще и без нагрузки, это же staging.
Стало интересно — а что там внутри вообще так жрёт?
📦 Запустил профайлинг CPU через Pyroscope и… понеслось.
UI у Pyroscope меня не устроил — флеймграфы красивые, но неудобные для глубокого анализа.
📥 Поэтому я выгрузил дамп как
pprof
файл и открыл его через go tool pprof
.Так мне удобнее, быстрее и информативнее.
📊 Профилирование показало несколько важных узких мест:
🧱 Проблема №1: конвертация данных из MySQL
Самое "жирное" место —
conversion.py
→ MySQLConverter.row_to_python
.Это код, который конвертирует строки из БД в Python-объекты на каждый запрос.
Наиболее затратные конвертации:
-
_DECIMAL_to_python
-
_INT_to_python
-
_JSON_to_python
-
_DATETIME_to_python
-
_STRING_to_python
✅ Решение:
📌 Использовал C extension у mysql-connector-python
Официальная документация: https://dev.mysql.com/doc/connector-python/en/connector-python-cext.html
📉 Результат:
- Минус 1 CPU
- До +40% ускорение некоторых endpoint’ов
🧠 Проблема №2: неэффективный Python-код
Пример: функция
change_type
, которая:- делает кучу проверок в логике
- использует неэффективные структуры данных, например, list для поиска вместо
set
/ dict
- обрабатывает сразу множество возможных вариантов логики, в зависимости от входных данных
✅ Решение:
📌 Переписал участок без изменения бизнес-логики:
- заменил структуры данных
- добавил ранние выходы
- убрал дублирующиеся проверки
📌 Cognitive complexity снизилась, временная сложность — тоже. Производительность выросла.
⚠️ Проблема №3: код, который не нужен, но работает
Профайлинг показал, что куча ресурсов уходит на код, который вообще не должен уже как год использоваться. Но код активно выполняется. WTF!?
🤷♂️ Функции вызываются, результат — пустой, но код исполняется. Причём часто и тяжело.
✅ Решение:
📌 Удалил мёртвый код, обновил импорты, подчистил зависимости.
📌 Поднял вопрос о полной деактивации этого кода и мы таки это сделали — ресурсы можно использовать лучше.
🐘 Проблема №4: Импорты. Много. Дорого.
Во время анализа я наткнулся на ещё одну тихую, но дорогую проблему — огромные ресурсы уходят на фазу импорта модулей в Python.
Конкретно — на
_find_and_load
, часть механизма импорта, который занимается поиском, загрузкой и инициализацией модулей.📌 Почему это важно?
- Импорты выполняются на каждый старт сервиса.
- Чем жирнее и зависимее ваши модули, тем дольше и тяжелее проходит импорт.
- Это не всегда очевидно, но можно видеть в профайлинге:
_find_and_load
, _find_and_load_unlocked
, _load_unlocked
– вот это всё.📊 У нас в сервисе модулей реально много.
Многое из них – просто "свалены в кучу", где-то грузятся тяжёлые зависимости. Да и сложная структура проекта вынуждает иметь большое количество импортов.
И, как итог, CPU тратится на то, что можно было бы стремиться избежать.
✅ Что с этим делать:
- Разделять модули по функциональности.
- Отложенные импорты (lazy import) – практика может применяться (но есть нюансы), если модуль нужен только в конкретной функции.
- Минимизировать зависимости и импорт только того, что действительно нужно.
- Следить за импортами в
__init__.py
— именно они могут тянуть за собой пол кодовой базы.Следующий шаг: memory профайлинг. Pyroscope в нашей инфрастуктуре такое не умеет и нужно приседать, но надеюсь дойдут руки и до RAM.
👏12🤡5👍2❤1
Forwarded from Таксики и лытдыбр σποραδικος
Принесла вам чудесную серию рисунков — Franciszka Themerson, Science and Technology:
1. radio-astronomy
2. distillery
3. high voltage
и, внезапно
4. the tea break (британские ученые такие британские, даже если они польские художницы, перебравшиеся в Лондон в сороковом)
1. radio-astronomy
2. distillery
3. high voltage
и, внезапно
4. the tea break (британские ученые такие британские, даже если они польские художницы, перебравшиеся в Лондон в сороковом)
👍4❤3👎1
#prog #article #amazingopensource
Jujutsu (jj) — система контроля версий, которая концептуально проще git и при этом мощнее.
Неплохой (но местами устаревший) обзор Jujutsu — jj init — сделал Chris Krycho. Также есть пока что неполный туториал от Стива Клабника, который тот написал главным образом для того, чтобы самому лучше разобраться с Jujutsu. Этот туториал даже рекомендуется в официальной документации.
Сразу скажу: jj работает поверх git, так что её можно поставить поверх имеющегося репозитория и потом без проблем удалить. (у jujutsu также есть нативный бекенд, но он пока не готов и не рекомендуется к использованию)
Что же такого примечательного в этой VCS? Попробую объяснить (но лучше почитайте по ссылкам, чесслово, там хорошо описано). В обоих описаниях бросается в глаза наличие операций, описанных в Where are my Git UI features from the future?.
Как и git, jj идейно работает на снапшотах файловой системы. В отличие от git, коммиты в jj напоминают объекты в ООП: они мутабельны и имеют идентичность, которая остаётся постоянной при изменениях самих коммитов — да, в том числе при ребейзах.
В отличие от git, в jj нет отдельной стадии стейджинга изменений и фиксации коммита. Более того, jj вообще не использует индекс — jj отслеживает изменения файлов и автоматически записывает их в текущий коммит (не волнуйтесь, есть средства для разбиения изменений). Для того, чтобы закончить с текущим коммитом, достаточно вызвать
Как я уже сказал, описание коммитов можно добавлять и менять в любой момент, вне зависимости от того, какой коммит текущий.
Ветки в jj анонимны, и это взрывает мозг тем, кто учился VCS на git (мне в том числе). Именованные ветки из git доступны в jj под именем bookmarks, и, в отличие от git, эти указатели не смещаются автоматически при добавлении дочерних коммитов — для этого нужно вызвать отдельную команду. Это выглядит неудобным, но по факту это даёт некоторые преимущества. Одно из них — нет нужды придумывать имя для каждого логического изменения. Оно требуется лишь тогда, как нужно расшарить изменения с другими. Другое — если в локальной копии слишком много изменений и стало понятно, что они идут куда-то не туда, можно просто откатиться до ветки (при условии, что локально её не двигали) и просто дропнуть коммиты после неё.
Коммиты можно свободно двигать в истории. В отличие от git rebase, имена коммитов после этого не меняются.
Ребейз и мердж в jj всегда успешно завершаются. Это не означает, что конфликты невозможны. Но в jj конфликты являются первоклассными значениями. Это позволяет слить несколько последовательностей коммитов в одну и разобраться с конфликтами позднее (и потом, возможно, слить разрешения конфликтов с мердж-коммитами).
Команда
Почти все команды jj позволяют указывать коммиты, на которых они действуют (и используют текущий коммит, если этот аргумент не указан). Более того, это может быть несколько коммитов — или revset в терминологии jj. Задавать revset-ы можно при помощи отдельного достаточно мощного языка выражений. В качестве побочного эффекта это означает, что, помимо всего прочего, в jj можно сделать мердж больше двух веток сразу.
Например, для того, чтобы просмотреть все локальные "ветки" (коммиты без дочерних коммитов), можно использовать
Jujutsu (jj) — система контроля версий, которая концептуально проще git и при этом мощнее.
Неплохой (но местами устаревший) обзор Jujutsu — jj init — сделал Chris Krycho. Также есть пока что неполный туториал от Стива Клабника, который тот написал главным образом для того, чтобы самому лучше разобраться с Jujutsu. Этот туториал даже рекомендуется в официальной документации.
Сразу скажу: jj работает поверх git, так что её можно поставить поверх имеющегося репозитория и потом без проблем удалить. (у jujutsu также есть нативный бекенд, но он пока не готов и не рекомендуется к использованию)
Что же такого примечательного в этой VCS? Попробую объяснить (но лучше почитайте по ссылкам, чесслово, там хорошо описано). В обоих описаниях бросается в глаза наличие операций, описанных в Where are my Git UI features from the future?.
Как и git, jj идейно работает на снапшотах файловой системы. В отличие от git, коммиты в jj напоминают объекты в ООП: они мутабельны и имеют идентичность, которая остаётся постоянной при изменениях самих коммитов — да, в том числе при ребейзах.
В отличие от git, в jj нет отдельной стадии стейджинга изменений и фиксации коммита. Более того, jj вообще не использует индекс — jj отслеживает изменения файлов и автоматически записывает их в текущий коммит (не волнуйтесь, есть средства для разбиения изменений). Для того, чтобы закончить с текущим коммитом, достаточно вызвать
jj new
для создания нового коммита, который отпочковывается от текущего. Описание для этого вводить необязательно — его можно добавить задним числом. Недостаток этого решения — большинство инструментов поверх git предполагают использование индекса и потому не очень хорошо стыкуются с jj.Как я уже сказал, описание коммитов можно добавлять и менять в любой момент, вне зависимости от того, какой коммит текущий.
Ветки в jj анонимны, и это взрывает мозг тем, кто учился VCS на git (мне в том числе). Именованные ветки из git доступны в jj под именем bookmarks, и, в отличие от git, эти указатели не смещаются автоматически при добавлении дочерних коммитов — для этого нужно вызвать отдельную команду. Это выглядит неудобным, но по факту это даёт некоторые преимущества. Одно из них — нет нужды придумывать имя для каждого логического изменения. Оно требуется лишь тогда, как нужно расшарить изменения с другими. Другое — если в локальной копии слишком много изменений и стало понятно, что они идут куда-то не туда, можно просто откатиться до ветки (при условии, что локально её не двигали) и просто дропнуть коммиты после неё.
Коммиты можно свободно двигать в истории. В отличие от git rebase, имена коммитов после этого не меняются.
Ребейз и мердж в jj всегда успешно завершаются. Это не означает, что конфликты невозможны. Но в jj конфликты являются первоклассными значениями. Это позволяет слить несколько последовательностей коммитов в одну и разобраться с конфликтами позднее (и потом, возможно, слить разрешения конфликтов с мердж-коммитами).
Команда
jj undo
отменяет действие предыдущей команды. Дико не хватает этого в git.Почти все команды jj позволяют указывать коммиты, на которых они действуют (и используют текущий коммит, если этот аргумент не указан). Более того, это может быть несколько коммитов — или revset в терминологии jj. Задавать revset-ы можно при помощи отдельного достаточно мощного языка выражений. В качестве побочного эффекта это означает, что, помимо всего прочего, в jj можно сделать мердж больше двух веток сразу.
Например, для того, чтобы просмотреть все локальные "ветки" (коммиты без дочерних коммитов), можно использовать
jj log -r 'heads(all())'
. all()
возвращает все коммиты в репе, а heads
извлекает из набора коммитов те, у которых нет дочерних. Если этого недостаточно и нужно добавить контекста — скажем, по два родительских коммита — можно использовать jj log -r 'ancestors(heads(all()), 2)'
. (это лишь мой пример, по умолчанию jj log
использует несколько более полезный формат)👍14🤔4