<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
  <title>Статьи | Oleg Tsvetkov</title>
  <subtitle>Личные заметки: инструкции, опыт и разбор сделанного про инфраструктуру, DevOps и разработку. Частично субъективно.</subtitle>
  <id>https://tsv.one/ru/feed.xml</id>
  <link rel="self" type="application/atom+xml" href="https://tsv.one/ru/feed.xml"/>
  <link rel="alternate" type="text/html" hreflang="ru" href="https://tsv.one/ru/articles/"/>
  <link rel="alternate" type="application/atom+xml" hreflang="en" href="https://tsv.one/feed.xml"/>
  <updated>2026-07-25T01:37:31+03:00</updated>
  <author>
    <name>Oleg Tsvetkov</name>
    <uri>https://tsv.one/</uri>
    <email>oleg@tsv.one</email>
  </author>
  <icon>https://tsv.one/favicon.svg</icon>
  <logo>https://tsv.one/face.webp</logo>
  <entry>
    <title>uv в проде: скорость реальна, интеграция не бесплатна</title>
    <id>https://tsv.one/ru/articles/uv-in-production-caveats</id>
    <link rel="alternate" type="text/html" href="https://tsv.one/ru/articles/uv-in-production-caveats"/>
    <published>2026-06-21T18:00:00+03:00</published>
    <updated>2026-07-25T01:37:31+03:00</updated>
    <summary type="text">uv быстрее pip примерно в 10 раз, но миграция вскрывает неочевидные изменения поведения. Пять мест из прода, каждое с ошибкой и фиксом.</summary>
    <category term="uv"/>
    <category term="python"/>
    <category term="pip"/>
    <category term="venv"/>
    <category term="Docker"/>
    <category term="Twine"/>
    <category term="DevOps"/>
    <category term="CI/CD"/>
    <category term="инфраструктура"/>
    <content type="html">&lt;p class=&quot;article-stamp&quot;&gt;uv 0.9+ · ~90 дней в проде&lt;/p&gt;

&lt;p class=&quot;article-lede&quot;&gt;uv, пакетный менеджер Astral, в бенчмарках быстрее pip в 10-100 раз. У нас на CI вышло скромнее, около 10x. Мне кажется, тут никто не удивлён, скорость заявлялась как основная фича.&lt;/p&gt;

&lt;p class=&quot;article-callout&quot;&gt;&lt;strong class=&quot;article-callout-label&quot;&gt;Дисклеймер.&lt;/strong&gt; С Astral я никак не связан. Всё, что ниже, это внутренний опыт VK и собственные замеры на нашей же инфре (кто я, всё в &lt;a href=&quot;https://tsv.one/ru/resume&quot;&gt;резюме&lt;/a&gt;). Чужие маркетинговые цифры не переписываю.&lt;/p&gt;

&lt;p&gt;Вердикт, чтобы он был сразу, а не где-то внизу под спойлером: uv оставляем, откатываться не собираемся, скорость того стоит. Но обещание про скорость и есть самая лёгкая часть сделки. Дорогими оказались несколько неочевидных изменений в поведении. За почти каждым стоит одна и та же причина: uv намеренно строже pip. Не баг. Чаще всего верное и осознанное решение, но это не мешает спотыкаться при уходе.&lt;/p&gt;

&lt;p&gt;И да, версия проставлена специально. uv движется быстро, конкретные флаги, env'ы и дефолты к моменту прочтения вполне могли измениться.&lt;/p&gt;

&lt;h2&gt;Скорость, если коротко&lt;/h2&gt;

&lt;p&gt;Цифру, которую любят все, приведу один раз и пойду дальше. На нашем CI шаг установки ужался с ~60 до ~3-5 секунд. Честные ~10x на этом шаге, заметные на первой же сборке.&lt;/p&gt;

&lt;p&gt;На этом про скорость всё. Она реальная, она приятная, она досталась почти даром. Дальше идёт то, что даром не досталось.&lt;/p&gt;

&lt;h2&gt;Пять особенностей, по заметности&lt;/h2&gt;

&lt;p&gt;Отсортировал не по тому, когда прилетело, а по тому, насколько громко. Сверху то, что ломает заметно и сразу. Снизу то, что может не работать молча, пока кто-нибудь случайно не заметит.&lt;/p&gt;

&lt;h3&gt;1. uv меняет дефолт pip по месту установки пакетов&lt;/h3&gt;

&lt;p&gt;pip, если не активен ни один venv, спокойно ставит пакеты в глобальное или пользовательское окружение. uv так не делает. Нет venv? Тогда либо активируй его, либо скажи явно: &lt;code&gt;--python /path/to/python&lt;/code&gt; или &lt;code&gt;--system&lt;/code&gt;. Флаг &lt;code&gt;--user&lt;/code&gt; uv не понимает в принципе, отвергает сразу.&lt;/p&gt;

&lt;p&gt;Забыл активировать venv? uv не угадывает за тебя и не лезет в систему молчком, а просто останавливается:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;error: No virtual environment found; run `uv venv` to create an environment, or pass `--system` to install into a non-virtual environment&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Было: «клиент просто запускается как раньше». Стало: «клиенту нужен активированный venv или явные флаги». Звучит мелко, пока это не двадцать разных сервисов/образов/репозиториев и несколько скриптов, которые предполагали установку через &lt;code&gt;--user&lt;/code&gt; или неявно глобальную.&lt;/p&gt;

&lt;p&gt;Идеология понятна - глобальные установки реально портят систему, и pip тут добрее. Но цена ухода настоящая.&lt;/p&gt;

&lt;h3&gt;2. Авто-установка Python спотыкается о файрвол (но исправимо)&lt;/h3&gt;

&lt;p&gt;uv умеет сам скачать нужный интерпретатор, тянет он его по запросу из astral'овского python-build-standalone. Казалось бы - теперь есть универсальный способ получить python нужной версии в образе любого дистрибутива. Удобно ровно до первого корпоративного периметра. Нет доступа наружу? Нет и Python. На ноутбуке с интернетом ты даже не заметишь, а в закрытом CI оно встанет.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;error: Failed to install cpython-3.12.12-linux-x86_64-gnu
  Caused by: Request failed after 3 retries
  Caused by: Failed to download https://github.com/astral-sh/python-build-standalone/releases/download/20260203/cpython-3.12.12%2B20260203-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz
  Caused by: error sending request for url (https://github.com/astral-sh/python-build-standalone/releases/download/20260203/cpython-3.12.12%2B20260203-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz)
  Caused by: operation timed out&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Фикс есть, и он штатный. Переменная &lt;code&gt;UV_PYTHON_INSTALL_MIRROR&lt;/code&gt; переписывает адрес, откуда uv берёт релизы, на ваше внутреннее зеркало (для PyPy исторически отдельная переменная, CPython'овская его не покрывала). То же самое кладётся в &lt;code&gt;[tool.uv]&lt;/code&gt;, чтобы не зависеть от того, что выставлено в окружении.&lt;/p&gt;

&lt;p&gt;Зеркало PyPI это не покроет: python-build-standalone живёт вне индекса пакетов, так что ему нужно отдельное зеркало. У нас это решилось без экзотики: подняли в Nexus проксирующий репозиторий для этих релизов и направили &lt;code&gt;UV_PYTHON_INSTALL_MIRROR&lt;/code&gt; на него. Рядом всё равно настраивается &lt;code&gt;HTTP(S)_PROXY&lt;/code&gt; и внутренний индекс пакетов, без этого история неполная.&lt;/p&gt;

&lt;h3&gt;3. Lock: параллельные установки теперь сериализуются&lt;/h3&gt;

&lt;p&gt;Кеш у uv &lt;a href=&quot;https://docs.astral.sh/uv/concepts/cache/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;concurrency-safe&lt;/a&gt;: append-only, безопасен для параллельных читателей и писателей. Блокируется venv на время установки и сборку отдельных артефактов, чтобы их не перезаписали на полпути. pip так не делал, и параллельные установки в одно окружение у него теоретически могли сломать друг друга.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;× Failed to download and build `pyyaml==6.0.3`
├─▶ Failed to acquire lock on the distribution cache
├─▶ Could not acquire lock
╰─▶ Timeout (300s) when waiting for lock on `/var/lib/jenkins/.cache/uv/sdists-v9/pypi/pyyaml/6.0.3` at `/var/lib/jenkins/.cache/uv/sdists-v9/pypi/pyyaml/6.0.3/.lock`, is another uv process running? You can set `UV_LOCK_TIMEOUT` to increase the timeout.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;У нас это вылезло там, где не ждали. На одном Jenkins-раннере может крутиться несколько джоб за раз. Раньше pip на это было плевать. Теперь они упираются в один lock, встают в очередь и начинают ловить таймауты.&lt;/p&gt;

&lt;p&gt;Можно сказать, что так честнее: pip ведь мог сломаться тихо, а тут хотя бы видимый таймаут. Только на практике эти таймауты у нас стреляли заметно чаще(пару раз в день), чем когда-либо выстрелил бы исходный гипотетический race. Вывод простой: либо стараешься сократить параллельный доступ, либо поднимаешь &lt;code&gt;UV_LOCK_TIMEOUT&lt;/code&gt;. Мы сделали и то, и другое.&lt;/p&gt;

&lt;h3&gt;4. Порядок индексов: first-index по умолчанию&lt;/h3&gt;

&lt;p&gt;uv по умолчанию ходит в режиме &lt;code&gt;first-index&lt;/code&gt;: берёт версии только из первого индекса, где пакет вообще нашёлся, и дальше не ищет. pip так не умеет, pip собирает кандидатов отовсюду и выбирает «лучшего». Звучит как регресс по удобству. На деле это защита от dependency confusion, той самой атаки, на которой в декабре 2022 погорел torchtriton: подсовываешь в публичный индекс пакет с именем внутреннего, и сборка тянет чужое вместо твоего.&lt;/p&gt;

&lt;p&gt;Вернуть pip-подобное поведение можно через &lt;code&gt;--index-strategy&lt;/code&gt; или &lt;code&gt;UV_INDEX_STRATEGY&lt;/code&gt;. Вариантов «небезопасно» тут, кстати, два, а не один: &lt;code&gt;unsafe-first-match&lt;/code&gt; и &lt;code&gt;unsafe-best-match&lt;/code&gt;. Второй ближе всего к pip, и он снова даёт возможность dependency confusion.&lt;/p&gt;

&lt;p&gt;У нас это осталось теорией: репозитории корпоративные, проблемы dependency confusion решаются на уровне выше, так что на проде мы просто вернули привычное поведение. Но мотив дефолта понятен и, в целом, верный. Ровно это пытаются стандартизировать в &lt;a href=&quot;https://peps.python.org/pep-0766/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PEP 766&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;5. Компиляция в байткод выключена по умолчанию, холодный старт медленнее pip&lt;/h3&gt;

&lt;p&gt;pip при установке компилировал &lt;code&gt;.py&lt;/code&gt; в &lt;code&gt;.pyc&lt;/code&gt; сразу. uv по умолчанию этого не делает: компиляция ленивая, при первом импорте. На практике это значит, что первый запрос к свежезадеплоенному сервису чуть медленнее, пока байткод не прогрелся. В проде с этим столкнуться не успели - этот момент поймал на чтении доки. Ну, если быть честным, подобных latency-critical Python-сервисов в моей сфере ответственности нет.&lt;/p&gt;

&lt;p&gt;Фикс на один флаг: &lt;code&gt;--compile-bytecode&lt;/code&gt; или &lt;a href=&quot;https://docs.astral.sh/uv/reference/settings/#compile-bytecode&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;UV_COMPILE_BYTECODE=1&lt;/code&gt;&lt;/a&gt;. Astral прямым текстом советуют включать его в Docker-сборках: сборка станет чуть дольше, зато прод не платит этот штраф на каждом холодном старте. А раз включать его всё равно советуют для всего, что идёт в прод, трудно не заметить, что «выключено» по умолчанию заодно не портит ту самую цифру в бенчмарке установки, которую все и меряют...&lt;/p&gt;

&lt;p&gt;Что не дотянуло до отдельного раздела (не споткнулись об это), но знать стоит: build isolation по PEP 517 включена по умолчанию.&lt;/p&gt;

&lt;h2&gt;Что в плюсе, помимо скорости&lt;/h2&gt;

&lt;p&gt;Помимо скорости uv привёз вполне осязаемые вещи.&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Скорость. Да.&lt;/strong&gt; Ха-ха, опять она, ~10x. Я упоминал, что утилита написана на Rust? Самое время упомянуть.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Управляемый Python.&lt;/strong&gt; Штука честно хорошая, но со звёздочкой: хорошая после того, как поднято зеркало. До зеркала это боль из пункта 2, после зеркала «один раз настроили и забыли».&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Сборка и публикация через uv.&lt;/strong&gt; Часть пакетов перевели на сборку и публикацию командами uv: легко заменили Twine, один инструмент на месте нескольких.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;article-figure&quot;&gt;&lt;img src=&quot;https://tsv.one/uv-replaces-toolchain.webp&quot; width=&quot;1200&quot; height=&quot;630&quot; alt=&quot;pip, pip-tools, virtualenv, pyenv, pipx и twine - каждый заменяется одной командой uv.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;figcaption&gt;Шесть привычных утилит, и все теперь одна команда uv. Не главная причина переезжать, но приятная.&lt;/figcaption&gt;&lt;/figure&gt;

&lt;h2&gt;Итог&lt;/h2&gt;

&lt;p&gt;Брать, если у вас сколько-нибудь живое Python-окружение (относительно часто обновляетесь и не застряли в прошлом) и болит время установки: выигрыш реальный, окупается быстро, не пожалели. Подождать (точнее, заложить время на интеграцию), если сидите в закрытом контуре с приватными индексами.&lt;/p&gt;

&lt;p class=&quot;article-callout is-key&quot;&gt;&lt;strong class=&quot;article-callout-label&quot;&gt;Правило.&lt;/strong&gt; Почти каждый дефолт uv, который удивил, оказался намеренной строгостью. Так что миграция это «найти все места, где старый workflow опирался на доброту pip». uv добрым не будет.&lt;/p&gt;

&lt;p&gt;За три месяца я не нашёл ни одного сюрприза, который оказался бы просто кривым решением. Немного неприятно, но полезно.&lt;/p&gt;

&lt;p&gt;И последняя особенность, уже не про код: Astral, венчурный стартап без публичной модели монетизации, недавно куплен OpenAI. Проект открытый, но держать в уме стоит.&lt;/p&gt;

&lt;p&gt;Оценка миграции: 8.5/10. Полтора балла снял не за uv, а за специфику собственного контура, который пришлось подготавливать.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Зачем это всё</title>
    <id>https://tsv.one/ru/articles/why-this-blog</id>
    <link rel="alternate" type="text/html" href="https://tsv.one/ru/articles/why-this-blog"/>
    <published>2026-06-13T12:00:00+03:00</published>
    <updated>2026-07-25T01:37:31+03:00</updated>
    <summary type="text">Первый пост: кто я, что тут будет и зачем. Инструкции, личный опыт и разбор сделанного, без претензии на истину в последней инстанции.</summary>
    <category term="блог"/>
    <category term="личный сайт"/>
    <category term="инфраструктура"/>
    <category term="DevOps"/>
    <content type="html">&lt;p&gt;У меня есть GitHub, забитый репозиториями, и этот сайт, который я переписываю заметно чаще, чем
    стоило бы. А вот места, где можно спокойно рассказать, зачем всё это затевалось и что по дороге
    сломалось (&lt;a href=&quot;https://tsv.one/ru/articles/uv-in-production-caveats&quot;&gt;например&lt;/a&gt;), не нашлось. Исправляю.&lt;/p&gt;

&lt;p&gt;Если коротко и без пафоса, я инфраструктурщик, грейд миддл. Серверы, CI/CD, контейнеры,
    немного Go и Python. Скучные подробности живут в &lt;a href=&quot;https://tsv.one/ru/resume&quot;&gt;резюме&lt;/a&gt;, тут не оно. На
    десктопе Arch + репы CachyOS (да, i use Arch btw; теперь это даже задокументировано).&lt;/p&gt;

&lt;p&gt;О чём это всё будет, примерно в порядке убывания частоты:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;заметки про то, как я что-то поднял, настроил или починил, и что при этом пошло не так (а
        что-то всегда идёт);&lt;/li&gt;
    &lt;li&gt;личный опыт из работы и пет-проектов, без причёсывания под success story;&lt;/li&gt;
    &lt;li&gt;разборы в духе «смотрите, что собрал», иногда просто чтобы самому через полгода вспомнить
        как.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Сразу про планку. Всё здесь не в духе «делайте как я, я знаю
    как правильно», а скорее «вот как я попробовал, в основном заработало, остальное вкусовщина». Мнение
    не окончательное, местами наверняка спорное, и через полгода я с частью сам не соглашусь. И это
    нормально. Когда знаешь, что прочитают, проверяешь себя внимательнее. Это также означает, что часть статей может быть изменена/дописана до версии, которая покажется лучше будущему-мне.&lt;/p&gt;

&lt;p&gt;Сам сайт это отдельная история. Он для меня не только визитка, но и полигон: кастомный CSS, ванильный
    JS, ноль фреймворков и почти болезненная гонка за размером, когда выпиливаешь байты, которых, кроме
    тебя, никто не заметит. Оптимизировать до последнего килобайта страницу, которую смотрит полтора
    человека, польза сомнительная, но мне нравится, и попутно понимаешь, как оно всё работает под
    капотом. Исходники открыты, если любопытно:
    &lt;a href=&quot;https://github.com/hekzory/hekzory.github.io&quot; target=&quot;_blank&quot;
        rel=&quot;noopener&quot;&gt;github.com/hekzory/hekzory.github.io&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;article-figure&quot;&gt;&lt;img src=&quot;https://tsv.one/why-this-blog-build.webp&quot; width=&quot;807&quot; height=&quot;617&quot; alt=&quot;Терминал с командой pnpm build: Vite выводит каждый файл с обычным и gzip-размером, сборка заняла меньше секунды.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;figcaption&gt;Одна сборка этого самого сайта. Размер каждого файла - это то самое число, которое я всё пытаюсь уменьшить.&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Будет ли это полезно кому-то ещё? Возможно. Будет ли полезно мне? Точно - хотя бы как внешняя
    память.&lt;/p&gt;

&lt;p&gt;В общем, поехали.&lt;/p&gt;</content>
  </entry>
</feed>
