<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>@hikkidg</title><author><name>@hikkidg</name></author><id>https://teletype.in/atom/hikkidg</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/hikkidg?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/hikkidg?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-07-04T09:34:23.693Z</updated><entry><id>hikkidg:microservice-arch-theory-and-practice</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/microservice-arch-theory-and-practice?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>Микросервисы - про границы, а не про масштабирование</title><published>2026-03-11T23:47:27.179Z</published><updated>2026-03-11T23:47:27.179Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/40/dd/40dd31c8-c01d-41fa-aa53-7e4dfce4bbe5.png"></media:thumbnail><category term="architecture" label="Architecture"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/0b/1c/0b1c01cb-1df9-4045-b246-3b2e8e3a2be3.png&quot;&gt;Микросервисы часто начинают внедрять на ранних этапах проекта, чтобы «подготовиться к масштабированию», но на практике это нередко создаёт больше проблем, чем решает. В этой статье я рассказываю реальную историю из стартапа, где мы начали дробить Django-монолит на микросервисы ещё до выхода продукта в продакшен. Архитектура, которая сначала выглядела аккуратной, довольно быстро превратилась в распределённый монолит с размытыми границами и дублирующейся логикой. Я разбираю, как мы к этому пришли, какие проблемы это вызвало и что потребовалось, чтобы постепенно привести систему к более понятным границам сервисов.</summary><content type="html">
  &lt;p id=&quot;QO2N&quot;&gt;Несколько лет назад меня позвали в стартап переписывать Django-монолит на микросервисы.&lt;/p&gt;
  &lt;p id=&quot;87nE&quot;&gt;Монолит при этом не работал в продакшене. Это даже нельзя было назвать MVP — реализовано было совсем немного, часть сценариев не закрыта, тестов почти не было.&lt;/p&gt;
  &lt;p id=&quot;ogFB&quot;&gt;Поэтому решение “срочно перейти на микросервисы” выглядело… неожиданным.&lt;/p&gt;
  &lt;p id=&quot;rexd&quot;&gt;Просто в какой-то момент техлид решил (а менеджмент поддержал), что нам нужно сразу строить правильную архитектуру — чтобы потом не переделывать (спойлер: 3 раза “не переделывали”).&lt;/p&gt;
  &lt;p id=&quot;kZQo&quot;&gt;Он ушел делать отдельный сервис с ключевой бизнес-логикой. Я же начал переписывать монолит:&lt;/p&gt;
  &lt;ul id=&quot;KYjF&quot;&gt;
    &lt;li id=&quot;XP4z&quot;&gt;перевел его на FastAPI&lt;/li&gt;
    &lt;li id=&quot;KsDQ&quot;&gt;сделал из него BFF&lt;/li&gt;
    &lt;li id=&quot;Akp6&quot;&gt;начал покрывать тестами&lt;/li&gt;
    &lt;li id=&quot;01pj&quot;&gt;и параллельно начал еще выделять из него отдельный сервис.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;gLrF&quot;&gt;Примерно то же самое происходило у техлида: начав писать один сервис, он понял, что “чтобы не раздувать”, надо выделить еще один. В итоге мы вроде как двигались к “микросервисной архитектуре”.&lt;/p&gt;
  &lt;figure id=&quot;oVxX&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0b/1c/0b1c01cb-1df9-4045-b246-3b2e8e3a2be3.png&quot; width=&quot;2596&quot; /&gt;
    &lt;figcaption&gt;Рис. 1. С чего мы начинали&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;EfzE&quot;&gt;Что получилось через несколько месяцев?&lt;/h2&gt;
  &lt;p id=&quot;ZmiJ&quot;&gt;У нас стало 4 сервиса.&lt;/p&gt;
  &lt;p id=&quot;ljZX&quot;&gt;Формально — красиво:&lt;/p&gt;
  &lt;ul id=&quot;JEDz&quot;&gt;
    &lt;li id=&quot;uvhm&quot;&gt;разные репозитории&lt;/li&gt;
    &lt;li id=&quot;Kifb&quot;&gt;разные пайплайны&lt;/li&gt;
    &lt;li id=&quot;zBkq&quot;&gt;отдельные деплои.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;NbE0&quot;&gt;Это же главные критерии микросервисной архитектуре!&lt;/p&gt;
  &lt;p id=&quot;CPrx&quot;&gt;По факту — между ними постоянно асинхронно и синхронно летали одни и те же сущности: User, Profile, Wallet, Order, Subscription и еще пара вспомогательных моделей.&lt;/p&gt;
  &lt;p id=&quot;ImuR&quot;&gt;Сервис A знал детали статусов из сервиса B.&lt;/p&gt;
  &lt;p id=&quot;6xaW&quot;&gt;Сервис B частично модифицировал данные сервиса C и посылал их обратно.&lt;/p&gt;
  &lt;p id=&quot;mjaJ&quot;&gt;BFF проксировал логику, которую по идее не должен был знать, но она была так размыта между роутами, что одинаковая логика расчета была и в сервисе аналитики и в самом BFF.&lt;/p&gt;
  &lt;p id=&quot;5M8s&quot;&gt;Сначала это выглядело не критично. Понятно что есть проблема, но это небольшой техдолг это же нормально. Сейчас надо сделать побыстрее потом вернемся и переделаем. Стартап камон!&lt;/p&gt;
  &lt;p id=&quot;F5C0&quot;&gt;Немного HTTP-запросов туда-сюда для синхронизации. Пара раздутых моделей в событиях брокера. Ничего страшного.&lt;/p&gt;
  &lt;p id=&quot;4a1h&quot;&gt;Но через полгода любое изменение стало занимать 2-3 раза больше времени, чем должно было.&lt;/p&gt;
  &lt;p id=&quot;sL4z&quot;&gt;Мы уже не могли точно предсказать, где всплывет побочный эффект, какой контракт придется менять, и кто вообще “владеет” конкретной бизнес-логикой.&lt;/p&gt;
  &lt;p id=&quot;OYbp&quot;&gt;В какой-то момент надо пойти потрогать траву, вернуться свежим взглядом к нашим сервисам и признать: “Мы построили распределенный монолит”.&lt;/p&gt;
  &lt;figure id=&quot;MhbT&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9b/31/9b31c2c0-8788-40fa-8ec7-935cbb998970.png&quot; width=&quot;2152&quot; /&gt;
    &lt;figcaption&gt;На самом деле этот ужас выглядит еще хуже, тут только часть связей&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;JvgS&quot;&gt;Что такое распределенный монолит в реальности?&lt;/h2&gt;
  &lt;p id=&quot;AuVd&quot;&gt;Буквально то, что у нас получилось:&lt;/p&gt;
  &lt;ul id=&quot;Iiv7&quot;&gt;
    &lt;li id=&quot;cgM3&quot;&gt;границы ответственности размыты&lt;/li&gt;
    &lt;li id=&quot;KbSN&quot;&gt;доменная модель размазана по нескольким сервисам&lt;/li&gt;
    &lt;li id=&quot;LDhQ&quot;&gt;изменения требуют синхронного обновления нескольких компонентов.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;bdtk&quot;&gt;Сетевая сложность появляется. Архитектурная изоляция отсутствует.&lt;/p&gt;
  &lt;p id=&quot;2z9y&quot;&gt;Нам удалось сесть на худшие стулья из двух миров:&lt;/p&gt;
  &lt;ul id=&quot;5gW9&quot;&gt;
    &lt;li id=&quot;vmKn&quot;&gt;у нас распределенная система со всей ее сложностью&lt;/li&gt;
    &lt;li id=&quot;syYy&quot;&gt;у нас связность монолита, когда буквально добавление или удаление поля в каком-нибудь классе заставляет нас каскадно проверять все импорты этого класса по репозиторию, в нашем случае вызывает такую же лавину изменений только во всех наших сервисах (репозиториях)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;BrTa&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1f/4d/1f4d1df5-0e11-4eb4-99d3-f0a9c1372dc8.png&quot; width=&quot;2562&quot; /&gt;
    &lt;figcaption&gt;Как это выглядит концептуально&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;YKar&quot;&gt;Как мы из этого выбирались?&lt;/h2&gt;
  &lt;p id=&quot;78qN&quot;&gt;Начался долгий и неприятный процесс рефакторинга.&lt;/p&gt;
  &lt;p id=&quot;ajra&quot;&gt;Это заняло не спринт и не два. В условиях постоянно меняющихся требований в стартапе и добавления новых функций для клиентов прошло больше года с начала наших исправлений этой ситуации.&lt;/p&gt;
  &lt;p id=&quot;4wGl&quot;&gt;Мы вырезали куски логики в отдельные сервисы (в какой-то момент у нас было 7 сервисов, потом мы сократили их до 5 обобщая логику), запретили менять “чужие” данные напрямую, пересмотрели границы доменов, убрали пересечения по сущностям.&lt;/p&gt;
  &lt;p id=&quot;sBwd&quot;&gt;По-настоящему “чистыми” микросервисами у нас сейчас можно назвать только 2 из 5. Они прошли несколько архитектурных итераций. Сначала были написаны на Python. Когда мы убедились, что правильно все выделили, доменная модель не меняется — переписали их на Go.&lt;/p&gt;
  &lt;p id=&quot;npZB&quot;&gt;Сейчас это:&lt;/p&gt;
  &lt;ul id=&quot;s9Aq&quot;&gt;
    &lt;li id=&quot;X2sp&quot;&gt;~4-5k строк бизнес-кода&lt;/li&gt;
    &lt;li id=&quot;Aui3&quot;&gt;~5-7k с тестами&lt;/li&gt;
    &lt;li id=&quot;D46m&quot;&gt;80% покрытие,&lt;/li&gt;
    &lt;li id=&quot;hUmu&quot;&gt;метрики Prometheus,&lt;/li&gt;
    &lt;li id=&quot;QzDO&quot;&gt;четкая зона ответственности.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;blockquote id=&quot;87sV&quot;&gt;Касательно последнего пункта: я раньше со скепсисом относился к критерию, что у хорошего микросервиса как у класса должна быть маленькая зона ответственности, которую можно сформулировать одним абзацем, но это действительно работает. Главное не использовать при описании общие слова типа “отвечает за пользовательский флоу” или “реализует ключевую бизнес-логику”, а использовать термины из вашего бизнес-домена, который вы моделируете с помощью программного кода “отвечает за жизненный цикл ордера: создание, отмену, изменение параметров, отслеживание статусов исполнение”, “рассчитывает ежедневную торговую статистику пользователей: оборот, количество сделок и средний размер позиции”.&lt;/blockquote&gt;
  &lt;p id=&quot;yVz1&quot;&gt;Остальные сервисы все еще требуют переработки. Там остаются пересечения по сущностям, перегруженная логика и накопленный техдолг. И мы понимаем, что на это уйдет еще время.&lt;/p&gt;
  &lt;figure id=&quot;hr8e&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8d/0c/8d0cbb0a-3a06-4c81-985f-7df80a7e17a5.png&quot; width=&quot;1862&quot; /&gt;
    &lt;figcaption&gt;Отрефакторенная часть нашей архитектуры&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;pUhy&quot;&gt;Написать микросервис — несложно. Сложно сделать его устойчивым к росту бизнеса.&lt;/p&gt;
  &lt;p id=&quot;kpdK&quot;&gt;Границы нельзя придумать на пустом месте. Они становятся очевидными только после того, как система проживет реальные сценарии и ошибки.&lt;/p&gt;
  &lt;p id=&quot;qADu&quot;&gt;Я пока не видел, чтобы микросервисная архитектура “идеально сложилась” с первого раза даже у сильных команд.&lt;/p&gt;
  &lt;p id=&quot;89tV&quot;&gt;Заранее подстелить себе солому с помощью микросервисов не получится. Подумайте лучше в сторону модульного монолита.&lt;/p&gt;

</content></entry><entry><id>hikkidg:python-go-decimal</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/python-go-decimal?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>🧮 Финансовые расчёты в Python и Go: как не потерять деньги из-за одного нуля или кавычек</title><published>2026-02-28T15:57:10.032Z</published><updated>2026-02-28T18:13:56.549Z</updated><summary type="html">Недавно я ловил баг в функции расчёта PNL. Из-за неправильного использования типов ошибка в расчетах накапливалась сначала в виде погрешности, а потом переросло в серьезную ошибку для пользователя. Для решения нужно было просто использовать правильный тип данных для финансовых расчетов. За этим багом скрывается целый мир подводных камней, о которых я давно не вспоминал и хочу рассказать.</summary><content type="html">
  &lt;nav&gt;
    &lt;ul&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#gcCL&quot;&gt;🐍 Python: лучший вариант работать через встроенный `decimal` &lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#PQNh&quot;&gt;Инициализация Decimal&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#aVTw&quot;&gt;Управление округлением: метод quantize()&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#6ZpL&quot;&gt;Контекст выполнения&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#Daxg&quot;&gt;Сравнение значений&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#Z1ry&quot;&gt;🐹 Go: точность под высокими нагрузками&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#ra8D&quot;&gt;shopspring/decimal&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#BYbH&quot;&gt;govalues/decimal&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#BqAV&quot;&gt;💡 Выводы&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/nav&gt;
  &lt;p id=&quot;VpP0&quot;&gt;Недавно я ловил баг в функции расчёта PNL. Из-за неправильного использования типов ошибка в расчетах накапливалась сначала в виде погрешности, а потом переросло в серьезную ошибку для пользователя. Для решения нужно было просто использовать правильный тип данных для финансовых расчетов. За этим багом скрывается целый мир подводных камней, о которых я давно не вспоминал и хочу рассказать.&lt;/p&gt;
  &lt;h2 id=&quot;gcCL&quot;&gt;🐍 Python: лучший вариант работать через встроенный &amp;#x60;decimal&amp;#x60; &lt;/h2&gt;
  &lt;p id=&quot;zbfo&quot;&gt;Если вам нужны расчеты до копеек (2-х знаков после запятой), то самый простой вариант использовать &lt;code&gt;int&lt;/code&gt;. Это наиболее эффективно с точки зрения производительности, точности и хранения в БД.&lt;/p&gt;
  &lt;p id=&quot;wqT9&quot;&gt;Если нам нужна большая точность, то определенно наш выбор - &lt;code&gt;decimal.Decimal&lt;/code&gt;. &lt;/p&gt;
  &lt;p id=&quot;4zpT&quot;&gt;На тему &amp;quot;почему &lt;code&gt;float&lt;/code&gt; это плохо для точных расчетов?&amp;quot; написано очень много материалов. Достаточно вспомнить, что в Python:&lt;/p&gt;
  &lt;pre id=&quot;f1Bo&quot; data-lang=&quot;python&quot;&gt;0.1 + 0.1 + 0.1 == 0.3  # False&lt;/pre&gt;
  &lt;p id=&quot;Pwko&quot;&gt;и больше про &lt;code&gt;float&lt;/code&gt; не вспоминать.&lt;/p&gt;
  &lt;h3 id=&quot;PQNh&quot;&gt;Инициализация &lt;code&gt;Decimal&lt;/code&gt;&lt;/h3&gt;
  &lt;p id=&quot;hEsK&quot;&gt;Главное правило - создавать &lt;code&gt;Decimal&lt;/code&gt; через строку (&lt;code&gt;Decimal(&amp;#x27;0.1&amp;#x27;)&lt;/code&gt;) или &lt;code&gt;int&lt;/code&gt; (&lt;code&gt;Decimal(1)&lt;/code&gt;), а не через число с плавающей точкой (&lt;code&gt;Decimal(0.1)&lt;/code&gt;). Этот способ убивает всю магию точности еще до создания объекта.&lt;/p&gt;
  &lt;pre id=&quot;2bFm&quot; data-lang=&quot;python&quot;&gt;from decimal import Decimal
price = Decimal(&amp;#x27;19.99&amp;#x27;)   # ✅ правильно
tax_rate = Decimal(&amp;#x27;0.08&amp;#x27;) # ✅ правильно
total = price * (1 + tax_rate)
print(total)  # 21.5892

price = Decimal(19.99)   # ❌ неправильно
tax_rate = Decimal(0.08) # ❌ неравильно
total = price * (1 + tax_rate)
print(total)  # 21.58919999999999834504049723&lt;/pre&gt;
  &lt;h3 id=&quot;aVTw&quot;&gt;Управление округлением: метод &lt;code&gt;quantize()&lt;/code&gt;&lt;/h3&gt;
  &lt;p id=&quot;DxDX&quot;&gt;Главный инструмент для приведения чисел к нужной точности (например, нужно округлить до 2-х знаков после запятой). не забывайте указывать стратегию округления при необходимости.&lt;/p&gt;
  &lt;pre id=&quot;JaUt&quot; data-lang=&quot;python&quot;&gt;from decimal import Decimal, ROUND_HALF_UP
value = Decimal(&amp;#x27;10.125&amp;#x27;)
value.quantize(Decimal(&amp;#x27;0.01&amp;#x27;), rounding=ROUND_HALF_UP)  # 10.13&lt;/pre&gt;
  &lt;h3 id=&quot;6ZpL&quot;&gt;Контекст выполнения&lt;/h3&gt;
  &lt;p id=&quot;1sA9&quot;&gt;Если нужно управления точностью расчетов во всем приложении, используйте &lt;code&gt;getcontext().prec&lt;/code&gt; глобально при старте приложения.&lt;/p&gt;
  &lt;pre id=&quot;U5WG&quot; data-lang=&quot;python&quot;&gt;from decimal import Decimal, getcontext
getcontext().prec = 20
print(getcontext().prec) # 20&lt;/pre&gt;
  &lt;p id=&quot;MDHp&quot;&gt;Если тебе нужно временно повысить точность для сложного расчета, используйте менеджер контекста &lt;code&gt;with localcontext()&lt;/code&gt;. Это безопасно изолирует изменения от остального приложения.&lt;/p&gt;
  &lt;pre id=&quot;hVZC&quot; data-lang=&quot;python&quot;&gt;from decimal import Decimal, localcontext

with localcontext() as ctx:
	ctx.prec = 27
	a = Decimal(&amp;#x27;123.45&amp;#x27;)
	b = Decimal(&amp;#x27;6.789&amp;#x27;)
	result = a * b&lt;/pre&gt;
  &lt;h3 id=&quot;Daxg&quot;&gt;Сравнение значений&lt;/h3&gt;
  &lt;p id=&quot;EFoZ&quot;&gt;&lt;code&gt;Decimal&lt;/code&gt; умный и сравнение с другими типами поддерживает, но такие операции могут дать непредсказуемый результат, поэтому всегда используем с &lt;code&gt;Decimal&lt;/code&gt; другие &lt;code&gt;Decimal&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;dFBg&quot; data-lang=&quot;python&quot;&gt;Decimal(&amp;quot;10.0&amp;quot;) == 10.0  # True
Decimal(&amp;quot;10.01&amp;quot;) == 10.01  # False
Decimal(&amp;quot;10.01&amp;quot;) == Decimal(10.01) # False, потому что см. про инициализацию&lt;/pre&gt;
  &lt;p id=&quot;jteR&quot;&gt;Для предсказуемости результатов в цепочках вычислений также важно использовать константы с тем же типом &lt;code&gt;Decimal&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;AztW&quot; data-lang=&quot;python&quot;&gt;ZERO = Decimal(&amp;#x27;0&amp;#x27;)
ONE = Decimal(&amp;#x27;1&amp;#x27;)&lt;/pre&gt;
  &lt;h2 id=&quot;Z1ry&quot;&gt;🐹 Go: точность под высокими нагрузками&lt;/h2&gt;
  &lt;p id=&quot;xXPF&quot;&gt;В Go нет встроенного &lt;code&gt;decimal&lt;/code&gt;. Стандартный &lt;code&gt;float64&lt;/code&gt; такой же неточный, как в Python. Аналогично если есть возможность использовать integer scaling, то это лучший вариант. В противном случае выбираем стороннюю библиотеку. И тут выбор встает между скоростью и удобством.&lt;/p&gt;
  &lt;h3 id=&quot;ra8D&quot;&gt;shopspring/decimal&lt;/h3&gt;
  &lt;p id=&quot;yA37&quot;&gt;Самая популярная библиотека - &lt;a href=&quot;https://github.com/shopspring/decimal&quot; target=&quot;_blank&quot;&gt;shopspring/decimal&lt;/a&gt;. Она простая и удобная, но под капотом использует &lt;code&gt;math/big&lt;/code&gt;, что приводит к аллокациям в куче и доп расходам на GC, что может быть критично для high-load.&lt;/p&gt;
  &lt;h3 id=&quot;BYbH&quot;&gt;govalues/decimal&lt;/h3&gt;
  &lt;p id=&quot;wdEy&quot;&gt;Недавно наткнулся на &lt;a href=&quot;https://github.com/govalues/decimal&quot; target=&quot;_blank&quot;&gt;govalues/decimal&lt;/a&gt;. Она создана специально для транзакционных финансовых систем. Фишки:&lt;/p&gt;
  &lt;ul id=&quot;gByH&quot;&gt;
    &lt;li id=&quot;Hv8C&quot;&gt;Иммутабельность - безопасна для конкурентного доступа&lt;/li&gt;
    &lt;li id=&quot;PnNi&quot;&gt;Нулевые аллокации - все операции без выделения памяти в куче&lt;/li&gt;
    &lt;li id=&quot;TSAd&quot;&gt;Без паник - все ошибки возвращаются явно.&lt;/li&gt;
    &lt;li id=&quot;CzTD&quot;&gt;Банковское округление&lt;/li&gt;
    &lt;li id=&quot;fSRq&quot;&gt;Поддержка до 19 десятичных знаков - хватит для большинства валют (и крипты)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;ei8g&quot;&gt;Пример:&lt;/p&gt;
  &lt;pre id=&quot;FxMi&quot; data-lang=&quot;go&quot;&gt;package main

import (
    &amp;quot;fmt&amp;quot;
    &amp;quot;github.com/govalues/decimal&amp;quot;
)

func main() {
    // Инициализация из строк — тоже безопасно
    price, _ := decimal.Parse(&amp;quot;19.99&amp;quot;)
    taxRate, _ := decimal.Parse(&amp;quot;0.08&amp;quot;)
    
    // Вычисления
    tr, _ := taxRate.Add(decimal.One)
    total, _ := price.Mul(tr)
    fmt.Println(total.Round(2)) // &amp;quot;21.59&amp;quot;
}
&lt;/pre&gt;
  &lt;p id=&quot;o62K&quot;&gt;По бенчмаркам &lt;code&gt;govalues&lt;/code&gt; быстрее &lt;code&gt;shopspring&lt;/code&gt; и не создает мусора. Если в вашем сервисе много операций по расчетам - присмотритесь.&lt;/p&gt;
  &lt;h2 id=&quot;BqAV&quot;&gt;💡 Выводы&lt;/h2&gt;
  &lt;ul id=&quot;jL4d&quot;&gt;
    &lt;li id=&quot;KiXk&quot;&gt;Используйте &lt;strong&gt;специализированные&lt;/strong&gt; типы в любом языке&lt;/li&gt;
    &lt;li id=&quot;EGdI&quot;&gt;В Python - &lt;code&gt;Decimal&lt;/code&gt; со строковой инициализацией, явным округлением и типобезопасными константами&lt;/li&gt;
    &lt;li id=&quot;NZHS&quot;&gt;В Go - выбирайте решение под нагрузку:&lt;/li&gt;
    &lt;ul id=&quot;sdmx&quot;&gt;
      &lt;li id=&quot;sQb7&quot;&gt;&lt;code&gt;int64&lt;/code&gt; если позволяет задача для максимальной скорости&lt;/li&gt;
      &lt;li id=&quot;oxwn&quot;&gt;&lt;code&gt;shopspring/decimal&lt;/code&gt; если нужное простое и массовое решение&lt;/li&gt;
      &lt;li id=&quot;PirX&quot;&gt;&lt;code&gt;govalues/decimal&lt;/code&gt; для производительности &lt;br /&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ul&gt;

</content></entry><entry><id>hikkidg:go-pprof</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/go-pprof?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>Как я отлавливал утечку памяти в высоконагруженном Go-сервисе</title><published>2026-01-25T23:13:48.510Z</published><updated>2026-01-25T23:18:57.726Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/fa/37/fa37032f-66fe-4b79-b1ac-baf34451b338.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/30/be/30beb82d-d7d3-4d65-8d47-5c3302cc2bfd.png&quot;&gt;Мы в компании постепенно переходим на Go, и одна из первых задач — переписать самые узки места, узлы, где качество решения влияет на всю систему целиком.</summary><content type="html">
  &lt;nav&gt;
    &lt;ul&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#HzBw&quot;&gt;Как возникла проблема?&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#KV7j&quot;&gt;Профилирование с помощью pprof&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#G8uR&quot;&gt;Команды, которые реально помогают&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#7Kj3&quot;&gt;Как смотреть профили из Kubernetes?&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#OoLL&quot;&gt;Что показал анализ?&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#yxN1&quot;&gt;Какие ошибки я нашёл в собственном коде?&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#xHts&quot;&gt;Наблюдаемость — не &quot;фича&quot;, а необходимость&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#VTvX&quot;&gt;Финальные мысли&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/nav&gt;
  &lt;h2 id=&quot;HzBw&quot;&gt;Как возникла проблема?&lt;/h2&gt;
  &lt;p id=&quot;MHOV&quot;&gt;Мы в компании постепенно переходим на Go, и одна из первых задач — переписать самые узки места, узлы, где качество решения влияет на всю систему целиком.&lt;/p&gt;
  &lt;p id=&quot;4nFI&quot;&gt;Недавно я взялся за Gateway-сервис, который стоит на входе: принимает запросы, подписывает его секретами, шлет дальше и возвращает ответ. Проще говоря, это прокси-слой между остальными сервисами и внешними API.&lt;/p&gt;
  &lt;p id=&quot;YGEa&quot;&gt;Раньше он у нас был на Python, но был заметно тяжел — потреблял сотни мегабайт памяти, медленно отрабатывал пиковые нагрузки и работал только на подпись запроса и не отправлял сам запрос, т. к. мы боялись, что если он полностью будет этим заниматься, то просто не справится (там надо было про горизонтальное масштабирование через процессы или поды, т. к. одного не хватало бы и т. д.). Поскольку скоро дело идет в продакшен, я переписал его на Golang с фреймворком &lt;code&gt;chi&lt;/code&gt;, сделал базовое кэширование и минимизировал бизнес-логику. Это также был хороший выбор для перевода на Go, т. к. у него был четко определен круг задач вокруг одной бизнес сущности и он целиком полностью отвечал только за нее — буквально одна таблица в БД, один репозиторий, один сервис. &lt;/p&gt;
  &lt;p id=&quot;yXjI&quot;&gt;Сначала это казалось победой: после запуска сервис едва потреблял &lt;strong&gt;20-30 МБ RAM&lt;/strong&gt;, что резко отличалось от прежних &lt;strong&gt;200-300 МБ&lt;/strong&gt;. Но к вечеру я заметил тревожный рост — через несколько часов работы сервис уже жрал почти &lt;strong&gt;1 ГБ памяти&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;tmRK&quot;&gt;Такое поведение было для нас неприемлемо. Понимать, что происходить во времени, практически невозможно простым наблюдением — нужны инструменты, которые покажут &lt;em&gt;почему&lt;/em&gt; память растёт.&lt;/p&gt;
  &lt;h2 id=&quot;KV7j&quot;&gt;Профилирование с помощью pprof&lt;/h2&gt;
  &lt;p id=&quot;sOzp&quot;&gt;Go уже из коробки включает мощный профайлер &lt;code&gt;pprof&lt;/code&gt;, который легко активируется и даёт очень подробную картину распределения памяти и поведения горутин.&lt;/p&gt;
  &lt;p id=&quot;16wI&quot;&gt;Добавить его в сервис можно буквально за пару строк:&lt;/p&gt;
  &lt;pre id=&quot;Vv31&quot; data-lang=&quot;go&quot;&gt;import _ &amp;quot;net/http/pprof&amp;quot;

func main(){
    go func() {
        http.ListenAndServe(&amp;quot;0.0.0.0:6060&amp;quot;, nil)
    }()
}&lt;/pre&gt;
  &lt;p id=&quot;mv69&quot;&gt;После этого в работающем сервисе становятся доступны эндпоинты:&lt;/p&gt;
  &lt;ul id=&quot;Dnno&quot;&gt;
    &lt;li id=&quot;PPew&quot;&gt;&lt;code&gt;/debug/pprof/heap&lt;/code&gt; — снимок текущего распределения памяти;&lt;/li&gt;
    &lt;li id=&quot;GGZk&quot;&gt;&lt;code&gt;/debug/pprof/allocs&lt;/code&gt; — распределение аллокаций;&lt;/li&gt;
    &lt;li id=&quot;DPq3&quot;&gt;&lt;code&gt;/debug/pprof/goroutine&lt;/code&gt; — текущие горутины;&lt;/li&gt;
    &lt;li id=&quot;q6eR&quot;&gt;&lt;code&gt;/debug/pprof/profile&lt;/code&gt; — CPU-профиль&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;H9EP&quot;&gt;Эти данные можно анализировать как локально, так и в браузере.&lt;/p&gt;
  &lt;h3 id=&quot;G8uR&quot;&gt;Команды, которые реально помогают&lt;/h3&gt;
  &lt;p id=&quot;h63V&quot;&gt;Анализ heap-снимка:&lt;/p&gt;
  &lt;pre id=&quot;osIN&quot; data-lang=&quot;bash&quot;&gt;go tool pprof http://localhost:6060/debug/pprof/heap&lt;/pre&gt;
  &lt;p id=&quot;asjQ&quot;&gt;В интерактивной консоли &lt;code&gt;pprof&lt;/code&gt; команды вроде &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;top -cum&lt;/code&gt;, &lt;code&gt;list &amp;lt;func&amp;gt;&lt;/code&gt; позволяют быстро найти функции, которые потребляют наибольшую часть памяти.&lt;/p&gt;
  &lt;p id=&quot;FHTd&quot;&gt;Для аллокаций используем:&lt;/p&gt;
  &lt;pre id=&quot;usma&quot; data-lang=&quot;bash&quot;&gt;go tool pprof http://localhost:6060/debug/pprof/allocs&lt;/pre&gt;
  &lt;p id=&quot;5Q74&quot;&gt;Это полезно, когда память вроде освобождается, но общее выделение растет со временем.&lt;/p&gt;
  &lt;p id=&quot;ctlx&quot;&gt;Чтобы посмотреть, сколько горутин живёт и где они зависают:&lt;/p&gt;
  &lt;pre id=&quot;ay33&quot;&gt;go tool pprof http://localhost:6060/debug/pprof/goroutine&lt;/pre&gt;
  &lt;p id=&quot;77OF&quot;&gt;А если хочется визуального представления, можно открыть граф вызовов прямо в браузере:&lt;/p&gt;
  &lt;pre id=&quot;mp8T&quot;&gt;go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap&lt;/pre&gt;
  &lt;h3 id=&quot;7Kj3&quot;&gt;Как смотреть профили из Kubernetes?&lt;/h3&gt;
  &lt;p id=&quot;KUJw&quot;&gt;Очень часть проблема проявляется только под нагрузкой в staging или даже в prod. Поскольку pprof слушает порт, его легко использовать внутри Kubernetes через перенаправление портов:&lt;/p&gt;
  &lt;pre id=&quot;rq6V&quot; data-lang=&quot;bash&quot;&gt;kubectl port-forward pod/your-pod 6060:6060&lt;/pre&gt;
  &lt;p id=&quot;y9au&quot;&gt;Локально становятся доступны профили по &lt;code&gt;localhost:6060&lt;/code&gt;, и дальше можно использовать те же команды &lt;code&gt;go tool pprof&lt;/code&gt; или открыть интерфейс в браузере. Это работает без изменения конфигурации ingress, без проброса портов наружу, просто через доступ к кластеру.&lt;/p&gt;
  &lt;h2 id=&quot;OoLL&quot;&gt;Что показал анализ?&lt;/h2&gt;
  &lt;p id=&quot;SjAx&quot;&gt;Сначала я думал, что дело в логике или в каком-то конкретном кэше. Однако &lt;code&gt;pprof&lt;/code&gt; наглядно показал, что память накапливается не из-за кода, который я писал, а из-за внешней библиотеки для Redis. Новая версия драйвера держала соединения открытыми и не освобождала их вовремя.&lt;/p&gt;
  &lt;p id=&quot;bdgz&quot;&gt;Как только я зафиксировал и вернул стабильную версию библиотеки (спасибо ишью на Github, где указали на какой версии все работает правильно) — рост памяти прекратился, и сервис занял устойчивое плато, колеблясь лишь на уровне обычного поведения GC.&lt;/p&gt;
  &lt;h3 id=&quot;yxN1&quot;&gt;Какие ошибки я нашёл в собственном коде?&lt;/h3&gt;
  &lt;p id=&quot;fcf4&quot;&gt;Пока искал корень проблемы, нашёл и собственные промахи:&lt;/p&gt;
  &lt;ul id=&quot;vDvv&quot;&gt;
    &lt;li id=&quot;6dys&quot;&gt;в некоторых местах &lt;code&gt;response.Body&lt;/code&gt; закрывался только при успешном результате — при ошибках тело оставалось висеть;&lt;/li&gt;
    &lt;li id=&quot;6Fuz&quot;&gt;я создавал HTTP клиент на каждый вызов функции, что просто заставляло выполнять лишнюю работу сервис и выделять на каждый запрос доп память.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;b6C4&quot;&gt;Да, это были мелочи, но даже такие &lt;em&gt;медленные утечки&lt;/em&gt; при нагрузке в production приводят к завышенному потреблению памяти и дополнительной работе сборщика мусора.&lt;/p&gt;
  &lt;h3 id=&quot;xHts&quot;&gt;Наблюдаемость — не &amp;quot;фича&amp;quot;, а необходимость&lt;/h3&gt;
  &lt;p id=&quot;sHla&quot;&gt;Здесь важно подчеркнуть: инструменты вроде &lt;code&gt;pprof&lt;/code&gt; помогают только в моменте, тогда как &lt;strong&gt;системный мониторинг&lt;/strong&gt; показывает динамику.&lt;/p&gt;
  &lt;p id=&quot;sCfC&quot;&gt;Я интегрировал в сервис Prometheus-метрики через мидлварь: статистика по вызовам роутов, текущее потребление памяти, скорость аллокаций, поведение GC и количество горутин. Эти метрики легко визуализируются в Grafana, и на графиках чётко видно, когда начинается рост, когда он останавливается, и как ведёт себя сервис с течением времени.&lt;/p&gt;
  &lt;p id=&quot;Svf2&quot;&gt;Такой подход к observability должен быть в любой команде, независимо от языка или инфраструктуры. Если вы не видите во времени, вы не сможете понять, что именно происходит.&lt;/p&gt;
  &lt;p id=&quot;jN6E&quot;&gt;Если вы не можете сделать даже этого, то хотя бы периодически снимайте метрики через &lt;code&gt;curl&lt;/code&gt; или &lt;code&gt;kubectl top pod &amp;lt;your-pod&amp;gt;&lt;/code&gt;. Вы уже сможете видеть подозрительное изменение ресурсов.&lt;/p&gt;
  &lt;figure id=&quot;5h7x&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/30/be/30beb82d-d7d3-4d65-8d47-5c3302cc2bfd.png&quot; width=&quot;1280&quot; /&gt;
    &lt;figcaption&gt;Наглядно видно, что левая часть уходит в космос, а правая после второго обновления на устойчивом плато&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;VTvX&quot;&gt;Финальные мысли&lt;/h2&gt;
  &lt;p id=&quot;oEwI&quot;&gt;Go действительно имеет мощные средства профилирования, и в большинстве случаев они удобнее, чем эквиваленты для Python. Но это не отменяет того, что хорошо бы &lt;strong&gt;задумываться о наблюдаемости еще до первой проблемы&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;8Wjv&quot;&gt;Наличие подробных метрик, возможность заглянуть в поведение сервиса под нагрузкой и быстро понять, где именно копится память — это то, что позволяет ловить проблемы раньше, чем они превратятся в инциденты на продакшене.&lt;/p&gt;

</content></entry><entry><id>hikkidg:git-github-ssh</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/git-github-ssh?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>🎯 Как работать с несколькими SSH-ключами в Git и GitHub (личный + рабочий аккаунт)</title><published>2025-12-02T07:57:27.410Z</published><updated>2025-12-02T07:57:27.410Z</updated><summary type="html">Если вы используете GitHub и у вас есть личный и рабочий аккаунты, то быстро появляется боль:
Git не понимает, какой SSH-ключ подставить, и начинает всё ломаться — от доступа к репо до установки зависимостей.</summary><content type="html">
  &lt;p id=&quot;KYnN&quot;&gt;Если вы используете GitHub и у вас есть &lt;strong&gt;личный&lt;/strong&gt; и &lt;strong&gt;рабочий&lt;/strong&gt; аккаунты, то быстро появляется боль:&lt;br /&gt;Git не понимает, &lt;strong&gt;какой SSH-ключ подставить&lt;/strong&gt;, и начинает всё ломаться — от доступа к репо до установки зависимостей.&lt;/p&gt;
  &lt;p id=&quot;kZXK&quot;&gt;Разбираем, как это решить красиво и автоматизировано.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;gnyH&quot;&gt;🔐 Проблема&lt;/h2&gt;
  &lt;p id=&quot;DZt7&quot;&gt;Git работает по SSH через ключи.&lt;br /&gt;Обычно всё просто:&lt;/p&gt;
  &lt;ol id=&quot;2fix&quot;&gt;
    &lt;li id=&quot;XC9T&quot;&gt;генерируете приватный + публичный ключ,&lt;/li&gt;
    &lt;li id=&quot;qeH9&quot;&gt;добавляете публичный в GitHub,&lt;/li&gt;
    &lt;li id=&quot;3Tjh&quot;&gt;клонируете по &lt;code&gt;git@github.com:user/repo.git&lt;/code&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;7wRq&quot;&gt;Но вот если аккаунтов &lt;strong&gt;несколько&lt;/strong&gt;, то возникает затык:&lt;br /&gt;Git &lt;strong&gt;не знает&lt;/strong&gt;, какой ключ выбрать для конкретного репозитория.&lt;/p&gt;
  &lt;p id=&quot;rIa0&quot;&gt;SSH ключи есть, но Git напрямую ими не управляет.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;FZOI&quot;&gt;✅ Шаг 1. Создаём alias для SSH&lt;/h2&gt;
  &lt;p id=&quot;gTii&quot;&gt;В файле &lt;code&gt;~/.ssh/config&lt;/code&gt; настраиваем «виртуальный сервер» — алиас, который будет использовать нужный ключ:&lt;/p&gt;
  &lt;pre id=&quot;YZVF&quot; data-lang=&quot;bash&quot;&gt;Host github-company
  HostName github.com
  User git
  IdentityFile ~/.ssh/your-company-private-key
  IdentitiesOnly yes&lt;/pre&gt;
  &lt;p id=&quot;3N1i&quot;&gt;Проверяем:&lt;/p&gt;
  &lt;pre id=&quot;7237&quot; data-lang=&quot;bash&quot;&gt;ssh -T git@github-company
# Hi &amp;lt;username&amp;gt;! You&amp;#x27;ve successfully authenticated, but GitHub does not provide shell access.&lt;/pre&gt;
  &lt;p id=&quot;YqYY&quot;&gt;Теперь можно клонировать репозитории через:&lt;/p&gt;
  &lt;pre id=&quot;oLYe&quot; data-lang=&quot;bash&quot;&gt;git clone git@github-company:company/repo.git&lt;/pre&gt;
  &lt;p id=&quot;Hw2o&quot;&gt;Но… так делать неудобно.&lt;/p&gt;
  &lt;h3 id=&quot;p9Ti&quot;&gt;❗ Почему&lt;/h3&gt;
  &lt;p id=&quot;qhSo&quot;&gt;Потому что внутри проекта могут быть другие SSH-ссылки, например в Python-зависимостях, которые тоже ведут на &lt;code&gt;git@github.com:company/...&lt;/code&gt;.&lt;br /&gt;И их приходится вручную менять на alias — иначе не скачивается.&lt;/p&gt;
  &lt;p id=&quot;8dMz&quot;&gt;Это ломает lock-файлы, требует ручных правок и очень легко случайно закоммитить.&lt;/p&gt;
  &lt;h2 id=&quot;2O8V&quot;&gt;✅ Шаг 2. Пусть Git &lt;strong&gt;сам&lt;/strong&gt; подменяет адреса&lt;/h2&gt;
  &lt;p id=&quot;VlCE&quot;&gt;Git умеет включать дополнительные конфиги в зависимости от пути к репозиторию.&lt;/p&gt;
  &lt;p id=&quot;1ymh&quot;&gt;Открываем (или создаём) &lt;code&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;JVei&quot;&gt;[user]
  name = Your Name
  email = your.email@example.com

[includeIf &amp;quot;gitdir:~/workspace/work/&amp;quot;]
  path = .gitconfig-work
&lt;/pre&gt;
  &lt;p id=&quot;B30a&quot;&gt;Теперь создаём файл &lt;code&gt;.gitconfig-work&lt;/code&gt; в домашней директории:&lt;/p&gt;
  &lt;pre id=&quot;RCwV&quot;&gt;[user]
  name = Your Name
  email = your.email@work.com

[url &amp;quot;ssh://git@github.com-company/company/&amp;quot;]
  insteadOf = ssh://git@github.com/company/

[url &amp;quot;git@github.com-company:company/&amp;quot;]
  insteadOf = git@github.com:company/&lt;/pre&gt;
  &lt;h2 id=&quot;JU2J&quot;&gt;🧠 Что теперь происходит&lt;/h2&gt;
  &lt;p id=&quot;Dwum&quot;&gt;Если вы работаете внутри &lt;code&gt;~/workspace/work/&lt;/code&gt;, Git:&lt;/p&gt;
  &lt;ul id=&quot;LuE7&quot;&gt;
    &lt;li id=&quot;Tjgc&quot;&gt;видит SSH-ссылку вида &lt;code&gt;git@github.com:company/...&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;L0Qf&quot;&gt;автоматически заменяет её на &lt;code&gt;git@github-company:company/...&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;20iJ&quot;&gt;SSH автоматически использует правильный ключ&lt;/li&gt;
    &lt;li id=&quot;kuMe&quot;&gt;вам вообще ничего менять не нужно&lt;/li&gt;
  &lt;/ul&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;LZVE&quot;&gt;🎉 Итог&lt;/h2&gt;
  &lt;p id=&quot;Heac&quot;&gt;Вы можете одновременно работать:&lt;/p&gt;
  &lt;ul id=&quot;Zvdf&quot;&gt;
    &lt;li id=&quot;jX2u&quot;&gt;под личным аккаунтом,&lt;/li&gt;
    &lt;li id=&quot;z4rS&quot;&gt;под рабочим аккаунтом,&lt;/li&gt;
    &lt;li id=&quot;UsOc&quot;&gt;без ручной правки ссылок,&lt;/li&gt;
    &lt;li id=&quot;IS4p&quot;&gt;без поломанных зависимостей,&lt;/li&gt;
    &lt;li id=&quot;Zxfo&quot;&gt;без риска случайно закоммитить alias-ссылки.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;fXib&quot;&gt;Вся магия — это связка:&lt;/p&gt;
  &lt;ul id=&quot;Nw53&quot;&gt;
    &lt;li id=&quot;Cu5L&quot;&gt;&lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;IRcJ&quot;&gt;&lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;6s2W&quot;&gt;&lt;code&gt;includeIf&lt;/code&gt; + &lt;code&gt;insteadOf&lt;/code&gt;&lt;/li&gt;
  &lt;/ul&gt;

</content></entry><entry><id>hikkidg:K9QEg3yNoYE</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/K9QEg3yNoYE?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>Сервер на коленке для простых задач: Raspberry Pi + Portainer</title><published>2025-03-28T00:14:44.771Z</published><updated>2025-03-28T00:15:21.957Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/a3/10/a3106a46-103c-40f0-88d7-255917352a9a.png"></media:thumbnail><category term="docker" label="Docker"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/ad/c5/adc53f9f-eae1-4e01-9841-bee3679a819f.png&quot;&gt;Периодически всплывают задачи, для которых необходим постоянно запущенное приложение, но по тем или иным причинам тебе не хочется использовать для этого VDSки. Например, он должно потреблять ничтожно мало процессорного времени и при этом периодически что-то выполнять раз в N часов: бэкапы, боты и т. д.</summary><content type="html">
  &lt;p id=&quot;Qexl&quot;&gt;Периодически всплывают задачи, для которых необходим постоянно запущенное приложение, но по тем или иным причинам тебе не хочется использовать для этого VDSки. Например, он должно потреблять ничтожно мало процессорного времени и при этом периодически что-то выполнять раз в N часов: бэкапы, боты и т. д.&lt;/p&gt;
  &lt;p id=&quot;lq7O&quot;&gt;Для таких случаев я приноровился использовать Raspberry Pi c веб-приложеним  &lt;a href=&quot;https://www.portainer.io/&quot; target=&quot;_blank&quot;&gt;Portainer&lt;/a&gt; для более удобного управления своими приложениями. С помощью этих двух инструментов можно держать дома полтора десятка простеньких веб-сервисов, баз данных, Телеграм ботов для личных нужд.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;ol id=&quot;jOzh&quot;&gt;
      &lt;li id=&quot;rqqm&quot;&gt;&lt;a href=&quot;#zLVj&quot;&gt;Raspberry Pi&lt;/a&gt;&lt;/li&gt;
      &lt;li id=&quot;VzTP&quot;&gt;&lt;a href=&quot;#RnY7&quot;&gt;Docker + Portainer&lt;/a&gt;&lt;/li&gt;
      &lt;li id=&quot;jIVH&quot;&gt;&lt;a href=&quot;#alaQ&quot;&gt;Разработка и развертывание приложений&lt;/a&gt;&lt;/li&gt;
      &lt;li id=&quot;qSPt&quot;&gt;&lt;a href=&quot;#zedQ&quot;&gt;Ресурсы&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;zLVj&quot;&gt;Raspberry Pi &lt;/h2&gt;
  &lt;p id=&quot;22wa&quot;&gt;Это, наверное, один из самых известных одноплатных компьютеров, которые можно питать от USB C разъема и использовать без активного охлаждения для не особо требовательных задач.&lt;/p&gt;
  &lt;p id=&quot;j2Ci&quot;&gt;На него есть огромное множество дистрибутивов Linux, с графической оболочкой и без. Есть даже версии Android и Windows 10/11 для тех, кому хочется особо пострадать.&lt;/p&gt;
  &lt;p id=&quot;8Oic&quot;&gt;Последнюю (на момент написания статьи) версию &amp;quot;Малинки&amp;quot; я в руках не держал. У меня &lt;a href=&quot;https://www.raspberrypi.com/products/raspberry-pi-4-model-b/&quot; target=&quot;_blank&quot;&gt;Raspberry Pi 4B 8 Gb&lt;/a&gt;. Её хватает для большинства задач в качестве сервера. Если хочется графическую оболочку, то лучше посмотреть в сторону 5-й версии, где уже и загрузку с SSD можно сделать и подключить доп. перефирию через PCI-E.&lt;/p&gt;
  &lt;p id=&quot;kKmT&quot;&gt;Я использую ее с SD картой на 128 Гб, на которой установлена Ubuntu Server 24.04 LTS. &lt;/p&gt;
  &lt;p id=&quot;RBNv&quot;&gt;Самый простой вариант установки - Rpi Imager. Утилита есть на все возможные системы в нашей галактике и прямо в ней можно найти Ubuntu Server нужной версии, чтобы не скачивать ее отдельно.&lt;/p&gt;
  &lt;blockquote id=&quot;RPXQ&quot;&gt;Замечу, что в ней сразу можно указать имя пользователя и пароль, а также запуск SSH сервера (который нам позже пригодится) и подключение к Wi-Fi. Но будьте готовы, что SSH и Wi-Fi не заведется сразу после установки. Не имею представления с чем это связано, но после установки до настройку лучше проводить с помощью внешнего монитора и проводной клавиатуры. Wi-Fi настраивал по инструкции &lt;a href=&quot;https://linuxconfig.org/ubuntu-20-04-connect-to-wifi-from-command-line&quot; target=&quot;_blank&quot;&gt;отсюда&lt;/a&gt;.&lt;/blockquote&gt;
  &lt;p id=&quot;Mleo&quot;&gt;После подключения к Wi-Fi мы можем подключиться к &amp;quot;Малинке&amp;quot; по ssh. IP адрес можно найти в админке роутера или через &lt;code&gt;ip a&lt;/code&gt;&lt;/p&gt;
  &lt;pre id=&quot;PICB&quot;&gt;ssh user@your_ip_here&lt;/pre&gt;
  &lt;h2 id=&quot;RnY7&quot;&gt;Docker + Portainer&lt;/h2&gt;
  &lt;h3 id=&quot;D1Xd&quot;&gt;Docker&lt;/h3&gt;
  &lt;p id=&quot;LtGI&quot;&gt;Самый удобный вариант использования практически любого сервера - использовать переносимые Docker контейнеры. &lt;/p&gt;
  &lt;p id=&quot;Vh2b&quot;&gt;Чтобы установить Docker на RPi с Ubuntu, можно воспользоваться стандартным скриптом:&lt;/p&gt;
  &lt;pre id=&quot;875n&quot;&gt;curl https://get.docker.com | sudo sh
docker info&lt;/pre&gt;
  &lt;h3 id=&quot;c2uQ&quot;&gt;Portainer&lt;/h3&gt;
  &lt;p id=&quot;KwS0&quot;&gt;В целом, если вам не лень заходить на сервер руками, то на этом можно остановиться, но мне удобнее смотреть на веб-интерфейс со статистикой и полезной инфой о том, что происходит на устройстве, поэтому следующим шагом будет установка &lt;a href=&quot;https://www.portainer.io/&quot; target=&quot;_blank&quot;&gt;Portainer&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;fPkz&quot;&gt;Это платформа для управления контейнерами с огромным числом функций. Самая базовая и самая главная для меня - удобный интерфейс, чтобы следить за тем, что происходить в нашей песочнице.&lt;/p&gt;
  &lt;figure id=&quot;nMtW&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ad/c5/adc53f9f-eae1-4e01-9841-bee3679a819f.png&quot; width=&quot;2880&quot; /&gt;
    &lt;figcaption&gt;Интерфейс&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;weQD&quot;&gt;Для установки Portainer на официальном сайте предлагается использовать docker образ, чтобы развернуть его всего за пару отдельных команд: &lt;a href=&quot;https://docs.portainer.io/start/install-ce/server/docker/linux&quot; target=&quot;_blank&quot;&gt;https://docs.portainer.io/start/install-ce/server/docker/linux&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;7vRX&quot;&gt;Но как по мне лучше все это написать в одном docker compose файле, чтобы все настройки наглядно отображались в одном месте:&lt;/p&gt;
  &lt;pre id=&quot;3SQ6&quot;&gt;services: 
  portainer: 
    image: portainer/portainer-ce:lts 
    ports: 
      - 8000:8000 
      - 9443:9443 
    restart: always 
    volumes: 
      - portainer_data:/data 
      - /var/run/docker.sock:/var/run/docker.sock 
  
volumes: 
  portainer_data:&lt;/pre&gt;
  &lt;pre id=&quot;Cpmb&quot;&gt;docker compose up -d&lt;/pre&gt;
  &lt;p id=&quot;AJ40&quot;&gt;Сохраняем в удобном месте и получаем запущенный Portainer, в который мы можем идти через обычный браузер на порту 9443. &lt;/p&gt;
  &lt;h2 id=&quot;alaQ&quot;&gt;Разработка и развертывание приложений&lt;/h2&gt;
  &lt;h3 id=&quot;zuR4&quot;&gt;Развертывание готовых контейнеров&lt;/h3&gt;
  &lt;p id=&quot;hwyH&quot;&gt;Для работы с контейнерами в Portainer у нас есть дво пункта меню - отдельные контейнеры и стеки.&lt;/p&gt;
  &lt;figure id=&quot;zHOi&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b9/f3/b9f33c70-51f8-46e9-bb59-7806a1a9e7f9.png&quot; width=&quot;319&quot; /&gt;
    &lt;figcaption&gt;Основные&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hTIp&quot;&gt;Соответственно, для простых одиночных вещей удобнее настраивать отдельные контейнеры. Если у нас есть docker-compose.yaml на 10 контейнеров с healthcheck и очередностью запуска, то наш выбор - стек. Стеки соответственно поднимают контейнеры.&lt;/p&gt;
  &lt;p id=&quot;Yxi3&quot;&gt;Другие вкладки отвечают за работу с образами, сетями, хранилищами и хостами доступными Docker. &lt;/p&gt;
  &lt;p id=&quot;0vW1&quot;&gt;По умолчанию образы берутся из глобального Docker Hub, но можно указать и собственные реестры (например, если разворачиваете для в рабочей сети).&lt;/p&gt;
  &lt;h3 id=&quot;cy5P&quot;&gt;Разработка&lt;/h3&gt;
  &lt;p id=&quot;rHOi&quot;&gt;Т. к. Raspberry Pi построена на базе ARM процессора, то компилируемые программы не совместимы с обычными компьютерами. Например, если вы пишите серверное приложение на C++ или Go, то учтите, что его нужно собирать специально под ARM64 платформу.&lt;/p&gt;
  &lt;p id=&quot;DWvC&quot;&gt;Скриптовые языки типа Python чувствуют себя в этом плане гораздо лучше, т. к. у них обычно используются мультиплатформенные образы, а т. к. скрипты не надо компилировать, они переносятся as-is.&lt;/p&gt;
  &lt;p id=&quot;uqLC&quot;&gt;Для сборки под несколько платформ на обычном ПК нам понадобится buildx:&lt;/p&gt;
  &lt;pre id=&quot;zPAn&quot;&gt;docker buildx create --name multiarch --driver docker-container --use
docker buildx build --platform=linux/amd64,linux/arm6
4 . -t some_tag --push&lt;/pre&gt;
  &lt;p id=&quot;XtzN&quot;&gt;Так мы можем собрать приложение под две платформы и сразу запушить в публичный Docker Hub.&lt;/p&gt;
  &lt;p id=&quot;Nidx&quot;&gt;Теперь мы можем развернуть наше приложение на RPi.&lt;/p&gt;
  &lt;p id=&quot;RICe&quot;&gt;Итого наш путь:&lt;/p&gt;
  &lt;ol id=&quot;qwEh&quot;&gt;
    &lt;li id=&quot;gAud&quot;&gt;Написать сервис&lt;/li&gt;
    &lt;li id=&quot;lXC4&quot;&gt;Собрать под RPi + залить в Docker Hub&lt;/li&gt;
    &lt;li id=&quot;HC5u&quot;&gt;Развернуть на RPi через docker-compose.yaml&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;b2sH&quot;&gt;Не сильно сложнее чем развернуть на хостинге, а оно все тут под рукой.&lt;/p&gt;
  &lt;h3 id=&quot;8dc8&quot;&gt;P. S.&lt;/h3&gt;
  &lt;p id=&quot;do8l&quot;&gt;Есть у Portainer еще одна полезная фича, которой я активно пользуюсь - подключение нескольких железок к одной панели через агентов.&lt;/p&gt;
  &lt;p id=&quot;YB0t&quot;&gt;Суть:&lt;/p&gt;
  &lt;ol id=&quot;SSqq&quot;&gt;
    &lt;li id=&quot;hPCy&quot;&gt;У вас есть несколько железок, которые можно приспособить под разные нужды.&lt;/li&gt;
    &lt;li id=&quot;RXOx&quot;&gt;На все ставим Linux и Docker&lt;/li&gt;
    &lt;li id=&quot;6p1k&quot;&gt;На самую слабую ставим основной Portainer (из шагов выше)&lt;/li&gt;
    &lt;li id=&quot;6JMG&quot;&gt;На остальные ставим агенты&lt;/li&gt;
  &lt;/ol&gt;
  &lt;figure id=&quot;LBiK&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a7/19/a719f451-6139-4148-acb2-90ab7692d20d.png&quot; width=&quot;1432&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;04CW&quot;&gt;Таким образом получаем дополнительные среды (Environments), которыми можем управлять из одной админ панели. &lt;/p&gt;
  &lt;p id=&quot;WVyW&quot;&gt;У меня так подключен Orange Pi Zero 3, на котором отдельно запущены Телеграм боты с менее требовательными ресурсами.&lt;/p&gt;
  &lt;p id=&quot;tyVU&quot;&gt;Также все описанные манипуляции можно произвести с несколькими виртуальными хостингами, чтобы связать ресурсы в одной панели управления.&lt;/p&gt;
  &lt;h2 id=&quot;zedQ&quot;&gt;Ресурсы&lt;/h2&gt;
  &lt;ol id=&quot;TR1g&quot;&gt;
    &lt;li id=&quot;6kVs&quot;&gt;&lt;a href=&quot;https://www.raspberrypi.com/products/raspberry-pi-4-model-b/&quot; target=&quot;_blank&quot;&gt;Raspberry Pi 4B&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;47aR&quot;&gt;&lt;a href=&quot;https://docs.portainer.io&quot; target=&quot;_blank&quot;&gt;Portainer&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;gbyb&quot;&gt;&lt;a href=&quot;https://get.docker.com/&quot; target=&quot;_blank&quot;&gt;Get Docker Script&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;IJLw&quot;&gt;&lt;a href=&quot;https://github.com/raspberrypi/rpi-imager&quot; target=&quot;_blank&quot;&gt;RPi Imager&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;

</content></entry><entry><id>hikkidg:copytrading</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/copytrading?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>Стратегии с автоследованием</title><published>2025-03-13T06:34:41.271Z</published><updated>2025-03-13T06:41:35.525Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/09/2a/092a0440-f3ae-4a6a-a117-0783a17eb363.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/00/05/0005d518-8a13-4ace-bab6-70578c1fde0a.jpeg&quot;&gt;Вы навреняка видели в приложениях брокеров баннеры типа &quot;Автоследование. Подключите готовую стратегию от профи и зарабатывайте&quot;. И конечно же хочется посмотреть, что там за подключение такое, что можно вот так взять и зарабатывать.</summary><content type="html">
  &lt;p id=&quot;mXOE&quot;&gt;Вы навреняка видели в приложениях брокеров баннеры типа &lt;em&gt;&lt;strong&gt;&amp;quot;Автоследование. Подключите готовую стратегию от профи и зарабатывайте&amp;quot;&lt;/strong&gt;&lt;/em&gt;. И конечно же хочется посмотреть, что там за подключение такое, что можно вот так взять и зарабатывать.&lt;/p&gt;
  &lt;figure id=&quot;dD5M&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/00/05/0005d518-8a13-4ace-bab6-70578c1fde0a.jpeg&quot; width=&quot;1344&quot; /&gt;
  &lt;/figure&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;uD60&quot;&gt;План&lt;/p&gt;
    &lt;ol id=&quot;t8BV&quot;&gt;
      &lt;li id=&quot;E9Z0&quot;&gt;Что такое автоследование?&lt;/li&gt;
      &lt;ol id=&quot;CX92&quot;&gt;
        &lt;li id=&quot;h3yl&quot;&gt;Как это работает?&lt;/li&gt;
        &lt;li id=&quot;hIgP&quot;&gt;Подводные камни&lt;/li&gt;
      &lt;/ol&gt;
      &lt;li id=&quot;EDRm&quot;&gt;Мой опыт&lt;/li&gt;
      &lt;ol id=&quot;Nyf1&quot;&gt;
        &lt;li id=&quot;c8Fj&quot;&gt;Стратегия №1 (вроде неплохо)&lt;/li&gt;
        &lt;li id=&quot;J4bc&quot;&gt;Стратегия №2 (чет не пошло)&lt;/li&gt;
        &lt;li id=&quot;QIAF&quot;&gt;Мой собственный портфель&lt;/li&gt;
      &lt;/ol&gt;
      &lt;li id=&quot;b6Id&quot;&gt;Выводы&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/section&gt;
  &lt;p id=&quot;wOFF&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;VqMK&quot;&gt;Что такое автоследование?&lt;/h2&gt;
  &lt;p id=&quot;5kft&quot;&gt;Это автоматизированная торговля, во время которой на вашем счете в реальном времени совершаются сделки, повторяющие сделки другого трейдера или управляющего.&lt;/p&gt;
  &lt;p id=&quot;gFEF&quot;&gt;Основная идея этого процесса - найти хорошую стратегию (хорошего трейдера), которая, как вам кажется, должна принести прибыль, не участвуя в этом процессе постоянно.&lt;/p&gt;
  &lt;h3 id=&quot;l4LQ&quot;&gt;Как это работает?&lt;/h3&gt;
  &lt;p id=&quot;d9tP&quot;&gt;Обычно все платформы предоставляют примерно одинаковый процесс работы с автоследованием:&lt;/p&gt;
  &lt;ol id=&quot;IYaS&quot;&gt;
    &lt;li id=&quot;m9kT&quot;&gt;&lt;strong&gt;Выбираем стратегию.&lt;/strong&gt; Внимательно смотрим на все доступные стратегии, изучаем статистику доходности, просадок, уровень риска, саму идею стратегии.&lt;/li&gt;
    &lt;li id=&quot;ruLH&quot;&gt;&lt;strong&gt;Подключаем капитал.&lt;/strong&gt; Необходимо выделить некоторое количество средств на счете или даже создать отдельный счет к стратегии. Средства все еще ваши, но вы таким образом определяете их будущее предназначение и не сможете потратить на что-то еще.&lt;/li&gt;
    &lt;li id=&quot;d2Hf&quot;&gt;&lt;strong&gt;Копирование сделок.&lt;/strong&gt; Все сделки выполняются на счете как в указанной стратегии в соответствии с долей выделенного капитала: условно на счету трейдера, за которым вы следите, было 100 000 р, вы выделили на стратегию 10 000 руб - в 10 раз меньше, соответственно все сделки будут выполняться пропорционально этому соотношению капитала.&lt;/li&gt;
    &lt;li id=&quot;3Er8&quot;&gt;Контроль и отключение. Обычно можно пополнять и снимать деньги, управляя выделенным капиталом, или вообще отключить стратегию, когда захочется.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;R0EZ&quot;&gt;Подводные камни&lt;/h3&gt;
  &lt;ul id=&quot;8l10&quot;&gt;
    &lt;li id=&quot;eeiM&quot;&gt;⚠️ нет гарантий прибыли: идея этого процесса в том, что трейдер не просто советует, что купить, а сам является участником процесса, рискуя своим капиталом. Это должно заставлять людей ответственно подходить к таким решениям, но все могут ошибаться.&lt;/li&gt;
    &lt;li id=&quot;kCl2&quot;&gt;⚠️ комиссии: если мы выбрали удачную стратегию и она приносит прибыль, то мы платим налог на прибыль + комиссию платформе/трейдеру за автоследование.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;ybxc&quot;&gt;Мой опыт автоследования&lt;/h2&gt;
  &lt;p id=&quot;zWle&quot;&gt;Самый главный вопрос, можно ли на этом реально заработать? Я вложился в две стратегии ради эксперимента чуть больше месяца назад. Выбирал не самые рисковые (где 100500% прибыли, а за все время сделали 70-90%).&lt;/p&gt;
  &lt;p id=&quot;vwAu&quot;&gt;При чем в приложении брокера отображаются 2 числа: реальный процент за все время (вот это маленькое зеленое число) и прогноз, который ставит сам автор стратегии (естественно пальцем в небо). У топовых стратегий процент за все время обычно опережает прогноз, но учтите что это именно ВСЁ ВРЕМЯ существования стратегии, в нее можно провалиться и посмотреть график прибыли.&lt;/p&gt;
  &lt;figure id=&quot;EJC5&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/61/f0/61f05871-cd27-4680-a384-72a9d085eb1a.png&quot; width=&quot;2252&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;dqzh&quot;&gt;Стратегия №1&lt;/h3&gt;
  &lt;p id=&quot;FALN&quot;&gt;Решил выбрать относительно консервативную историю без фьючерсов и вот этого всего. Заходил изначально на минималке - 12 000 рублей. Затем когда понял, что история перспективная докинул еще + поставил автопополнение.&lt;/p&gt;
  &lt;figure id=&quot;XUXM&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6a/43/6a4385cb-a91a-4480-9de5-b2f97fc83826.png&quot; width=&quot;2254&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VrHW&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;lncD&quot;&gt;Но есть интересный момент с доходами. На скрине выше брокер показывает 1 480 р 19 коп, но если открыть в приложении банка стратегию, то будет следующая картина. &lt;/p&gt;
  &lt;figure id=&quot;OdR8&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/28/2e/282eeb04-3e94-4e26-917c-0af18d33e321.jpeg&quot; width=&quot;1080&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;1s9e&quot;&gt;Внезапно на ~5 000 руб. больше. Получается что доходность портфеля в 4.5 раза выше и это уже прям отличный результат. Всего за месяц &lt;strong&gt;22%. &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;GHVT&quot;&gt;Чему верить?&lt;/p&gt;
  &lt;p id=&quot;zQEb&quot;&gt;Если выкачать статистику по всем операциям портфеля, то получается, что я счет пополнил всего на 25 200 руб, что сильно меньше 31 929 руб, значит второе число верное. Но это все еще не чистая прибыль, т. к. при продаже портфеля и закрытии счет автоследования нужно будет еще заплатить налог (комиссии за автоследование списываются регулярно). Даже с учетом налога в 13% получаем примерно &lt;strong&gt;19%&lt;/strong&gt; прибыли за месяц.&lt;/p&gt;
  &lt;h3 id=&quot;hIme&quot;&gt;Стратегия №2&lt;/h3&gt;
  &lt;p id=&quot;6o0O&quot;&gt;Тут ради эксперимента нашел стратегию с фьючерсами гораздо более рисковая тема (хоть и в приложении у них с предыдущей одинаковый показатель рисковости - 3 молнии, помните что фьючи, плечи и вот это все очень непростая история!). Тоже зашел по минимуму и подключил автоплатеж, но дополнительно не докладывал, т. к. результат за все время особо не удивил.&lt;/p&gt;
  &lt;figure id=&quot;Y7Av&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d7/c9/d7c9a7d3-e1df-47f9-8afb-d028a5bf2d75.png&quot; width=&quot;2232&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Mjxc&quot;&gt;Тут даже рассказывать особо не хочется. По изначальной оценке от автора у нее стоит 90% доходность и была довольно неплохая история раннее, но видимо в связи с последними политическими событиями рынок развернулся не совсем туда, куда планировал автор.&lt;/p&gt;
  &lt;p id=&quot;GqXa&quot;&gt;Как мы уже знаем, что цифрам на сайте или в ленте счетов верить нельзя, проваливаемся в саму стратегию и видим реальный результат:&lt;/p&gt;
  &lt;figure id=&quot;kwpw&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/33/55/335597d3-e5ab-4391-83dd-6e22d652c34b.jpeg&quot; width=&quot;1080&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;q9yb&quot;&gt;Здесь конечно печальнее осознавать, что в итоге стратегия уходит еще в больший минус, чем отображается, но в целом это было ожидаемо.&lt;/p&gt;
  &lt;h2 id=&quot;boT4&quot;&gt;Мой портфель за тоже время&lt;/h2&gt;
  &lt;p id=&quot;iCUu&quot;&gt;И на последок для сравнения мой собственный портфель, который я собирал по своему плану, за +/- тот же срок. На его сбор я тратил где-то пару часов в неделю, чтобы отсмотреть статистику, новости компаний, где-то подгадать момент на просадке.&lt;/p&gt;
  &lt;figure id=&quot;A7Ub&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6a/ac/6aacbb8b-fbf2-4f44-aa2a-dc387512cab8.png&quot; width=&quot;2282&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;glms&quot;&gt;Скриншот возможно сделал в не самое удачное время, потому что рынок штормит, и в течение дня на новостях эта сумма сильно меняется, но в минус не уходит, а колеблется от 1 до 5%.&lt;/p&gt;
  &lt;h2 id=&quot;Zkg2&quot;&gt;Выводы&lt;/h2&gt;
  &lt;p id=&quot;0TeW&quot;&gt;Если суммировать обе стратегии как один эксперимент по автоследованию, то картина такая: общая сумма капитала 46 943 руб, из них 5 900 руб чистой прибыли, доходность &lt;strong&gt;~12.5% за 1.5 месяца&lt;/strong&gt;. Считаю результат неплохим, но это везение. Поэтому помните, что:&lt;/p&gt;
  &lt;ol id=&quot;05Bj&quot;&gt;
    &lt;li id=&quot;WRBN&quot;&gt;В стратегии вам никто ничего не гарантирует&lt;/li&gt;
    &lt;li id=&quot;hGv0&quot;&gt;За стратегию нужно платить комиссии&lt;/li&gt;
    &lt;li id=&quot;EdxR&quot;&gt;Далеко не каждая стратегия может стабильно обгонять рынок, чтобы действительно приносить прибыль&lt;/li&gt;
    &lt;li id=&quot;sn1o&quot;&gt;Собрать отностильно хороший портфель можно самому, прикладывая не так много усилий.&lt;/li&gt;
    &lt;li id=&quot;sZL5&quot;&gt;Стратегия позволит практически не тратить на это время, т. к. нужно один раз ее выбрать и периодически пополнять&lt;/li&gt;
  &lt;/ol&gt;

</content></entry><entry><id>hikkidg:python-testing-pyramid</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/python-testing-pyramid?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>Пирамида тестирования в Python</title><published>2025-03-13T05:19:00.334Z</published><updated>2025-03-13T05:34:55.840Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/5a/1e/5a1e3d7e-a146-4f1e-b1d9-0c60d6e8885c.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/05/08/050869c7-0f60-4046-a7d4-f4032ad30908.png&quot;&gt;После опубликации предыдущей статьи получил комментарий: &quot;Я удивлён, что есть тесты, на которые нужно разворачивать докер для имитации сторонних сервисов&quot;. И тут я задумался, что возможно стоит немножко рассказать про тесты в целом в экосистеме Python.</summary><content type="html">
  &lt;p id=&quot;52H4&quot;&gt;После опубликации &lt;a href=&quot;https://teletype.in/@hikkidg/asycnio-and-sync-calls&quot; target=&quot;_blank&quot;&gt;предыдущей статьи&lt;/a&gt; получил комментарий: &lt;em&gt;&amp;quot;Я удивлён, что есть тесты, на которые нужно разворачивать докер для имитации сторонних сервисов&amp;quot;.&lt;/em&gt; И тут я задумался, что возможно стоит немножко рассказать про тесты в целом в экосистеме Python.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;DsWG&quot;&gt;План&lt;/p&gt;
    &lt;ol id=&quot;x655&quot;&gt;
      &lt;li id=&quot;OwE6&quot;&gt;&lt;a href=&quot;#%D0%92-%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5-%D0%B1%D1%8B%D0%BB%D0%B0...-%D0%BF%D0%B8%D1%80%D0%B0%D0%BC%D0%B8%D0%B4%D0%B0?-%D0%9D%D0%B5%D1%82&amp;#33;&quot;&gt;В начале была... пирамида? Нет!&lt;/a&gt;&lt;/li&gt;
      &lt;li id=&quot;s88k&quot;&gt;Пирамида!&lt;/li&gt;
      &lt;ol id=&quot;CCVs&quot;&gt;
        &lt;li id=&quot;jb2O&quot;&gt;Unit тестирование&lt;/li&gt;
        &lt;li id=&quot;Qkix&quot;&gt;Интеграционное тестирование&lt;/li&gt;
        &lt;li id=&quot;2ORa&quot;&gt;End-2-End&lt;/li&gt;
        &lt;li id=&quot;oqCf&quot;&gt;Смешение уровней&lt;/li&gt;
      &lt;/ol&gt;
      &lt;li id=&quot;8Qfz&quot;&gt;Покрытие тестами&lt;/li&gt;
      &lt;li id=&quot;Q0jf&quot;&gt;Тестирование и формальная верификация программ&lt;/li&gt;
      &lt;li id=&quot;YUZY&quot;&gt;Полезные материалы&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/section&gt;
  &lt;p id=&quot;T78b&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;zIun&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/05/08/050869c7-0f60-4046-a7d4-f4032ad30908.png&quot; width=&quot;1000&quot; /&gt;
    &lt;figcaption&gt;Так сказать основа!&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;В-начале-была...-пирамида?-Нет&amp;#33;&quot;&gt;В начале была... пирамида? Нет!&lt;/h2&gt;
  &lt;figure id=&quot;AOXq&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7f/d1/7fd1a3bc-aaeb-4e00-ba3c-c43b10780ead.png&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;Как пирамиду тестирования представляют себе синьоры&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VeDz&quot;&gt;Начнем с определения:&lt;/p&gt;
  &lt;blockquote id=&quot;R9fN&quot;&gt;&lt;strong&gt;Тестирование программного обеспечения&lt;/strong&gt; — процесс исследования, испытания программного продукта, имеющий своей целью проверку соответствия между реальным поведением программы и её ожидаемым поведением на конечном наборе тестов, выбранных определённым образом. &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BE%D0%B1%D0%B5%D1%81%D0%BF%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F&quot; target=&quot;_blank&quot;&gt;(сорс)&lt;/a&gt;&lt;/blockquote&gt;
  &lt;p id=&quot;WVhv&quot;&gt;На практике под тестированием в программировании понимаются обычно автоматическое или ручное тестирование вашей программы по определенным сценариям приближенным к реальному использованию.&lt;/p&gt;
  &lt;p id=&quot;7iis&quot;&gt;В этом материале рассмотрим именно аспекты касающиеся автоматизированного тестирования.&lt;/p&gt;
  &lt;h2 id=&quot;Пирамида&amp;#33;&quot;&gt;Пирамида!&lt;/h2&gt;
  &lt;figure id=&quot;5p6l&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ab/c1/abc1d629-0325-427c-973f-14318cafe5a4.png&quot; width=&quot;1040&quot; /&gt;
    &lt;figcaption&gt;Как масоны управляют управляют тестированием на самом деле&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hGDo&quot;&gt;Всё почалось с книги &amp;quot;Scrum: гибкая разработка ПО&amp;quot; Майка Кона, где были представлены 3 уровня тестов. Но мы будем разбирать конкретную пирамиду в контексте Python разработки.&lt;/p&gt;
  &lt;p id=&quot;Itkw&quot;&gt;В общем про тесты и про пирамиду можно сказать следующее:&lt;/p&gt;
  &lt;ol id=&quot;dfyz&quot;&gt;
    &lt;li id=&quot;zEle&quot;&gt;&lt;strong&gt;Тест должен быть на том же уровне, что и тестируемый объект&lt;/strong&gt;: если мы хотим проверить корректность работы минимальной единицы кода (функции или класса), мы не должны для этого разворачивать полноценный стенд с приложением.&lt;/li&gt;
    &lt;li id=&quot;ZK5f&quot;&gt;&lt;strong&gt;Тесты уровнем выше не проверяют логику тестов уровнем ниже&lt;/strong&gt;. Например, есть интеграционный тест ленты новостей: в нем мы сначала авторизуемся в системе через логин и пароль и затем с полученным ключом хотим получить список новостей из какой-то заготовленной базы подписок этого пользователя. Нам важно получить конкретный набор новостей для этого пользователя (возможно в определенном порядке, например, по дате создания), но нам не нужно проверять, что именно эти данные лежат в конкретной таблице &lt;code&gt;news&lt;/code&gt; в PostgreSQL.&lt;/li&gt;
    &lt;li id=&quot;frDx&quot;&gt;Чем выше в пирамиде тесты, тем они:&lt;/li&gt;
  &lt;/ol&gt;
  &lt;ul id=&quot;O18B&quot;&gt;
    &lt;li id=&quot;JDxs&quot;&gt;&lt;strong&gt;сложней в реализации, и соответственно, дороже в реализации;&lt;/strong&gt;&lt;/li&gt;
    &lt;li id=&quot;MOQp&quot;&gt;&lt;strong&gt;важнее для бизнеса и критичней для пользователей;&lt;/strong&gt;&lt;/li&gt;
    &lt;li id=&quot;Am52&quot;&gt;&lt;strong&gt;замедляют скорость прохождения тестовых наборов&lt;/strong&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;Unit-тестирование&quot;&gt;Unit тестирование&lt;/h3&gt;
  &lt;p id=&quot;8qlj&quot;&gt;(их еще называют модульными тестами, компонентными тестами)&lt;/p&gt;
  &lt;p id=&quot;Iy93&quot;&gt;На этом уровне мы тестируем минимальные части кода – функции, методы, классы. Обычно ограничиваются &lt;strong&gt;публичными&lt;/strong&gt; функциями и классами. В Python начинающие разработчики пренебрегают этим и лезут в функции, которые начинаются с нижнего подчеркивания. Если вам приходится этим заниматься, подумайте (!) возможно где-то процесс пошел не туда.&lt;/p&gt;
  &lt;p id=&quot;MveX&quot;&gt;Также обычно в тестах не происходит тестирование &amp;quot;чужого&amp;quot; кода – стандартной библиотеки и модулей Python или сторонних зависимостей.&lt;/p&gt;
  &lt;p id=&quot;T1Qy&quot;&gt;Unit тесты находят самые базовые ошибки, зачастую очень быстро пишутся и в большом количестве. Они сами по себе быстрые и при изменении кода позволяют быстро убедиться, что вы ничего не сломали.&lt;/p&gt;
  &lt;p id=&quot;yiSy&quot;&gt;Даже есть базовая рекомендация, если вы садитесь переписывать старый legacy проект, который не покрыт тестами, то для начала можно покрыть критические участки хотя бы юнитами, чтобы потом спать спокойно.&lt;/p&gt;
  &lt;p id=&quot;Yqoa&quot;&gt;В 99% разработкой модульных тестов занимается сам разработчик.&lt;/p&gt;
  &lt;p id=&quot;1rGW&quot;&gt;Есть довольно известная в среде программистов концепция TDD (Test Driven Development). Ее можно описать фразой:&lt;em&gt; &amp;quot;Сначала – тесты, потом – код&amp;quot;.&lt;/em&gt; И ее очень удобно применять именно на уровне Unit тестирования, когда вы можете представить себе ожидаемую работу небольшого компонента программы и начать с описания ожидаемого поведения.&lt;/p&gt;
  &lt;p id=&quot;biEj&quot;&gt;&lt;strong&gt;А что там в Python?&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;RFBS&quot;&gt;У нас есть стандартная библиотека &lt;code&gt;unittest&lt;/code&gt;, которая предоставляет базовые классы для написания модульных тестов. Также есть модуль &lt;code&gt;mock&lt;/code&gt; в ней, который позволяет делать моки (заглушки) объектов для проверок вызовов отдельных методов, или наоборот позволяет иммитировать работу объектов, которые мы не хотим явно вызывать.&lt;/p&gt;
  &lt;p id=&quot;Sijx&quot;&gt;Тесты в &lt;code&gt;unittest&lt;/code&gt; представляют собой &lt;strong&gt;наследников класса&lt;/strong&gt; &lt;code&gt;TestCase&lt;/code&gt; внутри, которых содержатся отдельные методы, начинающиеся со слова &lt;code&gt;test&lt;/code&gt; - собственно сами тесты. Еще есть методы &lt;code&gt;setUp()&lt;/code&gt; и &lt;code&gt;tearDown()&lt;/code&gt;, которые позволяют подготовить тестовые данные и выполнить какую-то очистку после выполнения тестов.&lt;/p&gt;
  &lt;p id=&quot;Z2Ok&quot;&gt;Своеобразным антиподом этому подходу с классами выступает библиотека &lt;code&gt;pytest&lt;/code&gt;, которая преимущественно используется для написания тестов &lt;strong&gt;в виде отдельных функций.&lt;/strong&gt; Соответственно, подготовка и завершение работы после тестов осуществляется тоже в функциях, которые называются фикстуры и помечаются декоратором &lt;code&gt;@pytest.fixture&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;4eZM&quot;&gt;Для примера напишем юнит тесты для &lt;a href=&quot;https://gist.github.com/andy-takker/f10d915c98fd69a2bb36bdd7902f72f4&quot; target=&quot;_blank&quot;&gt;кусочка игры &amp;quot;Виселица&lt;/a&gt;:&lt;/p&gt;
  &lt;ul id=&quot;sXJX&quot;&gt;
    &lt;li id=&quot;aAhh&quot;&gt;&lt;a href=&quot;https://gist.github.com/andy-takker/98abe3901968e920e09e65b8911acfed&quot; target=&quot;_blank&quot;&gt;с помощью &lt;code&gt;unittest&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;DlTr&quot;&gt;&lt;a href=&quot;https://gist.github.com/andy-takker/94351e1e30d68c40a116ecbb6df8db3f&quot; target=&quot;_blank&quot;&gt;с помощью &lt;code&gt;pytest&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Kuk2&quot;&gt;Почему это юнит тесты?&lt;/p&gt;
  &lt;ul id=&quot;stTm&quot;&gt;
    &lt;li id=&quot;d342&quot;&gt;Тестируется поведение одного отдельно взятого класса&lt;/li&gt;
    &lt;li id=&quot;OB8N&quot;&gt;В этих тестах не используются какие-либо сторонние сервисы, службы, не происходят какие-то side effects (например, работа с файловой системой)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;Usqz&quot;&gt;&lt;strong&gt;Интеграционное тестирование&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;0Ipr&quot;&gt;Проверяет взаимосвязанные компоненты, которые мы проверяли на модульном уровне с другими компонентами, а также интеграцию с компонента с системой.&lt;/p&gt;
  &lt;p id=&quot;MAc0&quot;&gt;Сюда относят:&lt;/p&gt;
  &lt;ul id=&quot;we2F&quot;&gt;
    &lt;li id=&quot;GV7m&quot;&gt;работу с базой данных&lt;/li&gt;
    &lt;li id=&quot;43U6&quot;&gt;работу с файловой системой&lt;/li&gt;
    &lt;li id=&quot;prNX&quot;&gt;работу с внешними службами и сервисами&lt;/li&gt;
    &lt;li id=&quot;ZqiR&quot;&gt;API тесты&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;AkuR&quot;&gt;По идее любой элемент системы, который касается среды, инфраструктуры или пользователя, который пользуется этой системы, требует интеграционных тестов.&lt;/p&gt;
  &lt;p id=&quot;HXnC&quot;&gt;В интеграционных тестах практически не используются моки для подмены поведения объектов, потому что моки не содержат ни бизнес-логики, ни реализации (а интеграционные тесты преимущественно завязаны на проверку реализации).&lt;/p&gt;
  &lt;p id=&quot;lpat&quot;&gt;В интеграционном тестировании выделяют 3 подхода:&lt;/p&gt;
  &lt;ul id=&quot;dvT8&quot;&gt;
    &lt;li id=&quot;6FQL&quot;&gt;&lt;strong&gt;Снизу вверх&lt;/strong&gt;: все мелкие части модуля собираются в один модуль (функцию или метод) и тестируются. Далее собираются следующие мелкие модули в один большой и тестируются с предыдущим и т. д.&lt;/li&gt;
    &lt;li id=&quot;FbhM&quot;&gt;&lt;strong&gt;Сверху вниз&lt;/strong&gt;: сначала проверяем работу крупных модулей, спускаясь ниже добавляем модули уровнем ниже. На этапе проверки верхних уровней могут использоваться моки для сиумляции данных от уровней ниже.&lt;/li&gt;
    &lt;li id=&quot;QNdW&quot;&gt;&lt;strong&gt;Большой взрыв&lt;/strong&gt;: собираем все реализованные модули всех уровней в систему и тестируем. Если что-то не работает или не доработали, то фиксим и дорабатываем&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;5sK9&quot;&gt;&lt;strong&gt;А что в Python?&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;75vu&quot;&gt;Если ваш проект это не какая-то библиотека абстрактных интерфейсов с чистой бизнес-логикой, то у вас будут интеграционные тесты.&lt;/p&gt;
  &lt;p id=&quot;k55P&quot;&gt;В каждом популярном фреймворке есть тестовый клиент для тестирования запросов к API, а для работы с файловой системой вам придет на помощь тот же &lt;code&gt;pytest&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;m9oM&quot;&gt;Из относительно сложных примеров интеграционных тестов, которые часто приходится реализовывать – это тесты внешних API и тесты для баз данных. В них в зависимости от конкретной библиотеки (и даже от конкретного проекта) могут меняться и требования к тестам.&lt;/p&gt;
  &lt;p id=&quot;TPfC&quot;&gt;Самый простой пример интеграционных тестов БД – это тест какого-нибудь класса репозитория, который отвечает за работу с одной конкретной таблицей. Чтобы правильно его протестировать необходимо подготавливать базу данных, желательно иметь фабрики для быстрого создания объектов с нужными параметрами в БД, а также очищать таблицу (или даже всю БД) после каждого теста, чтобы не было побочных эффектов.&lt;/p&gt;
  &lt;p id=&quot;r3e2&quot;&gt;И вот на интеграционном уровне уже можно почувствовать существенные плюсы от использования &lt;code&gt;pytest&lt;/code&gt; для тестирования, а именно систему фикстур. По сути фикстуры выполняют роль &lt;strong&gt;dependency injections&lt;/strong&gt; в тестировании, когда необходимо получать подготовленный объект в нужном месте с минимальными усилиями. Это существенно упрощает код самих тестов и делает их более читабельными. Также это позволяет сэкономить ресурсы, т. к. можно управлять жизненным циклом этих объектов и создавать их не на каждый тест, а на модуль, сессию или пакет.&lt;/p&gt;
  &lt;p id=&quot;J6CF&quot;&gt;В качестве примера интеграционных тестов &lt;a href=&quot;https://github.com/andy-takker/example-web-service/tree/master/tests&quot; target=&quot;_blank&quot;&gt;приведу свой репозиторий&lt;/a&gt;, где есть только интеграционные тесты по сути.&lt;/p&gt;
  &lt;p id=&quot;C9qg&quot;&gt;Для решения проблемы тестирования внешних API есть два подхода:&lt;/p&gt;
  &lt;ol id=&quot;PqAM&quot;&gt;
    &lt;li id=&quot;EIR7&quot;&gt;Подменять наш клиент, который должен работать с внешним миром на фейковый (например, по флагу &lt;code&gt;test&lt;/code&gt; или &lt;code&gt;debug&lt;/code&gt;), который соответствует по интерфейсам, но реальных запросов не делает&lt;/li&gt;
    &lt;li id=&quot;IfUB&quot;&gt;Подменять сервер, на который отправляются запросы (например, с помощью указания корневого URL сервиса и замены его на тестовый самописный сервер, API которого соответствует реальному, но с фейковыми данными)&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;ihWU&quot;&gt;В обоих случаях необходимо проводить отдельно ручное тестирование с реальным сервисом, но второй вариант гарантирует, что на момент ручного теста будет проверено больше кода с помощью авто тестов, но и с большими трудозатратами.&lt;/p&gt;
  &lt;p id=&quot;wKxq&quot;&gt;Для второго пути могу посоветовать &lt;a href=&quot;https://github.com/andy-takker/asyncly&quot; target=&quot;_blank&quot;&gt;собственную небольшую библиотеку для построения клиентов к внешним API и их тестирования&lt;/a&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;End-2-End&quot;&gt;End-2-End&lt;/h3&gt;
  &lt;p id=&quot;Tvft&quot;&gt;(E2E, сквозное, приемочное)&lt;/p&gt;
  &lt;p id=&quot;CjX0&quot;&gt;На этом уровне проиходит проверка требований к ПО, потому что тесты на этом уровне уже можно привязать к конкретным бизнес-сторям, которые были в требованиях. Зачастую это ручные тесты.&lt;/p&gt;
  &lt;p id=&quot;mt5J&quot;&gt;E2E тесты автоматизируются гораздо сложнее, дольше, стоят дороже, сложнее поддерживаются и трудно выполняются при регрессе. Таких тестов должно быть на порядок меньше чем интеграционных.&lt;/p&gt;
  &lt;p id=&quot;NhT9&quot;&gt;Обычно их проводят, когда продукт достиг необходимо уровня качества и заказчик ПО ознакомлен с планом приемки.&lt;/p&gt;
  &lt;p id=&quot;aiOz&quot;&gt;&lt;strong&gt;А что в Python?&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;tUAN&quot;&gt;Здесь нас интересует поведение системы, а не реализация, поэтому по возможности E2E тесты не должны касаться того, как система устроена внутри: что где хранится и как называется. Чтобы это не приводило к ситуациям, когда мы переименуем ключ в кэше, а тест сломается, потому что мы его там не найдем.&lt;/p&gt;
  &lt;p id=&quot;E6DV&quot;&gt;E2E тесты могут состоять из простых действий пользователей в системе, которые иммитируют выполнение конкретной User Story с помощь API запросов или если нас интересует UI подсистема, иммитация работы пользователя в браузере с помощью какого-нибудь Selenium. Но нужно помнить, что такие API тесты будут зависеть от конкретных путей роутов и формата передаваемых данных, которые довольно быстро можно поправить или вынести в библиотеку, чтобы сделать их менее хрупкими, то с UI тестами это невозможно и тут уже нужна устоявшаяся кодовая база и дизайн.&lt;/p&gt;
  &lt;p id=&quot;DSas&quot;&gt;Сами тесты удобно писать и запускать с помощью &lt;code&gt;pytest&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;ZLM6&quot;&gt;Также стоит отметить, что такие тесты, затрагивающие межсервисное взаимодействие, обычно лежат где-то отдельно от кода самих сервисов, требуют отдельного конфига &lt;code&gt;docker compose&lt;/code&gt; для подготовки стека.&lt;/p&gt;
  &lt;p id=&quot;9GGi&quot;&gt;Как правило, если запускается стек, то все E2E тесты на нем прогоняются без перезапусков отдельных элементов. Поэтому нам нужно беспокоится о том, что, выполняя действия, в каком-то из тестов эффектом волны мы можем сломать другой тест.&lt;/p&gt;
  &lt;p id=&quot;SrBB&quot;&gt;Хороших примеров в OpenSource нет и сам пока не написал, так что приложить пока нечего.&lt;/p&gt;
  &lt;h3 id=&quot;Смешение-уровней&quot;&gt;Смешение уровней&lt;/h3&gt;
  &lt;p id=&quot;oFfe&quot;&gt;Часто в проектах грань между &lt;em&gt;интеграционными и модульными&lt;/em&gt; тестами довольно тонкая, также очень близко могут находиться &lt;em&gt;интеграционные и E2E&lt;/em&gt; тесты.&lt;/p&gt;
  &lt;p id=&quot;qU0l&quot;&gt;Не стоит ставить терминологию в главу угла и спорить с коллегами, что и как называть. Лучше всего руководствоваться здравым смыслом и смотреть в суть происходящего в тестах.&lt;/p&gt;
  &lt;h2 id=&quot;Покрытие-тестами&quot;&gt;Покрытие тестами&lt;/h2&gt;
  &lt;figure id=&quot;IkgK&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7b/45/7b45d2f7-6fc5-4adb-8c4d-dd18a8f0484a.png&quot; width=&quot;357&quot; /&gt;
    &lt;figcaption&gt;Когда pipeline не пропускает PR из-за низкого процента покрытия&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;FByw&quot;&gt;Количество тестов само по себе мало говорит о том, насколько хорошо тесты написаны.&lt;/p&gt;
  &lt;p id=&quot;FAyS&quot;&gt;В качестве базовой метрики для оценки количества тестов на &amp;quot;достаточность&amp;quot; принято брать &lt;strong&gt;процент покрытия тестами.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;NIrj&quot;&gt;Буквально эта метрика говорит о том, какая часть исходного кода охвачена тестами.&lt;/p&gt;
  &lt;p id=&quot;ZKrC&quot;&gt;Этот показатель можно посчитать как:&lt;/p&gt;
  &lt;ul id=&quot;eeWt&quot;&gt;
    &lt;li id=&quot;b6qY&quot;&gt;&lt;strong&gt;Покрытие функций&lt;/strong&gt;: сколько объявленных функций было вызвано&lt;/li&gt;
    &lt;li id=&quot;A6ds&quot;&gt;&lt;strong&gt;Покрытие веток&lt;/strong&gt;: сколько выполнено веток структур if&lt;/li&gt;
    &lt;li id=&quot;UCR1&quot;&gt;&lt;strong&gt;Покрытие условий&lt;/strong&gt;: какая доля логических подвыражений была протестирована на истину/ложь&lt;/li&gt;
    &lt;li id=&quot;eOhT&quot;&gt;&lt;strong&gt;Покрытие строк&lt;/strong&gt;: сколько строк исходного код протестировано&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;rRZr&quot;&gt;&lt;strong&gt;О чем надо помнить?&lt;/strong&gt;&lt;/p&gt;
  &lt;ol id=&quot;Fv4h&quot;&gt;
    &lt;li id=&quot;uQsV&quot;&gt;Главный инструмент для расчета покрытия в Python – Coverage.py&lt;/li&gt;
    &lt;li id=&quot;xDeo&quot;&gt;Выберите к какому проценту стремиться и по какому критерию&lt;/li&gt;
    &lt;li id=&quot;QOWo&quot;&gt;Хотите поднять покрытие – пишите unit тесты&lt;/li&gt;
    &lt;li id=&quot;Kox0&quot;&gt;Используйте покрытие в pipeline Pull Request, чтобы можно было видеть как меняется процент покрытия и какие участки кода забыли покрыть&lt;/li&gt;
    &lt;li id=&quot;k8o4&quot;&gt;Высокое покрытие тестами не говорит о том, что в приложении не нарушена целостность системы и приложение может запуститься&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2 id=&quot;Тестирование-и-формальная-верификация-программ&quot;&gt;Тестирование и формальная верификация программ&lt;/h2&gt;
  &lt;p id=&quot;PCmM&quot;&gt;Как ни странно даже 100% покрытие тестами не гарантирует нам отсутствие в программе ошибок. Оно только гарантирует ожидаемое поведение при указанных в тестах параметрах.&lt;/p&gt;
  &lt;p id=&quot;HY0q&quot;&gt;Если мы хотим убедиться, что код корректен в общем случае (условно при любых ожидаемых вводных), используется &lt;strong&gt;формальная верификация программ.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;FeBX&quot;&gt;Это буквально доказательство корректности программ с помощью математических методов, как, например, в школе на уроках геометрии мы доказывали теоремы.&lt;/p&gt;
  &lt;p id=&quot;jp3m&quot;&gt;К такому методу прибегают довольно редко, обычно это требуется для программ, в которых ошибка может стоить очень дорого: космическая отрасль, автономный транспорт, банковские системы и т. д.&lt;/p&gt;
  &lt;h2 id=&quot;Полезные-материалы&quot;&gt;Полезные материалы&lt;/h2&gt;
  &lt;ol id=&quot;IvQv&quot;&gt;
    &lt;li id=&quot;1oLJ&quot;&gt;&lt;a href=&quot;https://habr.com/ru/articles/672484/&quot; target=&quot;_blank&quot;&gt;[Хабр] Подробнее про пирамиду тестирования&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;SyXa&quot;&gt;&lt;a href=&quot;https://www.atlassian.com/ru/continuous-delivery/software-testing/code-coverage&quot; target=&quot;_blank&quot;&gt;[Atlassian] Что такое покрытие кода?&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;qh79&quot;&gt;&lt;a href=&quot;https://docs.python.org/3/library/unittest.html&quot; target=&quot;_blank&quot;&gt;[Python Doc] unittest — Unit testing framework&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;L9LM&quot;&gt;&lt;a href=&quot;https://docs.pytest.org/en/stable/&quot; target=&quot;_blank&quot;&gt;[pytest] Documentation&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;dSft&quot;&gt;&lt;a href=&quot;https://coverage.readthedocs.io/en/7.6.10/&quot; target=&quot;_blank&quot;&gt;[Coverage.py] Documentation&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;N4BH&quot;&gt;&lt;a href=&quot;https://realpython.com/pytest-python-testing/&quot; target=&quot;_blank&quot;&gt;[Real Python] Effective Python Testing With pytest&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;EtAL&quot;&gt;&lt;a href=&quot;https://realpython.com/python-unittest/&quot; target=&quot;_blank&quot;&gt;[Real Python] Python&amp;#x27;s unittest: Writing Unit Tests for Your Code&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;wR2x&quot;&gt;&lt;a href=&quot;https://www.piter.com/collection/A21587/product/ekstremalnoe-programmirovanie-razrabotka-cherez-testirovanie-2&quot; target=&quot;_blank&quot;&gt;Кент Бек. Экстремальное программирование: разработка через тестирование&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;CgS2&quot;&gt;&lt;a href=&quot;https://habr.com/ru/articles/752668/&quot; target=&quot;_blank&quot;&gt;[Хабр] Что такое формальная верификация?&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;

</content></entry><entry><id>hikkidg:asycnio-and-sync-calls</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/asycnio-and-sync-calls?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>AsyncIO и синхронные вызовы</title><published>2025-03-13T05:19:45.052Z</published><updated>2025-03-13T05:34:32.213Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/5c/73/5c7346d5-942c-46ac-bf84-a7004b4e6389.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/bf/94/bf94c503-8f9e-4486-b75c-1f226d170ed2.png&quot;&gt;Недавно на работе столкнулся с интересным багом связанным с асинхронностью во время написания тестов. Мне нужно было залогиниться в сервисе несколько раз с неправильными данными, чтобы получить блокировку аккаунта (собственно механизм блокировки я и тестировал).</summary><content type="html">
  &lt;h3 id=&quot;С-чего-все-началось&quot;&gt;С чего все началось&lt;/h3&gt;
  &lt;p id=&quot;st9C&quot;&gt;Недавно на работе столкнулся с интересным багом связанным с асинхронностью во время написания тестов. Мне нужно было залогиниться в сервисе несколько раз с неправильными данными, чтобы получить блокировку аккаунта (собственно механизм блокировки я и тестировал).&lt;/p&gt;
  &lt;p id=&quot;wM3x&quot;&gt;Немного про технологический стек: Python 3.11, FastAPI, Redis, PostgreSQL. Тесты — синхронные на PyTest. Есть своя обвязка для запуска интеграционных тестов с запуском необходимого окружения с помощью Docker и самого тестируемого сервиса в контейнере.&lt;/p&gt;
  &lt;p id=&quot;ZdW0&quot;&gt;Довольно типичная задача. Пишу тест — делаем несколько POST запросов по роуту входа с правильным юзернеймом и неправильным паролем и потом пробуем сделать запрос с полностью корректными данными.&lt;/p&gt;
  &lt;figure id=&quot;XNRc&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bf/94/bf94c503-8f9e-4486-b75c-1f226d170ed2.png&quot; width=&quot;499&quot; /&gt;
    &lt;figcaption&gt;План теста&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;Внезапная-проблема&quot;&gt;Внезапная проблема&lt;/h3&gt;
  &lt;p id=&quot;sqFi&quot;&gt;При запуске тестов в первый раз все нормально - тест отработал за 20 сек, вроде кажется долго, но там специфическая система подготовки окружения, спишем на нее эту задержку. Готовлю Pull Request, прогоняю все тесты вместе с моим новым и получаю ошибку &lt;code&gt;TimeoutError&lt;/code&gt; — запрос к роуту логина завершился неудачно, т. к. сервер не ответил в течении 60 сек на запрос, то клиент автоматически его завершил по таймауту.&lt;/p&gt;
  &lt;figure id=&quot;8ktR&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/cc/53/cc5394ee-d16f-40b5-837c-ecb593891cfe.png&quot; width=&quot;417&quot; /&gt;
    &lt;figcaption&gt;Как тест работал реально&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;oi5z&quot;&gt;Ну, может баг или какой-то side effect сработал. Возможно тесты локально плохо работают в целом. Тем более на созвоне говорили, что с интеграционными тестами какая-то беда и они падают регулярно.&lt;/p&gt;
  &lt;p id=&quot;fwwh&quot;&gt;Можно было забить и списать это все на плохой проект, среду, организацию тестов и т д, но что-то здесь было не так...&lt;/p&gt;
  &lt;h3 id=&quot;Локализация-проблемы&quot;&gt;Локализация проблемы&lt;/h3&gt;
  &lt;p id=&quot;abmC&quot;&gt;Основную информацию о работе любого приложения можно получить с помощью логирования. Добавляем дополнительных сообщений после каждой инструкции, понижаем уровень отображения до &lt;code&gt;DEBUG&lt;/code&gt; и смотрим логи.&lt;/p&gt;
  &lt;p id=&quot;QGnj&quot;&gt;Внезапно сервис на самом деле нормально отвечал и воспринимал запрос как вполне обычный. Да, он отвечал на него 67 сек., но статус был правильный — &lt;code&gt;401 Unauthorized&lt;/code&gt; (не смог войти). Почему он не мог мне ответить в тесте быстро?&lt;/p&gt;
  &lt;p id=&quot;IqNF&quot;&gt;Первая мысль, т. к. речь шла о веб-сервисе — где-то происходит блокировка сервиса, а потом его отпускает. Смотрим в код, а код роута логина полностью синхронный, там что-то типа:&lt;/p&gt;
  &lt;pre id=&quot;PgAO&quot;&gt;@router.get(&amp;quot;/login&amp;quot;)
def login(...):
    ...
    settings = settings_provider.get_settings()
    if settings.count_failed_login_attempts:
        account_locker = account_locker_factory.get_or_create(...)
        # processing
    ...
&lt;/pre&gt;
  &lt;p id=&quot;OY3y&quot;&gt;Хоть FastAPI и полностью асинхронный фреймворк, но &lt;strong&gt;&lt;em&gt;если роут описан как синхронная функция, то FastAPI его оборачивает в асинхронную корутину и выполняет в отдельном потоке, чтобы не блокировать работу веб-сервиса&lt;/em&gt;&lt;/strong&gt;. Вроде все продумано, но почему-то сервис не отвечал.&lt;/p&gt;
  &lt;p id=&quot;rZvp&quot;&gt;Дополнительные логи и принты показали, что конкретно в ручке происходит зависание на этапе обращения к другому сервису. Получалась такая картина: тестовый клиент ходит в сервис авторизации, а сервис авторизации в сервис конфигурации за актуальными настройками. И зависает вот этот дополнительный запрос.&lt;/p&gt;
  &lt;figure id=&quot;NdDE&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ec/b9/ecb9c57f-4879-4167-a069-a2bae2c9c98d.png&quot; width=&quot;581&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;J3Hv&quot;&gt;В сервисе конфигурации тоже ничего криминального не было обнаружено, просто запрос в БД, который сам по себе выполнялся за миллисекунды. Но весь ответ сервера работал либо быстро (до секунды), либо зависал на неопределенный срок &lt;strong&gt;до минуты.&lt;/strong&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Цель-обнаружена&quot;&gt;Цель обнаружена&lt;/h3&gt;
  &lt;p id=&quot;BpLk&quot;&gt;Было понятно, что где-то на сервисе конфигурации происходит процесс, который на минуту блокирует сервис, а потом его отпускает, но не было понятно, где его искать.&lt;/p&gt;
  &lt;p id=&quot;UALZ&quot;&gt;Через пару десятков запусков было понятно, что время задержки ответа строго варьируется от 0 до 60 сек. Довольно не случайное число, скорее всего где-то стоит таймаут выполнения задачи, которая должна выполняться не более 60 сек и потом она или выполняется успешно, или ее завершают извне. Внезапно помог обычный поиск числа &lt;code&gt;60&lt;/code&gt; по проекту.&lt;/p&gt;
  &lt;p id=&quot;jB5s&quot;&gt;Нашелся таймер, который используется в библиотеке &lt;code&gt;PortChecker&lt;/code&gt; для проверки доступности сервисов через отправку healthcheck-запросов: если сервис не отвечает, то пробуем минуту до него достучаться. В интеграционных тестах поднимались только необходимые сервисы для запуска конкретных тестов, но общее поведение не менялось, поэтому данные запросы уходили в пустоту и просто каждый раз возвращали ошибку.&lt;/p&gt;
  &lt;figure id=&quot;7TAm&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/40/37/40378d3f-b1aa-4f7d-80bc-f50ac7dbb38d.png&quot; width=&quot;654&quot; /&gt;
    &lt;figcaption&gt;Полная схема запросов&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fbTM&quot;&gt;Но это совсем другой роут, другая функция-обработчик отвечающя за проверку доступов сервиса &lt;code&gt;/healthchecks&lt;/code&gt;. Эти запросы напрямую никак не участвовали в логине.&lt;/p&gt;
  &lt;p id=&quot;CcD2&quot;&gt;Так, код синхронный обнаружен, но ведь я уже выше писал: &lt;em&gt;&amp;quot;FastAPI его оборачивает в асинхронную корутину и выполняет в отдельном потоке&amp;quot;&lt;/em&gt;. В чем же может быть проблема? Обертка написана криво? Может быть неправильно определено место?&lt;/p&gt;
  &lt;p id=&quot;Lm60&quot;&gt;Ошибка закралась в сам роут отвечающий за проверку доступов сервисов: функция была обозначена как асинхронная, но под капотом выполняла синхронные блокирующие запросы. Соответственно для фреймворка &amp;quot;снаружи&amp;quot; это выглядит как неблокирующая функция из-за &lt;code&gt;async&lt;/code&gt;, а то, что внутри она была написана именно в синхронной логике, не беспокоит ни сам фреймворк, ни, видимо, программиста, который ее написал.&lt;/p&gt;
  &lt;pre id=&quot;HJcz&quot;&gt;@route.get(&amp;quot;/healthcheck&amp;quot;)
async def healthcheck_services(...):
    ...
    PortChecker.check(...) # 60 сек пытаемся получить ответ и на это время блокируем весь сервер
    ...
&lt;/pre&gt;
  &lt;p id=&quot;hkFp&quot;&gt;Это один из немногих случаев, когда решение бага состоит не в написании нового кода, а только в удалении старого. Если FastAPI роут написан как синхронная функция, то он его вызывает в потоке и во время этого вызова не блокируется работа всего сервера. &lt;a href=&quot;https://github.com/fastapi/fastapi/blob/master/fastapi/routing.py#L204-L214&quot; target=&quot;_blank&quot;&gt;(ссылка на исходники)&lt;/a&gt;&lt;/p&gt;
  &lt;pre id=&quot;JDxH&quot;&gt;async def run_endpoint_function(
   *, dependant: Dependant, values: Dict[str, Any], is_coroutine: bool
) -&amp;gt; Any:
    ...
    if is_coroutine:
       return await dependant.call(**values)
    else:
       return await run_in_threadpool(dependant.call, **values)
&lt;/pre&gt;
  &lt;figure id=&quot;RQVS&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/86/5c/865c5785-db21-47a5-a893-f5cd0121f569.png&quot; width=&quot;655&quot; /&gt;
    &lt;figcaption&gt;После исправления&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Qqua&quot;&gt;Когда я сделал функцию синхронной, тесты стали стабильно работать и проблем больше не было.&lt;/p&gt;
  &lt;p id=&quot;DLIZ&quot;&gt;Так что далеко не всё становится быстрее, когда бездумно используется асинхронный код.&lt;/p&gt;
  &lt;h3 id=&quot;Полезные-материалы&quot;&gt;Полезные материалы&lt;/h3&gt;
  &lt;ol id=&quot;30tY&quot;&gt;
    &lt;li id=&quot;iPiX&quot;&gt;&lt;a href=&quot;https://dev.to/bitecode/turn-sync-function-to-async-python-tips-58nn&quot; target=&quot;_blank&quot;&gt;[DEV] Turn sync function to async - Python Tips&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;RFP4&quot;&gt;&lt;a href=&quot;https://docs.python.org/3/library/asyncio-task.html#running-in-threads&quot; target=&quot;_blank&quot;&gt;[Python Docs] Coroutines and Tasks - asyncio.to_thread&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;WVCz&quot;&gt;&lt;a href=&quot;https://t.me/advice17/18&quot; target=&quot;_blank&quot;&gt;[Советы разработчикам] Потокобезопасность и конкурентный доступ&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;

</content></entry><entry><id>hikkidg:Programmistskie-sobesedovaniya-v-2024-10-25</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/Programmistskie-sobesedovaniya-v-2024-10-25?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>Программистские собеседования в 2024</title><published>2025-03-13T05:19:58.786Z</published><updated>2025-03-13T05:19:58.786Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/f8/75/f875ba22-7f1f-4057-bf7d-51f63437c815.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/9b/2a/9b2a9625-ae4d-4ed8-93a4-6d6b3246b20e.png&quot;&gt;Недавно я вновь вышел на рынок труда и активно прохожу собеседования в различные компании. Практически все компании проводят этап алгоритмических собеседований или лайвкодинга с задачками.</summary><content type="html">
  &lt;p id=&quot;Apj3&quot;&gt;Недавно я вновь вышел на рынок труда и активно прохожу собеседования в различные компании. Практически все компании проводят этап алгоритмических собеседований или лайвкодинга с задачками.&lt;/p&gt;
  &lt;p id=&quot;y9od&quot;&gt;Я собрал некоторые из них для разбора.&lt;/p&gt;
  &lt;h3 id=&quot;1.-Map&quot;&gt;1. Map&lt;/h3&gt;
  &lt;h4 id=&quot;Условие&quot;&gt;Условие&lt;/h4&gt;
  &lt;p id=&quot;UUjw&quot;&gt;Необходимо написать класс, который хранит в себе по уникальному ключу некоторый набор значений и наиболее эффективно реализует четыре операции: &lt;code&gt;set&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt; и &lt;code&gt;is_all_values_unique&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;qewB&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9b/2a/9b2a9625-ae4d-4ed8-93a4-6d6b3246b20e.png&quot; /&gt;
  &lt;/figure&gt;
  &lt;h4 id=&quot;Решение&quot;&gt;Решение&lt;/h4&gt;
  &lt;p id=&quot;pGt4&quot;&gt;Для упрощения задачи представим, что все ключи и значения могут быть хэшируемы.&lt;/p&gt;
  &lt;p id=&quot;R3km&quot;&gt;Т. к. у нас есть операции &lt;code&gt;set&lt;/code&gt; и &lt;code&gt;get&lt;/code&gt; и уникальный набор ключей, то в качестве контейнера для данных лучше всего выбрать класс &lt;code&gt;dict&lt;/code&gt;. У него операции вставки и поиска по ключу происходят за O(1).&lt;/p&gt;
  &lt;p id=&quot;Ylop&quot;&gt;Таким образом, мы можем сделать первый набросок:&lt;/p&gt;
  &lt;figure id=&quot;sX3l&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3e/3b/3e3b1312-4126-4d48-9c00-3dcb5183fd39.png&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;JV9a&quot;&gt;Для реализации последнего условия можно пойти двумя путями:&lt;/p&gt;
  &lt;ol id=&quot;w8V9&quot;&gt;
    &lt;li id=&quot;JD9i&quot;&gt;Реализовать метод &amp;quot;в лоб&amp;quot; просто добавлением всех значий словаря в &lt;code&gt;set&lt;/code&gt; и потом проверить равна ли длина множества значений количеству ключей словаря. Сложность по времени &lt;code&gt;O(n)&lt;/code&gt; и по памяти тоже &lt;code&gt;O(n)&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;Eeqi&quot;&gt;Добавить в класс &lt;code&gt;Map&lt;/code&gt; еще один словарь, в котором будут инвертированы ключи и счетчик значений, чтобы можно было быстро отвечать на вопрос: &amp;quot;Все ли добавленные значения уникальны?&amp;quot;. Дополнительно необходимо добавить в методы обновления и удаления дополнинельные операции с новым словарем. В таком случае мы получим сложность по времени &lt;code&gt;O(1)&lt;/code&gt; и по памяти &lt;code&gt;O(n)&lt;/code&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;figure id=&quot;VNJ5&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/18/c8/18c8e3c5-d4f3-4798-95c5-b229b711e1d9.png&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dI5Y&quot;&gt;В данной реализации можно вынести в отдельный метод удаление старого значения из второго словаря, но, в целом, это уже неплохое решение, в котором все операции работают за &lt;code&gt;O(1)&lt;/code&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;2.-Поиск-симметрии&quot;&gt;2. Поиск симметрии&lt;/h3&gt;
  &lt;p id=&quot;3njp&quot;&gt;Эту задачу в разных формулировкх получал на 4-х собеседованиях минимум.&lt;/p&gt;
  &lt;h4 id=&quot;Условие&quot;&gt;Условие&lt;/h4&gt;
  &lt;p id=&quot;DMbd&quot;&gt;Дан набор точек на плоскости с координатами &lt;code&gt;(x,y)&lt;/code&gt;, необходимо найти такую точку на оси X, через которую можно провести ось симметрии параллельную оси Y.&lt;/p&gt;
  &lt;figure id=&quot;DHBY&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4a/78/4a7832cc-5b80-45ec-8762-821062212cda.png&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;Решение&quot;&gt;Решение&lt;/h3&gt;
  &lt;p id=&quot;cZ2k&quot;&gt;Если ось симметрии можно провести, то она должна лежать ровно по середине между самой левой и самой правой точками. Для того чтобы их найти, можно отсортировать массив точек по X &lt;code&gt;sorted(a, key=lambda point: point[0])&lt;/code&gt; сложность &lt;code&gt;O(n log n)&lt;/code&gt;. Или просто в цикле найти min/max, в таком случае сложность будет &lt;code&gt;O(n)&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;ZIuR&quot;&gt;После нахождения потенциальной точки симметрии по X необходимо проверить, действительно есть ли для каждой точки в массиве своя симметричная пара. Решение в лоб - перебор массива вложенным циклом и поиск для каждой точки &lt;code&gt;(x+/-symmetric_x;y)&lt;/code&gt;. Чтобы уменьшить сложность алгоритма, можем воспользоваться множеством - поиск элемента во множестве &lt;code&gt;O(1)&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;Z5Zc&quot;&gt;Решение целиком выглядит так:&lt;/p&gt;
  &lt;figure id=&quot;ri9V&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1a/8d/1a8dd4a9-a4b3-449d-9081-2b062ca33de0.png&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;3.-Больше-половины&quot;&gt;3. Больше половины&lt;/h3&gt;
  &lt;h4 id=&quot;Условие&quot;&gt;Условие&lt;/h4&gt;
  &lt;p id=&quot;UVC4&quot;&gt;Дан массив элементов. Гарантируется, что в этом массиве есть такой элемент, который встречается больше &lt;code&gt;n / 2&lt;/code&gt; раз. Необходимо найти этот элемент.&lt;/p&gt;
  &lt;h4 id=&quot;Решение&quot;&gt;Решение&lt;/h4&gt;
  &lt;p id=&quot;jwZq&quot;&gt;Здесь можно пойти двумя путями:&lt;/p&gt;
  &lt;ol id=&quot;IRB2&quot;&gt;
    &lt;li id=&quot;M80c&quot;&gt;Отсортировать массив и выбрать центральный элемент. Сложность по времени &lt;code&gt;O(n log n)&lt;/code&gt;, потребление памяти - &lt;code&gt;O(1)&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;nh79&quot;&gt;Через счетчик элементов в виде словаря. Сложность по времени &lt;code&gt;O(n)&lt;/code&gt;, потребление памяти - &lt;code&gt;O(n)&lt;/code&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;figure id=&quot;9hys&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/50/13/501343b5-d7b6-4c62-b377-a80f3c92cf36.png&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;4.-Дороги&quot;&gt;4. Дороги&lt;/h3&gt;
  &lt;h4 id=&quot;Условие&quot;&gt;Условие&lt;/h4&gt;
  &lt;p id=&quot;YnLy&quot;&gt;Дан массив однонаправленных дорог. Гарантируется, что нет циклов. Сколькими способами можно добраться из пункта 1 в пункт N.&lt;/p&gt;
  &lt;pre id=&quot;zY1I&quot;&gt;&amp;gt;&amp;gt;&amp;gt; ([[1,2], [2,4], [1,3], [3,4], [1,4]], 4) =&amp;gt; 3
&lt;/pre&gt;
  &lt;h4 id=&quot;Решение&quot;&gt;Решение&lt;/h4&gt;
  &lt;p id=&quot;lzp0&quot;&gt;Эту задачу можно решить через обычный поиск в глубину (DFS) и подсчет полученных путей до самого конца графа или с помощью динамического программирования. Разберем второй вариант, т. к. он наиболее эффективный.&lt;/p&gt;
  &lt;figure id=&quot;XG45&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/46/07/46077e0d-ba2e-4189-ad8a-0422650aca2c.png&quot; /&gt;
  &lt;/figure&gt;

</content></entry><entry><id>hikkidg:Pro-Obsidian-08-31</id><link rel="alternate" type="text/html" href="https://teletype.in/@hikkidg/Pro-Obsidian-08-31?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=hikkidg"></link><title>Про Obsidian</title><published>2025-03-13T05:20:15.678Z</published><updated>2025-03-13T05:20:15.678Z</updated><summary type="html">Obsidian можно найти тут https://obsidian.md/</summary><content type="html">
  &lt;h3 id=&quot;Установка&quot;&gt;Установка&lt;/h3&gt;
  &lt;p id=&quot;MQtj&quot;&gt;Obsidian можно найти тут &lt;a href=&quot;https://obsidian.md/&quot; target=&quot;_blank&quot;&gt;https://obsidian.md/&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;0RdX&quot;&gt;Для Windows и MacOS можно скачать прям с официального сайта. Для Linux лучше использовать &lt;a href=&quot;https://flathub.org/apps/md.obsidian.Obsidian&quot; target=&quot;_blank&quot;&gt;flathub&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;eHJd&quot;&gt;Для iPhone и Android есть фирменные приложения.&lt;/p&gt;
  &lt;h3 id=&quot;Как-пользоваться?&quot;&gt;Как пользоваться?&lt;/h3&gt;
  &lt;p id=&quot;I779&quot;&gt;Создать первое хранилище в виде обычной папки и просто начать.&lt;/p&gt;
  &lt;p id=&quot;hVpU&quot;&gt;Вообще это просто заметки - текстовые файлы с различным содержимым. Но есть и свои классные фишки типа связей в виде графов между заметками, множества плагинов и расширенным стандартом Markdown, который позволяет очень удобно и красиво все оформлять. С помощью плагинов и небольшой автоматизации можно полностью настроить процесс ведения заметок под себя.&lt;/p&gt;
  &lt;p id=&quot;XLOr&quot;&gt;В качестве отправной точки можно начать с этих видео:&lt;/p&gt;
  &lt;ul id=&quot;UPmi&quot;&gt;
    &lt;li id=&quot;4wI4&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=unvwJRgX2bs&quot; target=&quot;_blank&quot;&gt;https://www.youtube.com/watch?v=unvwJRgX2bs&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;b3mU&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=zYlDnmlo39Q&quot; target=&quot;_blank&quot;&gt;https://www.youtube.com/watch?v=zYlDnmlo39Q&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;UoF6&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=rkfJqSjKF60&quot; target=&quot;_blank&quot;&gt;https://www.youtube.com/watch?v=rkfJqSjKF60&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;Синхронизация&quot;&gt;Синхронизация&lt;/h3&gt;
  &lt;p id=&quot;TbKm&quot;&gt;Одной из продающих (лично для меня) историй с Obsidian была концепция полного владения над своими заметками. То есть они лежат по умолчанию только на одном конкретном вашем устройстве, где они были созданы. Чтобы решить эту проблему есть как минимум три варианта:&lt;/p&gt;
  &lt;ul id=&quot;F8OG&quot;&gt;
    &lt;li id=&quot;iGPf&quot;&gt;Подписка Obsidian Sync&lt;/li&gt;
    &lt;li id=&quot;qNJF&quot;&gt;Сервис хостинга проектов с использованием системы контроля версий (Gitlab, Github)&lt;/li&gt;
    &lt;li id=&quot;dJGj&quot;&gt;Настройка синхронизации через облачные диски (Яндекс, Google и проч)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h4 id=&quot;Синхронизация-через-Github/Gitlab&quot;&gt;Синхронизация через Github/Gitlab&lt;/h4&gt;
  &lt;p id=&quot;ZFLS&quot;&gt;Идея данного метода очень проста - Github и Gitlab хорошо умеют работать с текстовыми файлами и предоставляют возможность бесплатного хранения папки с файлами причем с историей изменения благодаря Git. Таким образом, можно создать папку (репозиторий) в сервисе и с помощью утилиты &lt;code&gt;git&lt;/code&gt; синхронизировать ее с локальной папкой.&lt;/p&gt;
  &lt;p id=&quot;McEH&quot;&gt;Чтобы реализовать такую синхронизацию можно воспользоваться расширением &lt;a href=&quot;https://github.com/Vinzent03/obsidian-git&quot; target=&quot;_blank&quot;&gt;https://github.com/Vinzent03/obsidian-git&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;B4Mj&quot;&gt;Гайд как это можно сделать &lt;a href=&quot;https://desktopofsamuel.com/how-to-sync-obsidian-vault-for-free-using-git&quot; target=&quot;_blank&quot;&gt;https://desktopofsamuel.com/how-to-sync-obsidian-vault-for-free-using-git&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;oYMB&quot;&gt;Сразу нужно оговориться, что такой метод не подойдет для мобильных устройств, т. к. там довольно сложно установить &lt;code&gt;git&lt;/code&gt; и настроить его фоновую работу.&lt;/p&gt;
  &lt;h4 id=&quot;Синхронизация-через-облачные-диски&quot;&gt;Синхронизация через облачные диски&lt;/h4&gt;
  &lt;p id=&quot;i8Hw&quot;&gt;Наиболее простой и универсальный метод.&lt;/p&gt;
  &lt;p id=&quot;9UDk&quot;&gt;Практически все программы для работы с облачными дисками предлагают функционал синхронизации локальной папки с облачным хранилищем.&lt;/p&gt;
  &lt;p id=&quot;duq0&quot;&gt;Просто создаем хранилище Obsidian внутри этой папки и всё. Это без проблем работает с ноутбуками/компьютерами, но мобильные версии таких программ обычно не дают возможность синхронизации обычной папки с файлами на диск. Но есть программа &lt;a href=&quot;https://4pda.to/forum/index.php?showtopic=258965&quot; target=&quot;_blank&quot;&gt;FolderSync Pro&lt;/a&gt;, которая позволяет настраивать синхронизации с различными облачными хранилищами локально на телефоне.&lt;/p&gt;
  &lt;p id=&quot;HcGr&quot;&gt;Мною было активно использована на платформе Andorid на нескольких устройствах одновременно. Как дела обстоят у iPhone, не знаю.&lt;/p&gt;
  &lt;h3 id=&quot;Как-пользуюсь-сам?&quot;&gt;Как пользуюсь сам?&lt;/h3&gt;
  &lt;p id=&quot;wvNW&quot;&gt;Т. к. на ноутбуке у меня стоит Linux, то пришлось искать облачные диски с поддержкой данной платформы. Выбор стоял между Яндексом и Mega. У обоих неплохие приложения, которые без проблем ставятся на большинство современных дистрибутивов. В силу внешнеполитической обстановки выбрал Яндекс, т. к. шанс, что от него отключат очень низкий. (Если съедает паранойя на этот счет, то лучше поднять собственный сервер и сделать синхронизацию с ним по SFTP, SMB). На телефоне и планшете под Android стоит Folder Sync Pro, скачанный c 4PDA. На ноутбуке под управлением Fedora &lt;a href=&quot;https://yandex.ru/support/disk-desktop-linux/installation.html&quot; target=&quot;_blank&quot;&gt;Yandex Disk для Linux&lt;/a&gt;.&lt;/p&gt;

</content></entry></feed>