<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Oleg Mifle</title><generator>teletype.in</generator><description><![CDATA[Oleg Mifle]]></description><image><url>https://img2.teletype.in/files/d1/a0/d1a0292d-e273-476f-9241-ab0860aad838.png</url><title>Oleg Mifle</title><link>https://teletype.in/@mifleo</link></image><link>https://teletype.in/@mifleo?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/mifleo?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/mifleo?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Fri, 22 May 2026 12:19:58 GMT</pubDate><lastBuildDate>Fri, 22 May 2026 12:19:58 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@mifleo/system-design-courier-location</guid><link>https://teletype.in/@mifleo/system-design-courier-location?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo</link><comments>https://teletype.in/@mifleo/system-design-courier-location?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo#comments</comments><dc:creator>mifleo</dc:creator><title>Разбор задачи system design - передача местоположения курьеров.</title><pubDate>Tue, 04 Nov 2025 08:16:32 GMT</pubDate><description><![CDATA[Привет. В комментариях к посту с историями про собесы была просьба разобрать задачу. Выполняю)]]></description><content:encoded><![CDATA[
  <p id="AnNa">Привет. В комментариях к <a href="https://t.me/ask_for_oleg/108" target="_blank">посту</a> с историями про собесы была просьба разобрать задачу. Выполняю)</p>
  <blockquote id="VaPH">Я попробовал восстановить так, как это было на самом собесе, ничего не добавляя постафктум. Решение не может отличается от того, как сделали бы вы, это нормально.</blockquote>
  <p id="jTI5">Задача была поставлена следующим образом (с учётом собранных мною требований):</p>
  <p id="BNTH">Представьте, что вы проектируете бэкенд для клиентского приложения доставки. Пользователь открывает экран заказа и хочет увидеть, где сейчас находится его курьер.</p>
  <p id="LPSI">На вход у нас есть два внешних API, на которые мы не можем повлиять:</p>
  <ol id="z0xq">
    <li id="hDJH">Order API — по order_id возвращает данные по заказу, в т.ч. и courier_id. Среднее время ответа — 1 секунда.</li>
    <li id="vRwy">Courier API — по courier_id возвращает текущие координаты. Среднее время ответа — ещё 1 секунда.</li>
  </ol>
  <p id="m22G">Наш сервис должен принимать запрос от клиентского приложения вида</p>
  <pre id="APGC">GET /orders/{order_id}/location</pre>
  <p id="4kOP">и отвечать координатами курьера: широта и долгота.</p>
  <p id="dflo">Жёсткое требование:</p>
  <p id="Ocvx">время ответа сервиса не должно превышать<strong> 300 мс</strong>,</p>
  <p id="YAMs">даже при высокой нагрузке и географически распределённой системе.</p>
  <p id="Kzvj">Дополнительные условия:</p>
  <p id="eU8Z">- Мы не знаем список заказов заранее — о заказе узнаём, когда клиент впервые запрашивает его позицию.</p>
  <p id="k0fd">- Внешние API трогать или ускорять нельзя.</p>
  <p id="ea0D">- Сервис должен быть масштабируемым, отказоустойчивым и геораспределённым. 🐱</p>
  <p id="XdKs">То есть нам нужно уложиться в SLA 0.3 секунды, хотя единственное место, где есть нужные данные, отвечает ~2 секунды. 😨</p>
  <p id="hF00">Как это сделать? Чистая архитектура, никаких костылей.</p>
  <p id="O2aG">Чтобы уложиться в SLA по времени отклика и при этом не зависеть от скорости внешних API, система строится вокруг нескольких ключевых принципов:</p>
  <h3 id="Y10T">1️⃣ Асинхронное наполнение данных (warm-up).</h3>
  <p id="CgBc">Первый запрос по заказу не вызывает цепочку из двух медленных API. Вместо этого сервис мгновенно возвращает location = null, а фоновый воркер начинает процесс получения данных:</p>
  <ul id="Fmsu">
    <li id="gCtt">обращается к Order API, чтобы получить courier_id;</li>
    <li id="Qlid">по этому courier_id запрашивает координаты в Courier API;</li>
    <li id="m5PF">сохраняет результат в Cassandra.</li>
  </ul>
  <p id="Whzy">После этого все последующие запросы по данному заказу читают данные напрямую из базы.</p>
  <h3 id="ODb4">2️⃣ Хранение в Cassandra без промежуточных кэшей.</h3>
  <p id="gsnL">Сервис не использует локальные in-memory или Redis-кэши. Все данные хранятся централизованно в Apache Cassandra, которая обеспечивает:</p>
  <ul id="4Aie">
    <li id="TIYB">быструю запись (до десятков тысяч upsert-ов в секунду на ноду),</li>
    <li id="AecH">горизонтальное масштабирование без единой точки отказа,</li>
    <li id="NKxa">репликацию между регионами.</li>
  </ul>
  <p id="b4zG">Используется стратегия NetworkTopologyStrategy с ReplicationFactor = 3 на каждый регион и уровень согласованности LOCAL_QUORUM — это позволяет читать и писать данные локально, не дожидаясь удалённых DC.</p>
  <h3 id="cdYI">Почему Cassandra, а не Redis?</h3>
  <p id="RyD5">При выборе хранилища ключевым фактором стали характер нагрузки и требования к надёжности.</p>
  <ul id="Ir6K">
    <li id="gBni">Профиль нагрузки — write-heavy<br />Каждый активный курьер обновляет координаты каждые 2–5 секунд. При десятках тысяч активных курьеров это десятки тысяч UPSERT-операций в секунду.<br />Такая нагрузка типична не для кэш-систем, а для постоянных write-heavy хранилищ, оптимизированных под линейное масштабирование и дешёвые вставки.</li>
    <li id="QgH3">Геораспределённость и отказоустойчивость<br />Redis, даже в кластерном режиме, не предоставляет аналогичной модели актив-актив без внешней прослойки (обычно нужен либо CRDT-слой, либо дорогая мульти-мастер-репликация).</li>
    <li id="L1AF">Консистентность и долговечность<br />Для геопозиции допустима eventual consistency, но при этом важно, чтобы данные не терялись.</li>
  </ul>
  <p id="Tdql">Cassandra гарантирует сохранность записей после достижения кворума реплик, даже если узлы падают.</p>
  <p id="LCj5">Redis, напротив, ориентирован на скорость чтения и простоту, а не на долговечность — потеря узла или выключение snapshot-механизма может привести к частичной потере данных.</p>
  <h3 id="VZc7">3️⃣ Разделение API и воркеров</h3>
  <ul id="pFcj">
    <li id="jnDB">API-слой отвечает только за быстрые запросы клиента и формирование ответов (200 OK с координатами или warmingUp:true).</li>
    <li id="MvTu">Воркеры асинхронно взаимодействуют с внешними сервисами, обновляют данные в Cassandra и управляют фоновыми циклами обновления координат курьеров.</li>
  </ul>
  <p id="PkQ7">Один воркер может обслуживать сотни активных курьеров, выполняя обновления каждые 2–5 секунд.</p>
  <h3 id="jFvL">4️⃣ Схема хранения данных.</h3>
  <p id="5bgY">Используются две основные таблицы:</p>
  <pre id="UjUV">CREATE TABLE order_to_courier (
  order_id  text PRIMARY KEY,
  courier_id  text,
  assigned_ts timestamp
);
  
CREATE TABLE courier_location_current (
  courier_id  text PRIMARY KEY,
  lat double,
  lon double,
  provider_ts timestamp,
  updated_ts  timestamp
);</pre>
  <p id="i0EC">order_to_courier заполняется один раз на заказ, courier_location_current обновляется каждые несколько секунд.</p>
  <h2 id="7IsV"><strong>Поведение системы</strong></h2>
  <h3 id="bTcS">1️⃣ Клиент делает запрос GET /orders/{orderId}/location.</h3>
  <p id="e1lJ">Сервис обращается к Cassandra:</p>
  <ul id="FTR1">
    <li id="VyvC">если данные есть — возвращает координаты;</li>
    <li id="N1el">если нет — отдаёт null и ставит задачу в очередь.</li>
  </ul>
  <h3 id="hqx6">2️⃣ Воркеры обрабатывают очередь:</h3>
  <ul id="4iFG">
    <li id="HBHr">получают courier_id и координаты;</li>
    <li id="G1XH">обновляют таблицы order_to_courier и courier_location_current.</li>
  </ul>
  <h3 id="JZkU">3️⃣ Следующие запросы обслуживаются из Cassandra — без дополнительных внешних вызовов.</h3>
  <p id="uJRn"></p>
  <figure id="srh6" class="m_retina">
    <img src="https://img4.teletype.in/files/77/b6/77b65737-6a7f-4e4e-bf02-32af2aa5a548.png" width="581.5" />
    <figcaption>Схема приложения</figcaption>
  </figure>
  <figure id="7Twv" class="m_retina">
    <img src="https://img3.teletype.in/files/ee/0f/ee0f347d-c506-4bcb-b71c-2e45546dd7bf.png" width="773.5" />
    <figcaption>Течение данных</figcaption>
  </figure>
  <p id="nTiI">Что бы вы сделали иначе?</p>
  <p id="yxoy">⸻</p>
  <p id="LheR">Давайте оставаться на связи  - https://t.me/ask_for_oleg ☄️</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@mifleo/my-network</guid><link>https://teletype.in/@mifleo/my-network?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo</link><comments>https://teletype.in/@mifleo/my-network?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo#comments</comments><dc:creator>mifleo</dc:creator><title>Строим домашнюю сеть</title><pubDate>Wed, 27 Aug 2025 13:47:28 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/32/1f/321f9687-069a-4489-bb27-d6527f527142.png"></media:content><description><![CDATA[<img src="https://img2.teletype.in/files/93/df/93df486a-766c-4e7b-afca-9fe44bd61ba6.png"></img>Всем привет. Сегодня пусть будет не про архитектуру. Точнее, и про архитектуру, но не ту.]]></description><content:encoded><![CDATA[
  <figure id="Qxhk" class="m_retina">
    <img src="https://img2.teletype.in/files/93/df/93df486a-766c-4e7b-afca-9fe44bd61ba6.png" width="768" />
  </figure>
  <p id="V6s9">Всем привет. Сегодня пусть будет не про архитектуру. Точнее, и про архитектуру, но не ту.</p>
  <p id="0ZKk">Год назад мы с семьёй переехали в свою квартиру. Получился ещё тот квест, но это совсем другая история. Обустраиваемся тут, заканчиваем ремонт. Мне, как удалёнщику со стажем, важно сделать себе рабочее место и провести интернет хорошего качества. В принципе, первое, что появилось в доме, - это роутер. Причём это Huawei от Ростелекома. Не совсем отвратный, но и не топ. Причина появления именно такого роутера в том, что подключение происходит по оптике, а не витой паре. Мой прежний роутер такое не умел. В целом интернет работал, скорость была нормальная, всё в порядке. Так я думал, пока мы окончательно не перебрались домой. Не знаю, из чего у нас сделаны стены, но сигнал роутера нормального качества есть только в прихожей, где он и находится. Ну или в зоне прямой видимости. В других комнатах качество сигнала сильно деградирует.</p>
  <p id="Lt38">Я скачал тулзу для измерения уровня сигнала и походил по комнатам. В спальне уровень сигнала 30%, на рабочем месте - всего 20%. При этом если смещаюсь на 30 сантиметров назад и встаю напротив двери, откуда роутер почти видно, сигнал уже 85%. То есть до рабочего места сигналу надо преодолеть 5 стен, и это его почти полностью гасит. В старой квартире ситуация была почти такой же (по препятствиям), но там сигнал и качество деградировали не так сильно (по субъективной оценке, я тогда не замерял).</p>
  <p id="jfJ5">Помучался я так пару недель и понял, что не готов терпеть, потому что проще переключиться на мобильный интернет и раздавать через телефон - скорость выше, сигнал стабильнее.</p>
  <h3 id="hDCr"><strong>Попытка 1</strong></h3>
  <p id="SG4R">Решил подойти к проблеме в лоб. Достал свой старый роутер (ASUS RT-AC68U), связал его с Huawei как ретранслятор. Asus разместил рядом с рабочим местом, назначил ему такой же SSID с постфиксом rep. Ожидаемо, качество сигнала стало отличным -роутер же рядом. Но вот интернет остался плохим. Asus мог работать как ретранслятор только в диапазоне 2,4 ГГц. К тому же у него мощнее покрытие, из-за чего сеть не терялась даже в ближайшей к Huawei комнате. Получалось комбо: сильный сигнал и слабый интернет. Так прошёл ещё месяц, после чего я перешёл к следующей итерации.</p>
  <h3 id="4pMV"><strong>Попытка 2</strong></h3>
  <p id="46nJ">Раз у Asus лучше покрытие, значит можно поставить его в удачное место и наслаждаться связью, которая ловит даже на парковке. Я соединил оба роутера витой парой, настроил у Asus SSID, а у Huawei вообще выключил Wi-Fi, чтобы не создавать интерференцию. Качество сигнала на рабочем месте поднялось до 30% - уже лучше. Но 5 ГГц деградировал до 2,4 ГГц и не выдавал больше 15 Мб/сек. В целом стало лучше, чем было, но на созвонах и лекциях картинка всё ещё фризилась. Да и загрузка контента часто подвисала. Так прошло ещё полгода.</p>
  <h3 id="WqnJ"><strong>Попытка 3</strong></h3>
  <p id="Wv6K">Всё-таки дискомфорт перевесил мою лень. Я решил поискать более системное решение вопроса и сдуть пыль с диплома инженера по сетям.</p>
  <p id="8c3b">Какие варианты есть у современного мира?</p>
  <p id="kRrG">Самый очевидный - проложить витую пару до середины квартиры. Тогда сигнал будет <s>всратый</s> равномерный для всех. Решение хорошее, но, так как чистовая отделка уже сделана, а я не люблю висящие и валяющиеся провода, вариант был отложен до лучших времён.</p>
  <ol id="5BHy">
    <li id="2Wfn">Powerline-адаптеры (интернет через розетку). По витой паре передаются электрические импульсы, их можно пустить и через электросеть. Но, во-первых, от топологии проводки будет зависеть, насколько деградирует сигнал, а во-вторых, у меня комнаты разделены по автоматам, и нет уверенности, что это вообще заведётся.</li>
    <li id="ctlK">Использовать специализированный повторитель (усилитель). Я с этим мало работал, но почитал и понял, что это создаст ещё одну сеть, плюс съест скорость, и результат будет как в первой попытке.</li>
    <li id="jIDf">Mesh-система. Это беспроводная ячеистая сеть (WMN) с бесшовным роумингом. Она может работать как по проводу, так и по воздуху. В отличие от обычных репитеров, здесь используется выделенный транспортный канал (Backhaul), чтобы отделить служебный трафик. Это позволяет сохранить производительность. При этом у меня всегда один SSID, а подключение происходит к ближайшему роутеру. Управлять всей сетью можно как единым целым из любой точки квартиры.</li>
    <li id="0mqB">Можно было сделать два роутера с одним SSID и настроить расстояние, при котором роутер отменяет сигнал.</li>
    <li id="yq2K">Использовать WDS. Но я почитал - слишком сложно, хотя и похоже на рабочий вариант. Всё равно будет резать скорость.</li>
  </ol>
  <p id="Rd3n">В итоге я остановился на Mesh-системе с Wireless Backhaul. Правда, мои старые роутеры меш не поддерживали, поэтому пришлось раскошелиться на лакшери Keenetic. Настройка очень простая: на ведомом роутере надо переключить тумблер режима, связать главное устройство с ведомым и всё. Дальше магия WMN на канальном и сетевом уровнях модели OSI.</p>
  <p id="nxVj">В результате получилась топология:</p>
  <p id="lHRa">Huawei (как адаптер PON-LAN) → Keenetic Voyager Pro KN-3510 → Keenetic Speedster KN-3013.</p>
  <h3 id="dNuS"><strong>Итог</strong>: </h3>
  <p id="fVBV">один SSID для 2,4 и 5 ГГц на всех роутерах внутри сети. На рабочем месте сигнал 75% и скорость 200 Мб/сек. Потеря скорости составила ~55%. Не идеально, но лучшее из всего, что было. Из минусов - требуется перенастройка умных устройств.</p>
  <p id="oHZ3"></p>
  <p id="fAac"><a href="https://t.me/ask_for_oleg" target="_blank">Давайте оставаться на связи!</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@mifleo/feature-flags</guid><link>https://teletype.in/@mifleo/feature-flags?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo</link><comments>https://teletype.in/@mifleo/feature-flags?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo#comments</comments><dc:creator>mifleo</dc:creator><title>🚩Какая польза от Feature Flags 🚩</title><pubDate>Mon, 26 May 2025 17:42:08 GMT</pubDate><description><![CDATA[Что самое мощное в этом инструменте? Включение какого-то функционала? Я уверен, что не только.]]></description><content:encoded><![CDATA[
  <p id="r7hm">Что самое мощное в этом инструменте? Включение какого-то функционала? Я уверен, что не только.</p>
  <p id="Hmlc">Вообще, давно хочу раскрыть такую тему как Фича-флаги (или feature flags, feature toggles). Тема хоть и старая, но актуальная и интересная. Они бывают <em>статические</em>, т.е. устанавливаются на этапе сборки проекта, и <em>динамические</em>, это когда можно менять прямо в рантайме поведение приложения.</p>
  <p id="m6NJ">Соответственно, реализация может быть</p>
  <p id="q2ar">🧩 через конфиг (env, обычный файлик с конфигами)</p>
  <p id="PQwp">🧩 через штатную базу данных проекта (обычно это реляционная СУБД)</p>
  <p id="4tph">🧩 через in-memory DB (redis, memcache etc.)</p>
  <p id="Vofg">🧩 через 3d-party приложение (LaunchDarkly, Flagsmith и т.д.). В Gitlab (правда в Premium) тоже можно, кстати.</p>
  <p id="3xjG">🧩 своё решение 🧠 - тут уже кто на что способен.</p>
  <p id="c4C5">Соответственно, статические это быстро и дёшево, но не гибко. Нужно включить - передеплоивай приложение. Ну это ладно, это пол беды. Чтобы закатить что-то обратно - тоже нужно передеплоить. Это уже больнее.</p>
  <p id="DhnM">С динамическими этот процесс проще - поменял стейт флага, что-то включилось, поменял обратно - выключилось. И никаких передеплоев. Красота. Включить или выключить функционал сильно проще, чем запускать сборку, деплой и прочее. Особенно, если у вас целый пайплайн из процессов для того чтобы выкатить одну строчку. Особенно, если нужно отключить фичу.</p>
  <h2 id="kgNs">🎢 <em>Плавный rollout</em>.</h2>
  <p id="2p8a">Это одно из моих самых любимых.</p>
  <p id="mS4X">Фича флаги можно отлично применить для плавной раскатки. Включаем на процент пользователей, смотрим на графики: ошибки, загруженность ресурсов, алерты в Sentry. Если есть проблема она быстро себя проявит на небольшом числе пользователей, это поможет быстро её идентифицировать и пофиксить.</p>
  <p id="BWlV">В коде можно что-то типа такого сделать</p>
  <pre id="L5SM" data-lang="php">&lt;?php

final class FeatureRollout
{
    public function __construct(
        private readonly int $percentage // 0–100
    ) {
        if ($this-&gt;percentage &lt; 0 || $this-&gt;percentage &gt; 100) {
            throw new InvalidArgumentException(&#x27;Percentage must be between 0 and 100.&#x27;);
        }
    }

    public function isFeatureEnabledFor(string $userIdentifier): bool
    {
        $hash = crc32($userIdentifier);
        $bucket = $hash % 100;

        return $bucket &lt; $this-&gt;percentage;
    }
}

$rollout = new FeatureRollout(30); // включено для 30% пользователей

$userId = &#x27;some-uuid&#x27;;

if ($rollout-&gt;isFeatureEnabledFor($userId)) {
    echo &#x27;Показать новую фичу&#x27;;
} else {
    echo &#x27;Оставить старое поведение&#x27;;
}</pre>
  <p id="fNfc">тогда конфигурироваться должно и состояние флага, и процент раскатки.<br /></p>
  <h2 id="6gkS">По категориям</h2>
  <p id="zaUN">🔥 <strong>Release Toggles</strong> - позволяют интегрировать незавершённые функции в основную ветку кода, отключая их до готовности к релизу.<br /><br />Постоянно пользуюсь. Когда нужно делать крупные задачи, то разделить их на мелкие части и доставлять понемногу проще. Так не накапливается огромная куча непроревьювленного кода, который обрастает конфликтами. Минус такого подхода, что всё нужно будет перетестировать в конце.<br /></p>
  <p id="AaDM">🔥 <strong>Experiment Toggles</strong> - используются для A/B-тестирования и оценки пользовательских реакций на различные варианты функциональности.</p>
  <p id="FQvh">🔥 <strong>Ops Toggles</strong> - предоставляют возможность оперативного включения или отключения функций в зависимости от условий эксплуатации или производительности системы.<br /><br />Ну тут всё понятно. Самый кайф.</p>
  <p id="6vkY">🔥 <strong>Permissioning Toggles</strong> - ограничивают доступ к определённым функциям для конкретных групп пользователей, например, только для премиум-клиентов.</p>
  <h2 id="KPSF"><br />По стратегии использования</h2>
  <p id="ppaT">🧩 Краткосрочные - для временного использования и должны быть удалены после завершения эксперимента (расктаки).</p>
  <p id="PLe4">🧩 Долгосрочные - требуют постоянного управления и документирования. Они с нами надолго.<br /></p>
  <h2 id="19f7">Как обычно, у любого инструмента есть своя цена</h2>
  <p id="2bjV">⚠️ Сложность тестирования - yвуличие множества флагов может привести к комбинаторному взрыву вариантов для тестирования. Необходимо тщательно планировать стратегии тестирования.</p>
  <p id="Vdg3">⚠️ Технический долг - неудалённые или устаревшие флаги увеличивают сложность системы и могут привести к ошибкам. Важно регулярно проводить ревизию и удалять неиспользуемые флаги. В одной компании у нас через какое-то время после раскатки приходил бот и ставил задачу на выпиливание флага.</p>
  <p id="iE4A">⚠️ Если используем внешнюю систему, то нужно позаботится о кешировании флагом и об инвалидации кеша. Либо как хранить флаги поближе к коду. Ходить по http - не вариант.<br /><br />-<br /><br /><a href="https://t.me/ask_for_oleg" target="_blank">Давайте оставаться на связи</a> </p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@mifleo/nwWFwyYdM37</guid><link>https://teletype.in/@mifleo/nwWFwyYdM37?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo</link><comments>https://teletype.in/@mifleo/nwWFwyYdM37?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=mifleo#comments</comments><dc:creator>mifleo</dc:creator><title>Мьютексные блокировки в php фреймворках</title><pubDate>Wed, 09 Apr 2025 09:51:00 GMT</pubDate><description><![CDATA[Решил я разобраться как устроены мьютексы в различных фреймворках. А началось всё с того, что произошёл у меня спор с коллегой по поводу того, как отработает код в Yii2:]]></description><content:encoded><![CDATA[
  <p id="T2iB">Решил я разобраться как устроены мьютексы в различных фреймворках. А началось всё с того, что произошёл у меня спор с коллегой по поводу того, как отработает код в Yii2:</p>
  <pre id="Lxn8">$mutex-&gt;acquire(&#x27;my-lock&#x27;, 10);
</pre>
  <p id="pFXy">Поскольку в нашем проекте мьютексы хранятся в Redis, я был уверен, что число <code>10</code> в этом контексте обозначает TTL (Time To Live) блокировки, то есть время, через которое блокировка будет автоматически снята. Это предположение основывалось на том, что в команде Redis для установки блокировки используется следующий синтаксис:</p>
  <pre id="a2bl">SET lock_name unique_value NX PX 5000
</pre>
  <p id="HUgH">Здесь создаётся блокировка с именем <code>lock_name</code>, где &quot;владельцем&quot; является <code>unique_value</code> (снять блокировку можно только, указав это значение) на 5000 миллисекунд, если такого ключа у &quot;владельца&quot; нет.</p>
  <p id="NqXx">Кроме того, мой опыт работы с Symfony подсказывал, что при создании мьютекса можно передать только TTL. Однако, как оказалось, в случае с Yii2 я ошибался. Изучив код, я понял, что параметр <code>10</code> в методе <code>acquire</code> обозначает не TTL, а таймаут — время, в течение которого процесс будет ожидать освобождения мьютекса.</p>
  <p id="N7xv"><strong>Как устроены мьютексы в PHP-фреймворках?</strong></p>
  <p id="CJFu">Рассмотрим интерфейсы и их реализацию в различных фреймворках, поскольку на глубоком уровне они работают схожим образом, особенно при использовании Redis.</p>
  <h3 id="Yii2">Yii2</h3>
  <pre id="Tj0v">if (!$mutex-&gt;acquire($mutexName)) {
    return;
}
try {
    // критическая секция
} finally {
    $mutex-&gt;release();
}
</pre>
  <p id="B4bK">При таком использовании метод <code>acquire</code> сразу вернёт <code>false</code>, если блокировка не может быть установлена, и ожидание не произойдёт. Если вызвать <code>$mutex-&gt;acquire($mutexName, 10)</code>, процесс будет ожидать до 10 секунд, пока мьютекс не освободится. Однако TTL также можно задать.</p>
  <h3 id="Yii3">Yii3</h3>
  <p id="mQzD">В Yii3 добавлена обёртка <code>\Yiisoft\Mutex\Synchronizer</code>:</p>
  <pre id="nf5H">$synchronizer = new Synchronizer($mutexFactory);

$result = $synchronizer-&gt;execute(&#x27;my-lock&#x27;, function () {
    // критическая секция
}, 10); // таймаут 10 секунд
</pre>
  <p id="J9Rs">Здесь освобождение мьютекса происходит внутри <code>Synchronizer</code>, что удобнее и безопаснее. Это избавляет от необходимости писать собственные обёртки. Для тех, кто предпочитает полный контроль, мьютексы можно использовать так же, как в Yii2.</p>
  <h3 id="Laravel">Laravel</h3>
  <p id="QD1E">В Laravel мьютексы являются частью пакета кеширования:</p>
  <pre id="kkFc">Cache::lock(&#x27;my-critical-section&#x27;, 10)-&gt;block(5, function () {
    // критическая секция
});
</pre>
  <p id="Bf7c">Здесь создаётся мьютекс с TTL 10 секунд и таймаутом 5 секунд. Освобождение происходит автоматически. Можно также выполнить всё вручную:</p>
  <pre id="m17u">$lock = Cache::lock(&#x27;my-task&#x27;, 10);

if ($lock-&gt;get()) {
    try {
        // критическая секция
    } finally {
        $lock-&gt;release();
    }
}
</pre>
  <p id="EZ8t">Мьютексные блокировки часто применяются для предотвращения параллельного запуска фоновых задач. В Laravel существует middleware <code>WithoutOverlapping</code> в пакете очередей, который использует тот же мьютекс, предоставляя удобную обёртку для типовых задач.</p>
  <h3 id="Symfony">Symfony</h3>
  <p id="jkPp">Symfony предоставляет, пожалуй, самый гибкий и полный пакет для работы с мьютексами:</p>
  <pre id="byfb">$lock = $factory-&gt;createLock(&#x27;pdf-creation&#x27;, ttl: 30);
if (!$lock-&gt;acquire()) {
    return;
}
try {
    // выполнение задачи менее 30 секунд
} finally {
    $lock-&gt;release();
}
</pre>
  <p id="m31U">Здесь блокировка создаётся в неблокирующем режиме. Можно создать и в блокирующем, используя <code>acquire(true)</code>. Также можно обновлять TTL мьютекса через <code>$lock-&gt;refresh()</code>, если становится понятно, что первоначального TTL недостаточно.</p>
  <p id="6foT">Особое внимание в этом пакете привлекают блокировки с разделением доступа. Поддержка этой функции зависит от используемого адаптера; для Redis она доступна. В то время как <code>acquire</code> захватывает исключительную блокировку, после которой никакой другой процесс не сможет ни читать, ни писать в защищённый ресурс, <code>acquireRead</code> (разделяемая блокировка) позволяет нескольким процессам захватывать блокировку в режиме чтения, пока кто-то не выполнит исключительную блокировку.</p>
  <p id="W23W"><strong>Заключение</strong></p>
  <p id="BVdy">Подобные библиотеки делают использование мьютексов простым и удобным, скрывая тонкости реализации. Например, последовательное выполнение команд <code>GET</code> (проверка наличия ключа) и <code>DEL</code> (удаление ключа) для снятия мьютекса не гарантирует атомарность операции, поскольку между выполнением этих команд значение ключа может измениться другим процессом. Поэтому требуется выполнение Lua-скрипта:</p>
  <pre id="vFl0">if redis.call(&quot;GET&quot;, KEYS[1]) == ARGV[1] then
    return redis.call(&quot;DEL&quot;, KEYS[1])
else
    return 0
end
</pre>
  <p id="D4GH">Этот скрипт обеспечивает атомарность операции удаления мьютекса, проверяя значение ключа перед удалением.</p>
  <p id="ecaQ"></p>
  <p id="4Q6N">Подписывайся на <a href="http://t.me/ask_for_oleg" target="_blank">канал 🤘</a></p>

]]></content:encoded></item></channel></rss>