<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Redis</title>
    <description/>
    <link>https://tproger.ru/tag/redis</link>
    <atom:link href="https://tproger.ru/tag/redis/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sat, 03 Oct 2026 17:07:19 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Redis</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Пагинация и лимиты API: как отдавать данные постранично и не дать выгрести базу</title>
      <link>https://tproger.ru/articles/paginaciya-i-limity-api-kak-otdavat-dannye-postranichno-i-ne-dat</link>
      <comments>https://tproger.ru/articles/paginaciya-i-limity-api-kak-otdavat-dannye-postranichno-i-ne-dat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/paginaciya-i-limity-api-kak-otdavat-dannye-postranichno-i-ne-dat</guid>
      <description><![CDATA[<p>Пагинация через OFFSET тормозит на глубоких страницах и теряет строки. Смотрите, как перейти на курсор и настроить лимиты запросов — с кодом и замерами.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/paginaciya-i-limity-api-kak-otdavat-dannye-postranichno-i-ne-dat">Пагинация и лимиты API: как отдавать данные постранично и не дать выгрести базу</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Высокие нагрузки]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 07 Sep 2026 05:00:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Ваш API отдаёт страницы по двадцать записей и работает прекрасно ровно до того дня, когда у первого крупного клиента накопится полмиллиона строк. Тогда выяснится сразу две вещи: страницы стали открываться секундами, а синхронизация на стороне клиента дважды затянула одни и те же заказы.</p><p>Оба симптома дают один и тот же виновник — постраничная выдача через OFFSET. И оба чинятся одним переходом, причём переписывать приходится только слой выдачи.</p><p>Пагинация — это способ отдать большую коллекцию по частям. Вариантов ровно два. Offset-пагинация просит базу пропустить N строк и вернуть следующие двадцать. Курсорная (её же называют keyset) запоминает последнюю отданную запись и просит всё, что идёт после неё.</p><p>Стоимость OFFSET растёт линейно: на таблице в 1,2 млн строк выборка со смещением в миллион занимает 2,4 с против 3 мс у курсора.</p><p>Под параллельными вставками и удалениями OFFSET выдаёт дубликаты и пропуски, потому что нумерация строк сдвигается между запросами.</p><p>Курсор не поддерживает переход на произвольную страницу, поэтому для админок и таблиц с кнопкой «страница 47» offset остаётся правильным выбором.</p><p>Лимиты запросов на счётчике в памяти инстанса не работают: при десяти инстансах настроенные 100 запросов в минуту превращаются в 1000.</p><p>Скользящее окно на сортированном множестве Redis и атомарном Lua-скрипте занимает пятнадцать строк и снимает гонку без распределённых блокировок.</p><p>Результат на спокойной базе одинаковый. Поведение под ростом данных и параллельными записями — принципиально разное.</p><h2>Offset ломается двумя разными способами</h2><p>Про первый знают почти все, про второй вспоминают уже после инцидента.</p><h3>Он линейно замедляется</h3><p>PostgreSQL, MySQL и SQLite не умеют «перепрыгнуть» через строки. При OFFSET 100000 база честно читает сто тысяч строк и выбрасывает их, чтобы отдать следующие двадцать. Стоимость запроса растёт вместе со смещением.</p><p>Автор разбора <a href="https://dev.to/sirmax/why-i-switched-my-api-from-offset-to-cursor-pagination-and-when-you-shouldnt-1464">замерил это на таблице в 1,2 млн строк</a> в PostgreSQL 15 с холодным кешем и обычным btree-индексом по id:</p><p>Курсорный вариант на той же таблице держал 3 мс на любой глубине. Разница объясняется сложностью: поиск по индексу это O(log n), а большое смещение — O(offset). На пятидесятитысячной странице пользователь ждёт больше двух секунд вместо миллисекунд.</p><h3>Он врёт, когда в таблицу пишут</h3><p>Этот сценарий коварнее. Пользователь загрузил первую страницу с двадцатью записями, отсортированными от новых к старым. Пока он читал, в таблицу добавились три новые записи. Запрос второй страницы с OFFSET 20 вернёт часть того, что уже было на первой: всё сдвинулось на три позиции.</p><p>Клиент получает дубликаты. При удалении записей картина обратная — часть строк он не увидит никогда, потому что они проскочили мимо границы страницы. У автора статьи на этом сломалась синхронизация у клиента: задание дважды загрузило одни и те же двадцать заказов, и три письма в поддержку ушло на то, чтобы понять, что виновата пагинация, а не код клиента.</p><p>Курсор от этого защищён по построению. Курсор — это последняя фактически полученная запись, поэтому «всё, что после неё» остаётся верным независимо от того, сколько строк вставили или удалили между запросами.</p><h2>Как выглядит курсор в коде</h2><p>Простейшая версия работает по первичному ключу и умещается в один обработчик:</p><p>Обратите внимание на per_page + 1. Запрашивая на одну запись больше, чем собираетесь отдать, вы узнаёте о существовании следующей страницы без отдельного запроса COUNT. Лишняя запись отбрасывается, а её наличие становится флагом has_more.</p><h3>Составной курсор: место, где ломается наивная версия</h3><p>Курсор по одному id корректен, только пока вы сортируете по id. Как только сортировка идёт по created_at, простое условие created_at &gt; X начинает терять записи: два заказа, созданные в одну миллисекунду, дают ничью, и одна из строк молча исчезает из выдачи.</p><p>Лечится это составным курсором — ключ сортировки плюс первичный ключ, сравниваемые как кортеж:</p><p>Сравнение кортежей разрешает ничьи детерминированно, поэтому ни одна строка не дублируется и не пропадает. Под это нужен составной индекс (created_at DESC, id DESC), иначе выигрыш в скорости пропадёт вместе с планом запроса.</p><p><b>Про формат курсора:</b><br />Не отдавайте наружу голый идентификатор, если планируете менять схему. Заверните значения в маленький JSON с полем версии и закодируйте в base64: тогда через год вы поменяете состав курсора, не сломав клиентов, которые прямо сейчас держат страницу открытой.</p><h2>Когда офсетную пагинацию нужно оставить</h2><p>Курсор не бесплатный, и подменять им всё подряд не стоит. Есть четыре ситуации, где offset честно лучше:</p><ul><li>Интерфейсу нужен переход на конкретную страницу. У курсора нет номеров страниц, поэтому админки, таблицы и всё с кнопкой «перейти к странице 47» остаются на offset.</li><li>Данных мало и они не меняются. Справочник на пять тысяч строк не выиграет ничего, зато offset проще объяснить и отладить.</li><li>Пагинация идёт по памяти или по кешу, где смещение и так стоит O(1).</li><li>Сортировка задаётся пользователем по произвольной колонке. Курсор тут превращается в кодирование кортежей в непрозрачные строки, и это уже заметная сложность.</li></ul><p>Разумное правило: курсор по умолчанию для всего, что может перерасти десять тысяч строк или читается во время записи. Offset — для небольших, статичных и внутренних данных.</p><p>И отдельно стоит помнить, что пагинация обычно не единственная нагрузка на базу. Кто ещё и насколько сильно её грузит, разбирали в переводе про <a href="https://tproger.ru/translations/patterny-upravleniya-trafikom-k-postgresql-v-production">паттерны управления трафиком к PostgreSQL</a>.</p><h2>Вторая половина задачи: кто и сколько может забрать</h2><p>Правильная пагинация решает, как отдавать данные. Она не решает, сколько их заберут за минуту. Ограничение частоты запросов выглядит тривиально («считай запросы, блокируй сверх N») и превращается в задачу распределённых систем ровно в тот момент, когда серверов становится два.</p><h3>Сначала ответьте, от чего защищаетесь</h3><p>От выбранной цели зависит вся конструкция, и целей <a href="https://dev.to/weston_carnes_d580b505e0c/api-rate-limiting-patterns-algorithms-and-how-to-do-it-right-4l35">обычно выделяют четыре</a>:</p><ul><li>Защита от перегрузки — не дать всплеску положить сервис.</li><li>Честное распределение — не дать одному шумному клиенту вытеснить остальных.</li><li>Защита от злоупотреблений — притупить перебор паролей, скрейпинг и подстановку учётных данных.</li><li>Контроль расходов — ограничить дорогие ручки вроде вызова модели или построения отчёта. Тот же принцип со стороны поставщика разбирали на примере <a href="https://tproger.ru/articles/kak-ogranichit-rashody-na-openai-api-chtoby-ii-agenty-ne-sozhgli">лимитов трат в OpenAI API</a>.</li></ul><h3>Три алгоритма и один, который выбирают почти всегда</h3><p><b>Фиксированное окно</b> считает запросы в календарном интервале: сто в минуту со сбросом на начале минуты. Просто и дыряво: клиент отправляет сотню в 12:00:59 и ещё сотню в 12:01:00, то есть двести за одну секунду. Граница окна и есть дыра.</p><p><b>Скользящее окно</b> считает в непрерывно сдвигающемся интервале, часто взвешивая счётчик предыдущего окна по мере хода времени. Точнее, но требует больше состояния.</p><p><b>Ведро токенов</b> обычно и оказывается верным выбором по умолчанию. В ведре лежит до N токенов, оно пополняется с постоянной скоростью, каждый запрос тратит один токен. Такая схема разрешает контролируемые всплески (молчавший клиент накопил токены и может потратить их разом), но держит среднюю нагрузку в рамках. Родственное «дырявое ведро» выдаёт идеально ровный поток и нужно там, где принимающая сторона всплесков не переносит.</p><blockquote>Фиксированное окно пишут первым и потом жалеют. На ведро токенов мигрируют.</blockquote><h3>Ловушка, из-за которой лимит не работает вообще</h3><p>Счётчик в памяти процесса живёт до появления второго сервера. После этого «сто запросов в минуту» тихо означает «сто запросов в минуту на инстанс», и при десяти инстансах клиент законно получает тысячу. Ошибку не видно ни в логах, ни в мониторинге: лимит вроде бы работает, просто не тот.</p><p>Лечится это переносом счётчика в общее хранилище, обычно в Redis, причём проверка и инкремент обязаны быть атомарными. Последовательность «прочитать, сравнить, увеличить» из кода приложения гоняется между инстансами и недосчитывает запросы ровно под той нагрузкой, ради которой лимит и ставили.</p><h2>Скользящее окно на Redis: пятнадцать строк Lua</h2><p>Команда сервиса jo4.io столкнулась с частным случаем этой задачи: ручка подсказки категорий ходит в модель через Groq, каждый вызов стоит денег, а пользователь может дёрнуть её полсотни раз за минуту. Нужны были лимиты на конкретную ручку и на конкретного пользователя, причём в тот же день.</p><p>Скользящее окно они <a href="https://dev.to/anand_rathnas_d5b608cc3de/redis-sliding-window-rate-limiting-with-lua-the-15-line-script-that-saved-our-ai-budget-p0i">собрали на сортированном множестве Redis</a>. Скор записи — временная метка в миллисекундах, а весь алгоритм умещается в один Lua-скрипт, который Redis выполняет атомарно:</p><p>Первая команда чистит записи старше окна относительно текущего момента — именно поэтому окно скользящее, а не фиксированное: границ, к которым можно приурочить всплеск, здесь просто нет. PEXPIRE с запасом в секунду доубивает ключ, если новых запросов не приходит, так что чистить хранилище отдельно не нужно.</p><h3>Две детали, на которых легко ошибиться</h3><p><b>Уникальность члена множества.</b> ZADD не добавляет запись, а перезаписывает существующую, если член совпал. Два запроса в одну миллисекунду с одинаковым значением дадут счётчик, который занижен. Авторы склеивают метку времени с восемью символами UUID: метка обеспечивает уникальность почти всегда, а восемь шестнадцатеричных символов дают около четырёх миллиардов вариантов внутри одной миллисекунды.</p><p><b>Куда падать при отказе Redis.</b> Решение здесь принимается осознанно и по-разному для двух случаев. Если вызов не получил внятного ответа вовремя (таймаут клиента, сетевая икота), запрос пропускают: лимит здесь страхует бюджет, а не безопасность, и блокировать живого пользователя из-за сбоя инфраструктуры хуже, чем оплатить лишний вызов модели. А вот жёсткое исключение (Redis недоступен, соединение отвергнуто) пробрасывают наверх: если хранилище легло целиком, лучше шумно упасть, чем молча жечь бюджет вообще без ограничений.</p><p>Обошлось это в два часа работы вместе с тестами. Одна запись в сортированном множестве занимает около 50 байт, на пользователя их пять, и даже при десяти тысячах активных пользователей всё хранилище лимитов весит порядка 2,5 МБ. Настроенный лимит составил пять запросов за шестьдесят секунд на пользователя. Первым же уловом стал автоматизированный скрипт, бивший в ручку больше двухсот раз в час.</p><h2>Честный контракт с клиентом</h2><p>Ограничение без внятного ответа превращает вежливого клиента в невежливого: не понимая, что происходит, он начинает долбить чаще. Минимальный набор обязательств выглядит так:</p><ul><li>Отдавать 429 Too Many Requests, а не общий 400 или 503. По коду клиент понимает, что делать.</li><li>Присылать заголовок Retry-After, чтобы клиент знал момент следующей попытки, а не подбирал его перебором.</li><li>Показывать остаток бюджета в заголовках вида X-RateLimit-Remaining и X-RateLimit-Reset.</li></ul><p>Последний пункт стоит дешевле всего и работает лучше всего: клиент, который видит свой остаток, обычно распределяет запросы сам и до стены не доходит.</p><h2>Что забрать с собой</h2><p>Обе половины задачи решаются до того, как появится нагрузка, и обе стоят один вечер. Курсорная пагинация убирает и деградацию на глубоких страницах, и молчаливую порчу выдачи при параллельных записях. Атомарный счётчик в общем хранилище превращает лимит из декоративного в настоящий.</p><p>Отложить их дешевле всего прямо сейчас и дороже всего в момент, когда в поддержку придёт клиент с задвоенными заказами: тогда чинить придётся не только API, но и данные на его стороне.</p><p>Разборы, на которых основан материал: <a href="https://dev.to/sirmax/why-i-switched-my-api-from-offset-to-cursor-pagination-and-when-you-shouldnt-1464">переход с offset на курсор с замерами</a>, <a href="https://dev.to/weston_carnes_d580b505e0c/api-rate-limiting-patterns-algorithms-and-how-to-do-it-right-4l35">паттерны и алгоритмы ограничения частоты</a> и <a href="https://dev.to/anand_rathnas_d5b608cc3de/redis-sliding-window-rate-limiting-with-lua-the-15-line-script-that-saved-our-ai-budget-p0i">скользящее окно на Redis и Lua</a>.</p><p>Откройте свой самый нагруженный список и выполните запрос с большим смещением. Если ответ идёт дольше сотни миллисекунд, вы уже нашли, с чего начать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как я написал AI-бота на aiogram 3 с помощью нейросетей, выжил при 2500+ пользователей и почему SQLite "всё ещё торт"</title>
      <link>https://tproger.ru/articles/kak-ya-napisal-ai-bota-na-aiogram-3-s-pomoshhyu-nejrosetej-vyzhil-p</link>
      <comments>https://tproger.ru/articles/kak-ya-napisal-ai-bota-na-aiogram-3-s-pomoshhyu-nejrosetej-vyzhil-p?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Нуя]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-ya-napisal-ai-bota-na-aiogram-3-s-pomoshhyu-nejrosetej-vyzhil-p</guid>
      <description><![CDATA[<p>Технический кейс создания Telegram-бота Щёлк-ГДЗ (ИИ-репетитор по фото). Разбор архитектуры на Python (aiogram 3, aiosqlite), работы с API Gemini и решения проблем под нагрузкой 2500+ пользователей. </p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-ya-napisal-ai-bota-na-aiogram-3-s-pomoshhyu-nejrosetej-vyzhil-p">Как я написал AI-бота на aiogram 3 с помощью нейросетей, выжил при 2500+ пользователей и почему SQLite "всё ещё торт"</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[JSON]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[HR]]></category>
      <category><![CDATA[Telegram]]></category>
      <category><![CDATA[MIT]]></category>
      <category><![CDATA[Flash]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Маркетинг]]></category>
      <category><![CDATA[аналитик]]></category>
      <category><![CDATA[микро]]></category>
      <category><![CDATA[Пет-проект]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[VPS]]></category>
      <category><![CDATA[Hardware]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 26 Jul 2026 15:47:08 GMT</pubDate>
      <content:encoded><![CDATA[<p>На дворе 2026 год. Очередной постмортем микро-SaaS'а в Телеге.</p><p>Без маркетинга и успешного успеха. Только боль, костыли и суровый прод! Мой пет-проект - бот «Щёлк-ГДЗ». Это ИИ-помощник, который решает школьные задачки по фоткам, видео, гс, тг-кружкам и PDF. Под капотом <b>Python 3.12.3</b>, <b>aiogram 3</b>, <b>aiosqlite</b>,<b> апи OpenRouter </b>(модель Gemini 3.0 Flash) и <b>Робокасса</b>. Крутится всё это на дешёвом VPS в Нидерландах. Держит 2500+ пользователей и не падает.</p><p>В этой статье расскажу, как за 9 месяцев построил логичную архитектуру, победил ошибку FloodWait при стриминге ответа ИИ и почему в условиях 1 гига RAM обычная SQLite - отличное решение.</p><h2>Нейронка вместо джуна и Уроборос багов</h2><p>Сразу признаюсь... С нуля я это не писал :) Синтаксис мне генерили LLM-ки. Начинал с Gemini 2.5 Pro, затем перешел на 3.0 Pro, а сейчас использую 3.1 Pro.</p><p>Многие думают, что нейронка сама напишет проект "под ключ", но это миф. Я <b>никогда </b>не доверял ИИ проектирование архитектуры и использовал его как продвинутый<i> StackOverflow</i> (скармливал конкретную задачу (например, написать SQL-миграцию) и получал кусок кода).</p><p><i>ИИ — это не архитектор, а джун на спидах. </i></p><p>Как только логика усложнялась - гемини ловил <b>«Уроборос багов»</b>. Кидаешь баг <b>А</b> - он его фиксит, но появляется ошибка <b>Б</b>. Скармливаешь и её - фиксит, но возвращается баг <b>А</b>. Цикл замкнулся. Лечилось только созданием новых чатов, в которые я кидал код и писал запросы типа "Найди критические ошибки, логические дыры и баги".</p><p>Про продуктовую логику нейронка вообще не слышала. В первой версии рефералки был баг, где любой(если его аккаунта ещё нет в БД) мог написать в конце ссылки что любые 9 цифр (<i>?start=1234567890</i>) и получить бонусы.</p><p>Также были ошибки с гонкой состояний при оплатах и активациях промокодов, которые тоже фиксил запросами в новые чаты с ИИ. Так я исправил около 20 архитектурных дыр.</p><h2>Немного про архитектуру.</h2><p>Чтобы код не превратился в нечитаемую лапшу на 5000 строк, я жестко разбил всё на модули. Архитектура бота выглядит так:</p><figure><img src="https://media.tproger.ru/user-uploads/139416/2026-07-08/dfc8dd70-365f-4f70-bbbf-2b00b503bef4.webp" alt="" /></figure><p><b>main.py</b> - это точка входа с настройкой логгеров, коннетами aiohttp и запуском APScheduler</p><p><b>database.py</b> - вся работа с БД</p><p><b>neiro.py</b> - логика общения с апи OpenRouter (стриминг, сжатие фоток через Pillow, JSON-контекст)</p><p><b>states.py</b> - классы состояний aiogram.fsm.state (например, PaymentProcess, GdzMode)</p><p><b>utils.py</b> - легковесные утилиты. Например лок пользователей (защита от спама запросами):</p><p>Хэндлеры вынесены в отдельную папку handlers/:</p><p><b>handlers/common_handlers.py</b> - главное меню с обработкой /start (рефералки, utm-метки).</p><p><b>handlers/gdz_handlers.py</b> - сам процесс ИИ-решения (вход в GdzMode, прием фото, видео, кружочков).</p><p><b>handlers/pay_handlers.py</b> - это логика платежей (Robokassa) и "Умная корзина".</p><p><b>handlers/tasks_handlers.py</b> - квесты (выдача премиума за подписку на каналы спонсоров).</p><p>Сборку интерфейсов вынес в <b>all_def.py</b>. Не люблю, когда в хэндлерах генерится полотно текста с кнопками. Там же лежат функции склонения слов (1 запрос, 2 запроса, 5 запросов). А в <b>settings.py </b>лежат списки с рандомными ответами бота, чтоб казался живым.</p><p>Так же в <b>settings.py</b> я сделал кэширование картинок, тоесть при первом запуске бот грузит фото меню как <b>BufferedInputFile</b>, сохраняет <b>file_id</b> от Телеграма и дальше шлет картинки моментально по ID. Сервак говорит спасибо за сэкономленный трафик)</p><p>В <b>handlers/common_handlers.py</b> находится первичная маршрутизация. Вот так обрабатываются рефералки, переходы с сайта, с рекламы и другое при старте:</p><h2>Диета по токенам</h2><p>Хранить бесконечную историю диалогов дорого и бессмысленно. В бесплатной версии храню <b>10 последних сообщений</b> (5 пар вопрос-ответ), а в преме — <b>30. </b></p><p>Но фотки весят большое кол-во токенов. Если премиум-юзер закинет 30 фоток, OpenRouter выставит мне огромный счет. В итоге я прикрутил ограничение: Из <b>30 сообщений</b> ИИ видит только <b>10 последних картинок. </b></p><p>Старые фотки тупо вырезаю из JSON. Подменяю на системный промпт:</p><p>С довольно неплохой моделью (gemini 3.0 flash) это работает как часы (она честно признается, что забыла картинку, а не выдумывает что-то из воздуха).</p><h2>Стриминг, FloodWait и защита баланса</h2><p>Чтобы бот не выглядел тормозом, я сделал стриминг ответа от ИИ в <b>neiro.py</b>. Я обновляю сообщение в Телеграме чанками по мере получения их от ОпенРоутера.</p><p>Но если делать <b>message.edit_text </b>слишком часто, ловишь <b>FloodWait</b>. В итоге я выставил интервал в 0.7 секунд и обернул всё в жесткий<b> try/except</b>:</p><p>Стрим при этом не прерывается. Поспали и погнали дальше :) Если на этапе обработки файла или стриминга падает критическая ошибка - честно возвращаю юзеру запрос на баланс.</p><p>В <b>handlers/gdz_handlers.py</b> это выглядит так:</p><h2>SQLite тащит</h2><p>Почему не <b>Postgre</b>? Потому что для микро-SaaS с 2,5к пользователей<b> SQLite</b> хватает за глаза. Но в асинхронной среде она любит кидать ошибку <b>database is locked</b>.</p><p>Чтобы этого избежать, я включил <b>WAL-режим</b> при инициализации пула, разделил коннекты на <b>db_writer</b> и <b>db_reader</b>, а сложные операции доверил самому <b>SQL</b>.</p><p>Например, 00:00 запускается крон-таска, которая собирает огромную аналитику, начисляет всем активным юзерам +1 ежедневный запрос и сбрасывает просроченные подписки. И это всё это работает атомарно внутри <b>database.py</b>:</p><h2>Умная корзина на APScheduler</h2><p>Когда дело дошло до монетизации, всплыли две проблемы:</p><p>Первая: юзер оплатил, но забыл нажать кнопку <b>«✅ Я оплатил»</b> в боте. Бот ждет, юзер ждет, товар не выдается, поддержка кипит.</p><p>Вторая: юзер сформировал счёт и передумал ("брошенная корзина").</p><p>Вместе с LLM я с нуля изучил <b>apscheduler </b>и убил двух зайцев фоновыми задачами. Теперь в <b>handlers/pay_handlers.py </b>при генерации ссылки на оплату я создаю две отложенные таски:</p><h4>Как это работает?</h4><p>Через 5 минут срабатывает <b>async def auto_check_payment()</b>, которая тихо стучится в робокассу. Если статус<b> success</b> - бот сам начисляет запросы на баланс и радует клиента. Если статус <b>pending</b> - таска умирает, и в дело вступает 30-минутная таска.</p><p>Но она не шлёт спам вслепую, а лезит в БД и проверяет 3 бизнес-правила:</p><p>1. <b>await db.has_successful_payment_recently</b> - Не купил ли он другой товар за последние 3 часа?</p><p>2. <b>await db.get_latest_invoice_id</b> - А это точно самый последний сгенерированный им счет?</p><p>3. <b>await db.can_send_agitation</b> - Не присылали ли мы ему агитацию недавно?</p><p>Если проверки пройдены, то юзер получает сообщение: <i>"⏳ Домашка сама себя не решит! Ты начал оформлять покупку, но оплата так и не прошла..."</i>. Это поднимает конверсию оплат.</p><h2>Финал</h2><p>Почему я не использую <b>Redis </b>для стейтов? Ответ банален: мой дешевый VPS имеет всего 1 гб оперативки. Пул <b>aiohttp</b>, In-Memory стейты и асинхронные таски и так жрут 70% RAM. Редис тупо не влезет.</p><p>Как говорится, <i>работает — не трогай.</i></p><p>Сейчас проект обзавелся сайтом-витриной и продолжает развиваться. В планах - искать и чинить новые баги. Перееду на более мощное железо, когда сервак начнет физически задыхаться.</p><p>Готов ответить на вопросы по архитектуре и послушать советы в комментариях \(^^)/</p><p><i>P.s. вот ссылка на первую статью о моём проекте: </i></p><p>P.s. Если кто-то хочет потестить вживую(не реклама), то юз бота в тг <a>@gdzshchelk_bot</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Своя self-hosted двухсерверная VPN-архитектура. Подробный путь разработки и ошибки, с которыми вы можете столкнуться</title>
      <link>https://tproger.ru/articles/svoya-self-hosted-dvuhservernaya-vpn-arhitektura-podrobnyj-put-r</link>
      <comments>https://tproger.ru/articles/svoya-self-hosted-dvuhservernaya-vpn-arhitektura-podrobnyj-put-r?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Роман Фролов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/svoya-self-hosted-dvuhservernaya-vpn-arhitektura-podrobnyj-put-r</guid>
      <description><![CDATA[<p>Проект представляет из себя быстрый способ воссоздать архитектуру состоящую из 2 серверов (локальный + удаленный) с определенными сервисами, которые решают специфические задачи. Проект сделан прежде всего для меня, а также для людей которые хотят свой готовый self-hosted сервер из коробки с полной системой обслуживания</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/svoya-self-hosted-dvuhservernaya-vpn-arhitektura-podrobnyj-put-r">Своя self-hosted двухсерверная VPN-архитектура. Подробный путь разработки и ошибки, с которыми вы можете столкнуться</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Ruby]]></category>
      <category><![CDATA[unix]]></category>
      <category><![CDATA[IDE]]></category>
      <category><![CDATA[Музыка]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[DIY]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Навыки]]></category>
      <category><![CDATA[VPN]]></category>
      <category><![CDATA[Telegram]]></category>
      <category><![CDATA[Сервисы]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[Cloudflare]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Мессенджер]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[NFT]]></category>
      <category><![CDATA[Машин]]></category>
      <category><![CDATA[1C]]></category>
      <category><![CDATA[DeFi]]></category>
      <category><![CDATA[Grafana]]></category>
      <category><![CDATA[VPS]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 26 Jul 2026 15:46:26 GMT</pubDate>
      <content:encoded><![CDATA[<p>Всё
 началось с того, что меня перестал устраивать мой подход к 
развертыванию личных сервисов. Я решил переписать всё с нуля. К тому же 
это был отличный повод получить новые навыки и попробовать инструменты, 
до которых давно не доходили руки.</p><p>Арендовать мощные VPS под 
ресурсоемкие задачи в текущих реалиях выходит неоправданно дорого. При 
этом дома у меня уже был выделенный неттоп (мини-ПК) с 16 ГБ оперативной
 памяти на базе энергоэффективного процессора Intel N95.</p><p>Пройдя 
путь от простых Bash-скриптов и сторонних туннелей до полностью 
автоматизированной инфраструктуры, я создал проект ServeHub-2. В этой 
статье я подробно разберу, как эволюционировала сеть проекта, почему 
декларативный подход победил императивный и с какими неочевидными багами
 пришлось столкнуться в процессе автоматизации.</p><h2>История настройки сетевой архитектуры</h2><h3>Использование Tuna</h3><p>В
 самом начале проекта я еще не думал о VPN как о способе пробития NAT и 
основе, на которой будет строиться вся система. Первое, к чему я пришел —
 сервис Tuna. Если кратко, это аналог Cloudflare Tunnel за условные 300 
рублей. У него очень приятный веб-интерфейс и невероятно простая 
настройка. Однако для полноценной независимой архитектуры он не подошел 
по двум причинам:</p><ul><li>Сервера Tuna находятся вне контроля пользователя.</li><li>На удаленном сервере нельзя развернуть свои сопутствующие сервисы.</li></ul><p>В целом сервис действительно удобный, но для моих задач он оказался слишком ограничивающим.</p><p>Вот
 пример конфига для tuna (все максимально просто, создаем контейнеры для
 ssh туннеля, чтобы был доступ ssh, а также основной http туннель с 
привязкой к nginx или любому другому прокси если такой есть):</p><h3>В поисках гибкости: тесты Pangolin и переход к FRP</h3><p>После Tuna я решил двигаться в сторону собственного контроля и попробовал Pangolin.
 Однако решение быстро отвалилось: для дешёвого сервера оно оказалось 
слишком «тяжёлым» и избыточным. Главные минусы — ощутимый оверхед по 
ресурсам и лишний слой принудительной веб-аутентификации перед самими 
сервисами. Это ломало нормальное взаимодействие с родными мобильными 
клиентами (вроде Element для Matrix или Bitwarden для паролей), где 
повторная авторизация в браузере просто не нужна.</p><p>На смену пришел FRP (Fast Reverse Proxy).
 Он выполнял ту же функцию проброса, но хостил я его уже на собственном 
арендованном VPS в режиме Layer 4 (TCP). Это дало абсолютную гибкость: 
внешний VPS не заглядывал внутрь пакетов и ничего не расшифровывал, а 
просто пересылал сырой поток домой. Кроме того, это отлично 
оптимизировало расходы: вместо 300 рублей за Tuna и ещё 300 рублей за 
отдельный VPN, я стал платить всего 500 рублей за один стойкий VPS, 
который мог настраивать как хочу. (к тому же можно было обойтись даже 
дешевле, так как VPS с 4 гб оперативки загружен всего лишь на 40%, 
процессор всего на 20%-40%)</p><p>FRP состоит из 2 конфигурационных 
файлов (один на удаленном сервере frp server, другой на локальном frp 
client), все также довольно просто, однако его можно использовать на 
разных слоях. У меня он брал трафик за стандартный TCP и передавал его 
на локальный сервер.</p><p>Также доп. фишка в том что основная 
конфигурация происходит именно в frpc, который, в свою очередь, передает
 часть настроек на frp на удаленном сервере.</p><p>frpc.toml:</p><p>frps.toml:</p><h3>Полноценный переезд на WireGuard (AmneziaWG) и 8 часов отладки</h3><p>Со временем архитектура эволюционировала в сторону полноценной VPN-сети на базе WireGuard, а точнее — его модификации AmneziaWG. Причин для этого шага было несколько:</p><ul><li>Безопасность: FRP всё же открывал внутренние ресурсы в публичный интернет, оставляя их доступными для сканеров портов.</li><li>Удобство маршрутизации:
 Все участники сети стали равноправными узлами в одной виртуальной 
локальной подсети. Больше не нужно было настраивать постоянные 
односторонние пробросы.</li><li>Дополнительный бонус:
 Поскольку удаленный VPS был куплен в Нидерландах, через этот же VPN я 
автоматически получил безопасный доступ ко всем зарубежным ресурсам.</li></ul><p>Для реализации схемы на удаленном VPS был развернут контейнер wg-easy с поддержкой AmneziaWG, а на домашнем мини-ПК — клиентский контейнер Amnezia (сборка из Dockerfile с использованием amnezia-tools  и модулем ядра хоста).</p><p>И
 именно здесь я поймал самый изнурительный баг проекта. После 
развертывания трафик упорно шёл только в одну сторону. Часов 8 ушло на 
диагностику iptables, маршрутов и чтение зарубежных форумов (что 
бесполезно, учитывая специфику наших блокировок). Оказалось, провайдер 
просто дропал обратный трафик стандартного WireGuard, так как пакеты шли
 без маскировки. Причина крылась в docker-compose.yml: у меня было прописано image: ghcr.io/wg-easy/wg-easy:latest. Как выяснилось, тег latest  на Docker Hub намертво прилип к старой 14-й версии, а поддержка параметров AmneziaWG появилась только в ветке 15.x. Изменение тега на конкретную версию (15.3) решило проблему за секунду.</p><p>Благодаря
 переходу конфигурация получилась максимально простой, основные отличия 
от стандартной документации выделил в коде: (в основном то, на что 
пришлось долго рыть информацию)</p><h3>Настройка Nginx</h3><p>Чтобы
 сервисы были доступны исключительно внутри подсети VPN, я задействовал 
Nginx. Доступ к приложениям был жестко ограничен на уровне конфигурации —
 веб-сервер принимает запросы только из диапазона IP-адресов 10.8.0.0/24. Любые попытки постучаться на сервер из внешнего интернета без активного VPN-туннеля автоматически сбрасываются Nginx.</p><p>Логика
 распределения завязана на proxy-протоколе: Nginx на удаленном VPS 
выступает основным входным узлом, принимает зашифрованный трафик, 
заворачивает его в заголовки с реальным IP-адресом клиента и через 
туннель перекидывает на локальный Nginx домашнего сервера. Локальный 
веб-сервер уже сам расшифровывает SSL и распределяет трафик по конечным 
Docker-контейнерам, сохраняя реальные IP в логах безопасности.</p><p>Вставлю
 один кусок кода из nginx на удаленной машине для примера, так все 
остальное примерно похоже (конфиги использовались в виде .template):</p><h3>SSL-сертификаты</h3><p>Для получения валидных SSL-сертификатов я настроил работу через автоматический Certbot по challenge-валидации DNS-01 c API Webnames. Сам домен привязан к внутреннему IP-адресу 10.8.0.1.
 Проверка через DNS позволила выпустить единый wildcard-сертификат на 
весь домен и его поддомены без необходимости держать открытым 80-й порт 
веб-сервера наружу.</p><p>Уточню, что certbot запускается автоматически 
во время выполнения Ansible плейбука, поэтому самому кроме указания 
переменных ничего делать не нужно.</p><p>В
 итоге получилась схема, при которой все сервисы доступны по красивым 
доменным именам с HTTPS, но абсолютно невидимы для внешнего интернета.</p><p>Вот настройка certbot:</p><h2>История софта</h2><p>Параллельно
 с сетевой структурой развивался и сам набор приложений. Изначально я 
хотел собрать в одном месте утилиты, которыми пользуюсь каждый день, но в
 процессе селфхостинга быстро понимаешь: нельзя просто накидать 
контейнеров и надеяться, что мини-ПК справится, а конфигурационные файлы
 не превратятся в кашу.</p><h3>Первый стек и оптимизация</h3><p>Первыми на домашнем сервере прижились медиа-сервисы: Navidrome для стриминга музыки и Audiobookshelf
 для аудиокниг и подкастов. Они легковесные, имеют отличные мобильные 
клиенты с синхронизацией прогресса и полностью закрывают мои 
потребности. Позже к ним добавился Nextcloud как единое независимое облако для файлов, контактов и семейных документов.</p><p>Затем встал вопрос безопасного хранения паролей. Сначала я смотрел в сторону оригинального Bitwarden, но в итоге я выбрал Vaultwarden
 — альтернативный сервер на Rust, полностью совместимый с API Bitwarden.
 Он потребляет считанные мегабайты оперативной памяти и работает 
идеально. Дополнительно для удобства управления всей этой распределенной
 Docker-инфраструктурой в локальный стек был добавлен Portainer. (+ Portainer Agent на удаленный сервер)</p><p>Из интересных моментов, где мне пришлось немного больше возиться, чем с остальными сервисами это nextcloud настройка:</p><h3>Ошибки проектирования: почему я удалил Matrix (Synapse)</h3><p>Не все решения прошли проверку временем. На этапе использования Tuna и FRP я развернул сервер Matrix (Synapse)
 для защищенного обмена сообщениями. Мне казалось это крутой идеей, но 
когда я окончательно перешел на AmneziaWG, целесообразность мессенджера 
внутри закрытого туннеля сошла на нет.</p><p>Synapse требовал слишком 
много ресурсов, впустую расходовал оперативку домашнего ПК и усложнял 
конфиг Nginx. При этом реальной пользы для семьи он не приносил. Для 
критических алертов инфраструктуры и повседневного общения проще и 
эффективнее оказалось использовать Telegram. (плюс алертинг настроен 
именно через него) В итоге я полностью выпилил Synapse из стека, 
освободив ресурсы.</p><p>Вместо него я добавил в связку к wg-easy локальный AdGuard Home.
 Теперь он работает прямо внутри VPN-сети: очищает весь трафик от 
рекламы и трекеров на лету, кэширует DNS-запросы и не дает истории 
веб-серфинга улетать внешним провайдерам.</p><p>Основной проблемой с 
которой я столкнулся при использовании AdGuard Home, так это то что я 
так и не понял как заставить использовать AmneziaVPN клиент AdGuard как 
основной DNS, при этом AmneziaWG работает прекрасно. Как я понимаю дело в
 том что AmneziaWG работает намного проще на уровне ip и у него нету 
никаких доп фильтров, настроек и подобного, поэтому он просто берет 
данные из конфига.</p><h3>От костылей на Bash к декларативному Ansible</h3><p>Весь
 стек на обоих серверах разворачивался через bash скрипты, что было уж 
очень плохо с точки зрения идемпотентности. Во первых из-за bash мне 
постоянно приходилось очищать сервера, так как нормальных проверок у 
меня не было и писать я их не хотел, а также было много костылей с 
импортом переменных, записями в файлы и подобным.</p><p>Так я пришел к декларативному подходу и Ansible.
 Теперь вся конфигурация описывается в виде плейбуков и ролей, 
отражающих конечное желаемое состояние серверов. Конфиденциальные данные
 перенесены в файл secrets.yml, а хрупкие конструкции 
автоматизированы через шаблоны Jinja2. Проект стал идемпотентным: если 
шаги уже выполнены, Ansible их просто пропускает.</p><p>В итоге 
получилось несколько yaml файлов для стандартной настройки системы 
(bootstrap_os.yml) и для настройки каждого из хостов (setup_local.yml и 
setup_remote.yml). В итоге теперь все что нужно чтобы полностью с нуля 
развернуть проект - скачать Ansible, несколько других зависимостей на 
свой рабочий пк и запустить один manage_deploy.sh, в котором можно будет
 выбрать сценарий как будет вести себя Ansible и спокойно дождаться 
разворачивания сервисов.</p><p>Также благодаря Ansible я удобно 
реализовал переносимость проекта. Так как я решил не использовать тома 
docker, и вместо этого храню все в папках, чтобы перенести старые данные
 проекта нужно просто скопировать папку apps-data и положить ее в нужное
 место и все само заработает после повторного развертывания проекта. В 
Ansible выделяется отдельная пауза для этого.</p><p>Вот пример основного плейбука deploy.yml:</p><h3>Тестирование мультидистрибутивности с помощью Vagrant</h3><p>Проект
 изначально затачивался под работу на трех дистрибутивах: Ubuntu, Debian
 и Arch Linux. Тестировать Ansible-плейбуки прямо на рабочей локальной 
машине (в моем случае — EndeavourOS) слишком рискованно, а создавать 
виртуальные машины руками — долго и неудобно.</p><p>Решением стал Vagrant,
 позволяющий за пару минут развернуть чистые ОС в VirtualBox из готовых 
образов. Но в процессе настройки мультивендорного стенда в режиме 
сетевого моста (public_network) всплыли две критические проблемы:</p><ol><li>Конфликт DNS:
 По умолчанию Vagrant создает NAT-интерфейс для управления нодой. При 
включении второго (публичного) интерфейса для локальной сети ломался 
дефолтный DNS-резолвер. Проблему пришлось решать принудительной очисткой
 и перезаписью файла /etc/resolv.conf через inline-скрипт автоматизации Vagrant.</li><li>Проблема с GRUB на Debian: В используемом базовом образе generic/debian12
 конфигурация GRUB сохраняла жесткую привязку к конкретному имени диска 
из окружения сборщика. При повторном развертывании плейбуков на тестовом
 стенде это приводило к сбоям загрузчика. Чтобы автоматизировать 
очистку, пришлось внедрить скрипт, который на лету определяет имя 
системного диска через lsblk и автоматически передает правильные параметры в загрузчик через утилиту debconf-set-selections.</li></ol><p>Вот код Vagrantfile: (в node.vm.provision происходит основное решение ошибок)</p><p>Надежная система бэкапов на базе BorgmaticДля создания резервных копий я внедрил Borgmatic
 (удобную надстройку над дедуплицирующим инструментом Borg Backup). Весь
 процесс автоматизирован с помощью связки системных юнитов borgmatic.service и borgmatic.timer. В бэкап уходят две ключевые директории: apps-data (конфигурации приложений и баз данных) и PersonalData (медиатека: музыка, книги, подкасты, файлы Nextcloud).</p><p>Развертывание
 системы бэкапов полностью берет на себя Ansible. Мне достаточно указать
 UUID внешнего жесткого диска — скрипт сам проверит его наличие в 
системе, примонтирует в нужную директорию, создаст зашифрованный 
репозиторий и настроит политику ротации (хранение 7 ежедневных, 4 
еженедельных и 6 ежемесячных копий). Также в Prometheus выведен 
мониторинг самого репозитория .borg для отслеживания его размера и статуса успешности архивации.</p><p>Главная
 проблема при бэкапе работающих Docker-контейнеров — риск скопировать 
базу данных в «битом» или неконсистентном состоянии, если в момент 
создания архива в нее шла активная запись. Чтобы решить эту проблему, я 
задействовал механизм хуков в конфигурации Borgmatic.</p><p>Перед началом резервного копирования автоматически срабатывает команда остановки контейнеров проекта (docker-compose down),
 а после успешного завершения (или в случае возникновения непредвиденной
 ошибки) контейнеры автоматически поднимаются обратно в фоновом режиме. 
Для удобного просмотра архивов и быстрого восстановления файлов я 
развернул веб-интерфейс Borg UI.</p><p>Конфигурация borgmatic: (использую как Jinja2 шаблон, чтобы Ansible в плейбуках сам подставил переменные)</p><h2>Наблюдаемость (Observability) уровня Enterprise</h2><p>В последних релизах (v1.2.0 и v1.3.0) фокус проекта сместился на мониторинг и работу с логами.</p><h3>Эволюция алертинга и переход на Gatus</h3><p>Изначально для мониторинга доступности я смотрел на Uptime Kuma, но отказался из-за отсутствия удобной декларативной настройки через конфиги. Затем я развернул связку Blackbox Exporter и Alertmanager
 с уведомлениями в Matrix. Но тут крылась логическая несостыковка: весь 
алертинг был завязан на локальном ПК, и в случае его аппаратного отказа я
 бы просто лишился уведомлений.</p><p>Тогда я решил вернуть Uptime Kuma,
 но развернуть его на удаленном VPS в качестве внешнего «сторожа» и 
автоматизировать его настройку через Python-скрипт с библиотекой uptime-kuma-api. Но и тут ждали «грабли» — библиотека не обновлялась три года и намертво ломалась на свежих версиях Kuma.</p><p>В итоге идеальным решением стал Gatus.
 Он изначально проектировался под управление через YAML-конфиги и 
поддерживает отправку алертов, если сервер не отвечает. Из-за специфики 
фронтенда Gatus (он не умеет работать из подкаталога типа /gatus), мне пришлось перенести его и wg-easy на полноценные субдомены gatus. и wireguard.</p><p>Для Gatus получился простой конфиг:</p><p>Для мониторинга аппаратных ресурсов удаленного и локального серверов была развернута связка node-exporter + cAdvisor. Все уведомления теперь приходят мгновенно в Telegram-бота. Чтобы 
избежать лавины одинаковых сообщений (например, при перезагрузке хоста),
 в Alertmanager настроена жесткая группировка и дедупликация событий. 
Также добавлен экспортер для AdGuard Home, выводящий статистику 
заблокированных запросов в Grafana.</p><h3>Централизованные логи: Loki + Grafana Alloy</h3><p>В релизе v1.3.0 в стек была добавлена централизованная система сбора логов Loki. Вместо устаревшего Promtail в качестве агента сбора я применил Grafana Alloy.</p><p>Он
 эффективно собирает логи со всех запущенных Docker-контейнеров, парсит 
их и передает в Loki. Теперь вся история событий, ошибок веб-сервера 
Nginx или падений внутренних приложений доступна в едином интерфейсе 
Grafana с возможностью удобной фильтрации через LogQL, что значительно 
упрощает отладку.</p><p>Конфиг Loki:</p><p>Конфиг Grafana Alloy: (локальный конфиг)</p><h2>Интерфейс: переход на Homepage</h2><p>Изначально для 
домашней страницы я написал кастомную минималистичную HTML-панель с 
Glassmorphism-дизайном. Выглядело это красиво, но добавлять новые 
сервисы вручную через постоянную правку исходного кода было крайне 
неудобно.</p><figure><img src="https://media.tproger.ru/user-uploads/139471/2026-07-15/8a8f2752-8aac-4aa5-898e-98affd01cdff.webp" alt="" /><figcaption>HTML + CSS</figcaption></figure><p>В итоге я заменил самописную страницу на полноценный комьюнити-проект Homepage.
 Это дало некоторую гибкость: вся панель настраивается через простые 
YAML-файлы и поддерживает встроенные виджеты интеграции. Пока что она 
простенькая, но возможно в будущем сделаю что-то более продвинутое.</p><figure><img src="https://media.tproger.ru/user-uploads/139471/2026-07-15/90c65d4a-1310-4a30-b1ad-9e4d3083959d.webp" alt="" /><figcaption>Homepage</figcaption></figure><h2>Заключение</h2><p>Постарался
 подробно рассказать о структуре проекта и как я к этому пришел, очень 
много я не добавил, если пост зайдет, то я обязательно более подробно 
пройдусь по некоторым моментам: как я настраивал Matrix и на каком 
моменте я решил его убрать, как собирал локальный amnezia 
контейнер-клиент, что нового я добавил в релизе 1.4.0 и другое.</p><p>Также
 я перешел с manage_deploy.sh на отдельный GUI клиент, который 
будет полностью автоматизировать процесс подготовки к deploy. (написан на Go Wails). Про это тоже возможно сделаю статью.</p><p>Вся кодовая база проекта, подробная документация, инструкции по развертыванию открыты и доступны для сообщества:</p><p>🔗 GitHub-репозиторий: <a href="https://github.com/canntstand/ServeHub-2" rel="noopener noreferrer nofollow">https://github.com/canntstand/ServeHub-2</a></p><p>Это
 моя первая статья на этом сайте и в то же время первый личный проект, которому я 
отдал так много времени (3 месяца). Буду рад вашему фидбеку в 
комментариях!</p>]]></content:encoded>
    </item>
    <item>
      <title>Как приручить legacy-код: безопасная модернизация без заморозки фич</title>
      <link>https://tproger.ru/articles/kak-priruchit-legacy-kod-bezopasnaya-modernizaciya-bez-zamorozki</link>
      <comments>https://tproger.ru/articles/kak-priruchit-legacy-kod-bezopasnaya-modernizaciya-bez-zamorozki?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[KODE]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-priruchit-legacy-kod-bezopasnaya-modernizaciya-bez-zamorozki</guid>
      <description><![CDATA[<p>Как модернизировать legacy-код без остановки продукта: Strangler Fig Pattern, feature flags, shadow testing и безопасная миграция данных. Практика и антипаттерны.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-priruchit-legacy-kod-bezopasnaya-modernizaciya-bez-zamorozki">Как приручить legacy-код: безопасная модернизация без заморозки фич</a>»</p>]]></description>
      <category><![CDATA[Интернет]]></category>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Опрос]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Ruby]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[Практика]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Рефакторинг]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Tor]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[HR]]></category>
      <category><![CDATA[MIT]]></category>
      <category><![CDATA[Финтех]]></category>
      <category><![CDATA[ICO]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Twitter]]></category>
      <category><![CDATA[Игра]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Kubernetes]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[аналитик]]></category>
      <category><![CDATA[OpenTelemetry]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[DeFi]]></category>
      <category><![CDATA[Grafana]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[Legacy]]></category>
      <category><![CDATA[Техника]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 23 Jun 2026 06:16:57 GMT</pubDate>
      <content:encoded><![CDATA[<p>Legacy-код — одна из самых болезненных тем в инженерных командах. Обычно все понимают, что система устарела: архитектура мешает быстро выпускать изменения, новые фичи приходится встраивать через обходные пути, тесты либо неполные, либо отсутствуют, а любое изменение в одном модуле неожиданно ломает другой.</p><p>Но при этом к такому коду часто боятся прикасаться. И не без причины. В старых системах редко бывает понятная карта зависимостей. Документация устарела, часть знаний живет только в головах нескольких разработчиков, а бизнес при этом продолжает ждать новых релизов, интеграций и продуктовых экспериментов.</p><p>Так появляется классическая ловушка legacy: систему надо модернизировать, но остановить развитие нельзя. Переписать всё с нуля страшно, поддерживать как есть — всё дороже. В результате продукт обрастает временными решениями, скорость разработки падает, а стоимость каждого следующего изменения растет.</p><p>Хорошая новость в том, что модернизация legacy-кода не обязана быть большим взрывом. Старую систему можно менять постепенно, сохраняя рабочий продукт, не замораживая фичи и не устраивая один критический релиз, от которого зависит всё.</p><h2>Почему Big Bang-переписывание чаще всего заканчивается плохо</h2><p>Когда команда долго живет с устаревшей системой, идея переписать всё с нуля выглядит очень соблазнительно. Кажется, что можно наконец избавиться от технического долга, выбрать нормальную архитектуру, перепроектировать модули, покрыть всё тестами и начать «правильно».</p><p>На старте такой план часто звучит логично. Особенно если текущая система действительно мешает развитию. Например, мобильное приложение растет, у него уже миллионы пользователей, бэкенд написан несколько лет назад как монолит, а каждая новая фича требует изменений в десятке мест. Команда устала чинить регрессии, бизнес устал ждать, и всем хочется «один раз нормально переписать».</p><p>Проблема не в самой идее переписывания, а в условиях, при которых оно проваливается. Большой риск возникает, когда совпадают четыре фактора: переписывание занимает много месяцев, в это время бизнес продолжает развивать старую систему, новая версия покрывает сразу большую часть функциональности, а откат связан с миграцией данных. Если все четыре пункта присутствуют, Big Bang почти гарантированно превратится в долгий и дорогой проект.</p><p>Допустим, команда решила переписать модуль заказов в e-commerce-продукте. В старой версии есть корзина, промокоды, доставка и оплата. Команда планирует за полгода сделать новый сервис заказов. Но за эти полгода бизнес добавляет подписки, подарочные сертификаты, частичную оплату бонусами и новую логику возвратов. В итоге новая система, которую проектировали под старые требования, к моменту релиза уже нуждается в доработке.</p><p>Есть и другая проблема: большой релиз почти всегда несет максимальный риск. Если вы заменяете крупный кусок системы целиком, ошибка влияет сразу на большую часть пользователей. Откат тоже становится сложным, потому что новая логика уже связана с новыми данными, контрактами и интеграциями.</p><p>Big Bang всё-таки бывает оправдан — но в узких условиях. Если кодовая база молодая (год-два), пользователей мало, у системы нет критичного состояния в БД и продукт можно временно заморозить или вести в обоих контурах параллельно, полное переписывание может оказаться дешевле постепенной миграции. Это редкая ситуация, и она быстро исчезает по мере роста продукта. В зрелых системах безопаснее работает другой подход — постепенная архитектурная эволюция.</p><h2>Пример: как команда переписала сервис документов и потеряла полгода</h2><p>Команда сопровождала сервис — старый модуль на aiohttp с Pydantic v1, через который проходила вся обработка путевых листов и актов осмотра транспорта. Сервис существовал шесть лет, был покрыт тестами фрагментарно, а его API использовали мобильное приложение водителей, диспетчерская веб-панель и пакетный импорт.</p><p>Команда решила переписать сервис целиком: перейти на FastAPI, обновить Pydantic до v2, заодно почистить контракты и заменить внутреннее хранилище документов с MongoDB на PostgreSQL. План был рассчитан на четыре месяца.</p><p>Через восемь месяцев проект всё ещё не был готов к выкатке, а к десятому месяцу команда откатила миграцию полностью. Причин было несколько.</p><p>Во-первых, переписывание шло параллельно с продуктовой разработкой. За время миграции бизнес добавил два новых типа документов и изменил правила подписи актов. Новая система проектировалась под старые требования и к моменту готовности уже не соответствовала продукту.</p><p>Во-вторых, команда не написала характеристических тестов. Поведение «как есть» нигде не было зафиксировано, и расхождения находились только в продакшене после переключения.</p><p>В-третьих, у старого сервиса были скрытые побочные эффекты, о которых никто не помнил. При смене статуса документа публиковал событие в Kafka, которое читал биллинг и сервис аналитики. В новой реализации это поведение не было воспроизведено, потому что в коде оно выглядело как «лишний» вызов. После переключения биллинг перестал получать события, и расхождение обнаружили только через две недели — по жалобе финансового отдела.</p><p>В-четвёртых, переключение было сделано «в лоб»: маршрут в API Gateway просто перенаправили на новый сервис. Фича-флага не было, теневого запуска не было, плана отката не было. Когда выяснилось, что новый сервис строже валидирует исторические форматы документов и отклоняет часть старых записей, быстро вернуться на старую реализацию не получилось — её к тому моменту уже отключили на стенде, а в БД успели уйти записи в новом формате.</p><p>В итоге миграцию свернули, потратив около десяти человеко-месяцев и потеряв доверие бизнеса. Сервис до сих пор работает в исходной реализации, а команда переходит к плану, описанному ниже.</p><h2>Strangler Fig Pattern: как заменить систему по частям</h2><p>Один из самых практичных подходов к модернизации legacy-кода — Strangler Fig Pattern. В софтверном виде паттерн был сформулирован Мартином Фаулером в 2004 году под названием StranglerFigApplication. Идея проста: не переписывать систему целиком, а постепенно выносить отдельные части в новую реализацию.</p><p>Название пришло из биологии. Фикус-душитель растет вокруг дерева-хозяина и постепенно вытесняет его. В архитектуре принцип похожий: старая система продолжает работать, новая функциональность появляется рядом, а затем отдельные потоки постепенно переводятся на новую реализацию.</p><p>Представим старый монолит интернет-магазина. Внутри него есть каталог, корзина, заказы, платежи, скидки, личный кабинет и уведомления. Переписать всё сразу — рискованно. Но можно начать с относительно изолированного участка, например с уведомлений.</p><p>Сначала команда описывает текущий контракт: какие события приходят в модуль уведомлений, какие каналы используются, какие шаблоны отправляются, какие ошибки считаются допустимыми. Затем рядом создается новый сервис уведомлений, который реализует тот же контракт. На первом этапе он может даже не отправлять реальные сообщения, а только принимать события и логировать результат. После проверки часть трафика переводится на новую реализацию. Когда сервис стабилизируется, старый код уведомлений удаляется из монолита.</p><p>Strangler Fig хорошо работает там, где между старым и новым кодом есть сетевая граница: HTTP, message bus, RPC. Если такой границы нет — например, нужно постепенно заменить функцию или класс внутри одного процесса — используется родственный паттерн Branch by Abstraction: над старой реализацией создается абстракция, рядом пишется новая реализация, переключение происходит через конфигурацию или фича-флаг, после стабилизации старая ветка удаляется. Снаружи это выглядит как Strangler Fig, но без сетевого прокси.</p><p>Такой подход снижает риск. В системе нет одного большого релиза, где всё меняется сразу. Есть серия небольших контролируемых изменений. Каждое можно протестировать, измерить и откатить.</p><h2>Главное правило: сначала повторить поведение, потом улучшать</h2><p>Одна из частых ошибок при модернизации legacy-кода — попытка одновременно переписать систему и улучшить бизнес-логику. Команда смотрит на старый модуль и думает: «Раз уж мы его трогаем, давайте сразу сделаем нормальную архитектуру, изменим контракты, уберем странные кейсы и перепишем поведение».</p><p>Это опасный путь. В legacy-системах странное поведение часто существует не случайно. За ним может стоять неочевидное бизнес-правило, старый клиент, интеграция с внешней системой или исторический баг, на который уже кто-то завязался.</p><p>Например, в системе расчета налогов может быть правило: для контрактов, заключенных до 2018 года, НДС округляется в меньшую сторону до целого рубля, а для всех остальных — по математическим правилам. Новый разработчик может решить, что это ошибка, и «исправить» округление. Но потом выяснится, что часть крупных клиентов держит это поведение в своих сверках, а смена правила приведет к расхождениям в актах и претензиям.</p><p>Прежде чем менять поведение, его нужно зафиксировать. Для этого пишут характеристические тесты (characterization tests, иногда называемые golden master или approval tests). Идея простая: на реальных данных или их обезличенных копиях прогоняется старая реализация, её ответы сохраняются как эталон, и любые будущие изменения, отклоняющиеся от эталона, отлавливаются автоматически. Тесты пишутся не для красоты, а для того, чтобы зафиксировать существующее поведение — даже странное — перед тем, как его трогать. Подробно эта техника описана у Майкла Физерса в книге Working Effectively with Legacy Code; на практике её удобно реализовать через библиотеки семейства approval-tests (approvaltests-python, approvaltests-java и аналоги).</p><p>Поэтому первый этап модернизации — не улучшение, а воспроизведение текущего поведения. Новая реализация должна вести себя так же, как старая. Даже если старое поведение кажется странным. Только после стабилизации можно отдельно обсуждать, что именно стоит менять.</p><h2>Feature toggles: как включать новую логику без риска</h2><p>Feature toggles, или фича-флаги, — один из главных инструментов безопасной миграции. Они позволяют включать и выключать новую логику без деплоя.</p><p>В обычной разработке релиз часто выглядит бинарно: код либо выкатили, либо нет. При миграции legacy это неудобно. Гораздо безопаснее иметь возможность включить новую реализацию для 1% пользователей, затем для 10%, потом для половины аудитории и только после этого для всех.</p><p>Например, команда переносит расчет стоимости доставки из монолита в новый сервис. С помощью фича-флага это выглядит так:</p><p>user_id передается явно, чтобы решение «попал ли пользователь в новый сегмент» было стабильным от запроса к запросу. Иначе один и тот же клиент будет получать разные ответы при обновлении страницы, и поведение системы станет непредсказуемым.</p><p>На первом этапе флаг включают только для внутренней команды. Потом для тестового сегмента пользователей. Затем для небольшой доли реального трафика. Если метрики стабильны, долю увеличивают. Если появляются ошибки, флаг выключают, и пользователи снова идут в старую реализацию.</p><p>Важно различать два разных типа флагов. Флаг постепенной выкатки (rollout flag) меняется редко и контролирует, какой процент пользователей видит новую логику. Kill switch — отдельный флаг, единственная задача которого — мгновенно выключить новую реализацию при инциденте. Kill switch должен опрашиваться на каждом запросе, его кэширование должно жить секунды, а не минуты, и он принципиально не должен зависеть от той системы, которую он выключает. Иначе в момент аварии может оказаться, что выключатель сам недоступен.</p><p>В качестве инфраструктуры для флагов команды обычно берут одну из платформ: LaunchDarkly, Unleash, Flagsmith, GrowthBook, либо собирают собственную поверх Redis или конфигурационного сервиса. Для миграции важны три свойства: быстрое распространение изменений (секунды, а не минуты), поддержка таргетинга по пользователю/сегменту и аудит — кто и когда менял флаг.</p><p>Важно, что фича-флаг — это не просто if в коде. Для серьезной миграции нужны правила: кто может включать флаг, как быстро его можно отключить, какие метрики отслеживаются, когда флаг должен быть удален.</p><p>Последний пункт особенно важен. Если флаги не удалять, система быстро превращается в набор ветвлений, где никто уже не понимает, какая логика актуальна.</p><h2>Shadow testing: как проверить новую систему на реальном трафике</h2><p>Feature toggles помогают безопасно переключать пользователей. Но перед этим хорошо бы понять, совпадает ли новая логика со старой. Для этого используют shadow testing.</p><p>Shadow testing — это запуск новой реализации параллельно старой, но без влияния на пользователя. Пользовательский запрос по-прежнему обрабатывает старая система, а новая получает копию запроса и считает результат «в тени». Пользователю этот результат не показывается. Команда только сравнивает ответы.</p><p>Например, есть старый модуль расчета скидок. Он учитывает промокоды, сегмент пользователя, историю покупок, регион и партнерские условия. Команда пишет новый сервис скидок. Чтобы не переключать пользователей сразу, можно запустить теневой режим:</p><p>Два момента, на которые стоит обратить внимание в этом коде. Теневой вызов запускается через asyncio.create_task — корутина сразу планируется в event loop и начнёт выполняться, как только функция вернёт управление. И весь блок завернут в try/except: исключение в новой логике не должно ронять основной запрос. Без этих двух свойств shadow testing рискует ухудшить продакшен вместо того, чтобы безопасно его проверить.</p><p>Небольшая оговорка для продакшена: event loop держит на task только слабую ссылку, и без сохранённой ссылки задача может быть собрана сборщиком мусора прямо во время выполнения. В реальном коде Task имеет смысл класть в set фоновых задач и удалять оттуда через add_done_callback. В примере выше эта обвязка опущена для читаемости.</p><p>Для критичной доменной логики — платежей, биллинга, расчета тарифов — допустимый уровень расхождения должен быть около нуля: цель в shadow-режиме не «как можно меньше различий», а «понимаем каждое расхождение». Для менее чувствительных доменов (рекомендации, ранжирование результатов поиска) можно жить с расхождением в долях процента, но и там расхождения нужно классифицировать, а не игнорировать. Возможно, это баги новой реализации. А возможно, старая система содержит устаревшую логику, которую нужно отдельно обсудить с бизнесом.</p><p>Shadow testing особенно полезен для критичных доменных частей: платежей, биллинга, расчета тарифов, персональных предложений, транзакций. Там нельзя просто «попробовать на пользователях» и посмотреть, что будет.</p><p>При этом важно отличать теневую проверку чтения от теневой проверки записи. Чтение проверить относительно дёшево: запрос идёт в обе системы, ответы сравниваются, никаких внешних эффектов нет. С записью всё сложнее. Если новая реализация в shadow-режиме действительно создаст заказ, спишет деньги или отправит письмо, у пользователя возникнут двойные эффекты. Поэтому для writes либо вводят идемпотентные ключи и shadow-режим без реальных побочных действий (внешние вызовы заменены no-op-стабами, БД — отдельной shadow-копией), либо вообще отказываются от теневой проверки записи в пользу постепенной выкатки за фича-флагом.</p><p>Сравнение ответов в реальной системе тоже не сводится к одной функции compare. Нужно отдельно решать, как игнорировать «нормальный» шум (метки времени, идентификаторы, порядок коллекций), как сэмплировать трафик, чтобы не утопить хранилище расхождений, и как организовать триаж — кто и в каком ритме разбирает накопившиеся диффы. Готовые решения этого класса — GitHub Scientist (Ruby и его порты в другие языки), Twitter Diffy, либо собственный лёгкий регистратор поверх Kafka и таблицы расхождений.</p><h2>С чего начинать модернизацию</h2><p>Начинать лучше не с самого больного и не с самого центрального модуля. Это звучит контринтуитивно, потому что обычно хочется сразу взяться за главный источник проблем. Но если начать с ядра системы, команда быстро упрется в максимальное количество зависимостей и рисков.</p><p>Удобный способ выбрать первый кусок — оценить кандидатов по двум осям: насколько модуль критичен для бизнеса (low / high) и насколько сильно он связан с остальной системой (low / high). Начинать стоит с квадранта low-criticality + low-coupling: ошибки в нем не уронят бизнес-показатели, а малое количество зависимостей позволит провести миграцию полностью, не утянув за собой смежные модули. Высоко-критичные и сильно связанные части (платежи, ядро авторизации) трогают в последнюю очередь — на этот момент команда уже наберёт опыт безопасной миграции.</p><p>Хорошие точки входа обычно: уведомления, генерация отчетов, поиск, история операций, профиль пользователя, отдельная часть каталога. Важно, чтобы у команды была возможность описать контракт: какие данные входят, какие выходят, какие ошибки возможны, какие внешние системы участвуют.</p><p>Допустим, в банковском приложении есть старый модуль истории операций. Он медленный, сложно расширяется, но при этом не выполняет сами транзакции. Это хороший кандидат для первой миграции. Ошибка в истории операций неприятна, но обычно менее критична, чем ошибка в списании денег.</p><p>Команда может вынести чтение истории в отдельный сервис, сначала запустить его в shadow-режиме, потом включить для части пользователей, затем полностью перевести чтение на новую реализацию. При этом критичная транзакционная логика останется в старой системе до тех пор, пока команда не наберет опыт безопасной миграции.</p><h2>Миграция данных: самая сложная часть</h2><p>Большая часть статьи говорит о маршрутизации запросов и переключении трафика. Но в реальных проектах основная сложность лежит ниже — в данных. Старая и новая реализации почти всегда работают с общим состоянием: одной БД, одним хранилищем документов, одним набором очередей. Переехать туда «одним коммитом» нельзя.</p><p>Базовый рабочий приём — Expand-Contract (он же Parallel Change). Изменение схемы делается в три такта. На этапе expand в БД добавляются новые поля, таблицы или индексы, при этом старое поведение полностью сохраняется. Затем — migrate: обе реализации начинают писать и в старое, и в новое место (dual writes), а отдельный фоновый процесс делает backfill — заполняет новые поля историческими данными. После этого читатели по одному переключаются на новую схему. Только когда никто из читателей не использует старую структуру, наступает contract — удаление лишних колонок и таблиц.</p><p>Несколько практических деталей, которые часто упускают:</p><p>·         Dual writes — это не бесплатная операция. Две записи означают две точки отказа. Если одна из них упала, нужно решать, что делать: продолжать ли работу, ставить ли событие в очередь на повтор, помечать ли запись как несогласованную. Простое «сначала пишем туда, потом сюда» в продакшене на нагрузке приводит к расхождениям.</p><p>·         Backfill часто длиннее, чем кажется. На большой таблице миграция в одном UPDATE блокирует продакшен. Поэтому backfill делают батчами по N тысяч строк с паузами, отслеживают прогресс и предусматривают возможность остановить и продолжить.</p><p>·         Онлайн-изменения схемы на крупных таблицах делаются не штатным ALTER TABLE, а специализированными инструментами: gh-ost или pt-online-schema-change для MySQL, встроенные онлайн-механизмы PostgreSQL для индексов и колонок, Liquibase/Flyway — для управления версионированием изменений в репозитории.</p><p>·         Shadow testing данные не покрывает. Можно сравнить, что новая реализация возвращает то же, что и старая, но если за этим стоит другая схема в БД, проверка корректности самой миграции данных — это отдельная работа: сверки, контрольные суммы, выборочный аудит исторических записей.</p><p>Без этих шагов любая красивая фасадная архитектура наталкивается на разъезжающиеся данные — и тогда даже идеальный Strangler Fig снаружи не спасает.</p><h2>Прокси-слой как точка контроля</h2><p>Чтобы постепенно заменять legacy-код, нужно управлять маршрутизацией запросов. Для этого часто создают прокси-слой, API Gateway или фасад, через который проходит обращение к старой и новой логике. В терминах Domain-Driven Design такой слой часто называют Anti-Corruption Layer: он защищает новую реализацию от старых контрактов и наоборот, позволяя двум моделям сосуществовать без взаимного «загрязнения».</p><p>Без такой точки контроля миграция становится хаотичной. Часть клиентов ходит напрямую в старый модуль, часть — в новый, часть использует обходные пути, а команда теряет возможность централизованно переключать трафик.</p><p>Прокси-слой решает несколько задач. Он скрывает детали реализации от клиентов, позволяет направлять часть запросов в новую систему, поддерживает фича-флаги, собирает метрики и упрощает откат.</p><p>В качестве технической основы команды обычно берут один из трех вариантов: классический API gateway (Kong, AWS API Gateway), service mesh (Envoy, Istio) или более простой reverse proxy (NGINX, HAProxy). Service mesh особенно удобен, когда трафик уже идёт внутри Kubernetes-кластера: маршрутизацию можно менять конфигурацией, без правок кода клиентов и сервисов.</p><p>Например, мобильное приложение обращается к endpoint /orders/history. Раньше этот endpoint напрямую обслуживал монолит. После введения API Gateway приложение продолжает ходить по тому же контракту, но внутри gateway может решать, куда направить запрос: в legacy-модуль или новый сервис истории заказов.</p><p>Управление маршрутизацией обычно делается не «всё или ничего», а на основании атрибутов запроса: значения заголовка (X-Migration-Cohort: new), куки, хэша от user-id (стабильное разбиение пользователей на сегменты) или географического региона. Это позволяет выкатывать новую реализацию сначала на одну страну, на сотрудников самой компании или на тестовый сегмент — и только потом расширять охват.</p><p>Для клиента ничего не меняется. Для команды появляется управляемость.</p><h2>Наблюдаемость: без метрик миграция превращается в гадание</h2><p>Постепенная модернизация невозможна без нормальной наблюдаемости. Если команда не видит, что происходит внутри системы, она не сможет безопасно переключать трафик.</p><p>Минимальный набор — это логи, метрики и распределенная трассировка (distributed tracing). Нужно понимать, сколько запросов идет в старую и новую реализацию, сколько ошибок возникает, как меняется latency, где появляются таймауты, какие статусы возвращаются, какие бизнес-метрики проседают.</p><p>Технические метрики стоит формулировать не как «средний ответ» и «процент ошибок», а в терминах SLI и SLO: целевые показатели вида «99.9% запросов на /orders/history отвечают быстрее 300 ms за 30 дней» с явным error budget. Latency измеряется по перцентилям (p50, p95, p99) — среднее значение почти всегда обманчиво, а хвосты распределения говорят о реальном опыте пользователя. На время миграции имеет смысл выставить отдельные SLO для нового и старого пути и сравнивать их.</p><p>В качестве инструментов де-факто стандартом стал OpenTelemetry для трассировок, метрик и логов — единый протокол, который пишет в практически любое хранилище. Дальше — Prometheus и Grafana для метрик, Jaeger или Tempo для traces, Sentry или аналог для ошибок. Для миграции важна возможность фильтровать метрики по «варианту» — отдельно по старому и новому пути — иначе все цифры смешаются и реальную динамику будет не видно.</p><p>Технических метрик недостаточно. Если команда переносит оформление заказа, важно смотреть не только на 500 ошибки и время ответа, но и на конверсию в оплату, количество брошенных корзин, повторы запросов, обращения в поддержку.</p><p>Пример: новая система формально отвечает быстрее старой и не дает ошибок. Но после включения на 10% пользователей падает конверсия в оплату. Причина может быть не в серверной ошибке, а в изменении порядка полей, другом тексте сообщения или потере какого-то edge-case. Без бизнес-метрик команда может решить, что миграция успешна, хотя для продукта она уже создает проблему.</p><h2>Практическая последовательность миграции</h2><p>Рабочая последовательность обычно выглядит так.</p><p>Сначала команда выбирает ограниченный участок системы. На этом этапе важно не просто назвать модуль, а описать его границы. Какие сценарии он закрывает? Кто его вызывает? Какие данные он читает и пишет? Какие внешние интеграции использует? Какие неочевидные бизнес-правила в нем есть?</p><p>Затем поверх legacy-логики создается стабильный контракт. Это может быть API, фасад, gateway или отдельный слой внутри приложения. Главная задача — сделать так, чтобы клиенты зависели не от внутренней реализации, а от понятного интерфейса. На этом этапе полезно вспомнить про contract testing (Pact, Spring Cloud Contract): автотесты со стороны потребителей фиксируют, что именно они ожидают от API, и предупреждают о ломающих изменениях до того, как они доедут до продакшена.</p><p>После этого рядом пишется новая реализация. Она должна повторять текущее поведение, а не сразу становиться «идеальной версией будущего». На этом этапе полезно фиксировать все расхождения: где старая система работает странно, где требования не описаны, где бизнес-правила требуют уточнения.</p><p>Следующий этап — shadow testing. Новая система получает копии реальных запросов, считает результат, но пользователю по-прежнему возвращается ответ legacy. Команда сравнивает результаты и устраняет расхождения.</p><p>Когда новая реализация достаточно стабильна, начинается постепенное переключение через feature toggles. Сначала внутренние пользователи, потом 1% реального трафика, затем 5–10%, затем 50% и только после этого 100%.</p><p>На каждом этапе команда смотрит на метрики. Если всё стабильно, движение продолжается. Если появляются проблемы, флаг выключается, трафик возвращается в legacy, а команда разбирает причины.</p><p>Последний этап — удаление старого кода. Это не формальность, а обязательная часть миграции. И «удалить старый код» — это не один коммит, а явный Definition of Done: вырезана старая ветка кода, удалён фича-флаг, обновлена документация и схемы архитектуры, переименованы или удалены устаревшие дашборды и алерты, обновлены runbook’и для on-call и проведено короткое внутреннее обучение. Если этого не сделать, через полгода никто уже не вспомнит, какой путь актуален, и легаси-ветвление останется в коде навсегда.</p><h2>Откат миграций: дешёвый только пока не пошли записи</h2><p>Откатить миграцию, в которой ещё не было записи в БД, легко: достаточно переключить фича-флаг, и трафик снова идёт через старую реализацию. Откатить миграцию, в которой новая система уже неделю писала данные в новые таблицы, — отдельный, гораздо более тяжёлый разговор.</p><p>Поэтому ещё на этапе проектирования каждое изменение должно сопровождаться явным планом отката. Удобно различать три типа шагов.</p><p>Полностью обратимые шаги. Чтение через новый сервис, расчёт «в тени», новые метрики. Откат — выключить флаг. Это самый комфортный режим, и в нём стоит держать миграцию как можно дольше.</p><p>Обратимые с компенсацией. Новая реализация пишет дополнительные данные (например, дублирует операции в новую таблицу), но старый источник тоже обновляется. Откат возможен, но требует решить, что делать с уже записанными данными: оставить, очистить, синхронизировать. План этих действий должен быть написан до выкатки, не во время инцидента.</p><p>Forward-only. После некоторой точки откат становится невозможен — например, после того, как старая схема удалена или внешние интеграции перенастроены на новый сервис. Такие шаги допустимы, но к ним нужно приходить отдельно, осознанно, с особенно строгими SLO в предыдущем этапе. До forward-only-перехода имеет смысл подержать систему в режиме параллельной работы дольше, чем по графику.</p><p>Базовое правило: ни один шаг миграции не должен уходить в продакшен, если у команды нет письменного ответа на вопрос «как мы откатываемся в случае проблемы». Иначе при инциденте откатываться будут на ходу — и не факт, что успешно.</p><h2>Пример: как тот же сервис мигрировали со второй попытки</h2><p>После неудачного опыта команда взялась за тот же сервис заново, но изменила подход.</p><p>На первом шаге они зафиксировали поведение существующего сервиса. На самые часто используемые сценарии (создание путевого листа, подпись акта осмотра, выгрузка пакета документов за период) написали характеристические тесты на реальных продакшен-данных, обезличенных и сохранённых как фикстуры. Любое будущее изменение поведения теперь падало в CI как явное расхождение.</p><p>Параллельно команда провела инвентаризацию побочных эффектов. Из исходного кода и логов выяснилось, что сервис не только хранит документы, но и: публикует событие в Kafka при смене статуса, инкрементирует счётчик в Redis для рейтинга водителей, отправляет webhook во внешнюю систему партнёра, пишет в таблицу аудита. Каждый из этих эффектов попал в отдельный пункт чек-листа «что должно остаться» в новой реализации.</p><p>Затем команда выбрала первый кусок для выноса — не весь сервис, а только чтение документов (GET /documents/{id} и GET /documents/by-driver/{driver_id}). Это была наименее рискованная часть: ошибки в чтении неприятны, но не ломают финансовые потоки.</p><p>Новый сервис написали на FastAPI рядом со старым. На уровне API Gateway появилось правило маршрутизации: запросы на чтение шли в старый сервис, но в фоне дублировались в новый. Ответ пользователю всегда возвращал legacy, а ответ нового сервиса сравнивался с эталоном и записывался в отдельную таблицу для разбора. Использовали обёртку поверх asyncio.create_task — на ответ пользователя теневой вызов не влиял.</p><p>За три недели shadow-режима команда нашла четыре расхождения. Два оказались багами новой реализации (округление времени, неправильная сортировка вложений). Два — давно забытыми особенностями старого сервиса (одно поле возвращалось в UTC, другое — в локальной зоне; так было исторически, бизнес не возражал, но в новой реализации захотели единый формат). Все четыре зафиксировали явно: баги — починили, особенности — согласовали с продуктовой командой как осознанное изменение.</p><p>Когда расхождений не осталось, включили фича-флаг на сотрудников самой компании. Через неделю — на 1% реальных водителей. Дальше шаг по 5%, 25%, 50%, 100% с паузой в несколько дней между этапами. На каждом шаге следили не только за HTTP-ошибками и latency, но и за продуктовыми метриками: количество подписанных актов, время от открытия документа до подписи, доля повторных запросов. Один раз пришлось откатиться с 25% на 5% — в одном из регионов выросло время отклика из-за неэффективного запроса. Исправили, выкатили снова.</p><p>Через два месяца чтение полностью перешло в новый сервис. Старый код чтения и фича-флаг удалили в том же релизе. После этого по той же схеме мигрировали запись документов, потом публикацию событий, потом импорт из внешних систем. Полная миграция заняла девять месяцев — почти столько же, сколько провалившийся Big Bang, — но продукт всё это время продолжал развиваться, инцидентов не было, и в конце команда осталась с системой, которую понимает.</p><h2>Типичные ошибки при работе с legacy</h2><p>Первая ошибка — пытаться улучшить всё сразу. Команда одновременно меняет архитектуру, бизнес-логику, контракты и инфраструктуру. В результате становится невозможно понять, какая именно часть вызвала проблему. Правильнее сначала воспроизвести поведение, стабилизировать новую реализацию и только потом улучшать.</p><p>Вторая ошибка — недооценивать скрытые зависимости и побочные эффекты. Legacy-код часто делает больше, чем кажется. На один и тот же вызов могут быть навешаны: запись в таблицу аудита, инкремент счётчика в кэше, публикация события в очередь, обновление статуса связанной сущности, инвалидация кэша, дёрганье webhook’а во внешнюю систему. Если в новой реализации воспроизвести только явный путь, скрытые потребители молча перестанут получать данные — и узнают об этом через жалобу бизнеса, а не через ошибку в логах. Поэтому перед выносом любого модуля имеет смысл составить инвентаризацию побочных эффектов: пройтись по коду и логам и выписать каждое нелогичное действие отдельным пунктом чек-листа.</p><p>Третья ошибка — отсутствие наблюдаемости. Без логов, метрик и трассировки команда не управляет миграцией, а угадывает. Особенно опасно смотреть только на технические ошибки и игнорировать бизнес-показатели.</p><p>Четвертая ошибка — не договариваться с бизнесом. Модернизация не должна быть невидимой «инженерной активностью в стол». Её нужно встраивать в roadmap, объяснять эффект и договариваться о приоритетах. Если бизнес не понимает, зачем команда тратит время на миграцию, работа будет постоянно проигрывать новым фичам.</p><p>Пятая ошибка — не удалять старый код. Временное сосуществование старой и новой логики нормально. Вечное сосуществование — нет. Если legacy не удаляется, технический долг не уменьшается, а просто меняет форму.</p><p>Шестая ошибка — не удалять фича-флаги после миграции. Флаг, который сыграл свою роль и больше никогда не выключается, превращается в постоянное ветвление в коде. Через год команда не помнит, можно ли удалить такую ветку или там сидит важный edge-case. Через два — кода с такими «мёртвыми» флагами становится больше, чем основной логики. Поэтому каждый флаг должен заводиться с условием удаления («после полной выкатки и двух недель стабильной работы») и иметь ответственного, кто этим удалением займётся.</p><p>Отдельно стоит упомянуть организационную сторону. Закон Конвея работает и в обратную сторону: если новый и старый код владеются разными командами с разными приоритетами, миграция будет тормозиться независимо от выбранного паттерна. На время миграции имеет смысл явно проговорить, кто отвечает за переход, и не разделять старую и новую реализации между несовместимыми roadmap’ами.</p><h2>Компромиссы, к которым нужно быть готовыми</h2><p>Постепенная модернизация безопаснее Big Bang-переписывания, но она не бесплатна. Некоторое время система будет сложнее, чем раньше. В ней появятся старый и новый код, прокси-слой, фича-флаги, дублирование логики, дополнительные метрики.</p><p>Shadow testing увеличит нагрузку на инфраструктуру, потому что часть запросов будет обрабатываться дважды. Команде придется поддерживать дисциплину: документировать контракты, отслеживать флаги, удалять старую реализацию после миграции, поддерживать contract-тесты в актуальном состоянии.</p><p>Но это контролируемая сложность. Она распределена во времени и управляется инженерными практиками. В отличие от Big Bang-риска, где команда долго работает с минимальной обратной связью, а потом выкатывает один большой релиз с максимальной неопределенностью.</p><h2>Когда Strangler Fig особенно оправдан</h2><p>Постепенная миграция особенно хорошо подходит для систем, где downtime невозможен или слишком дорог. Это финтех, e-commerce, биллинг, мобильные бэкенды с большой аудиторией, высоконагруженные продукты, старые монолиты и системы с большим количеством интеграций.</p><p>Если продуктом ежедневно пользуются сотни тысяч или миллионы людей, нельзя позволить себе «переписать и посмотреть, что будет». Нужно менять архитектуру так, чтобы пользователь не замечал процесса миграции.</p><p>Этот подход также полезен там, где бизнес продолжает активно развивать продукт. Если фичи нельзя заморозить на полгода, модернизация должна идти параллельно с продуктовой разработкой.</p><h2>Когда модернизацию лучше не делать</h2><p>Постепенная миграция — мощный инструмент, но у неё тоже есть стоимость, и иногда правильный ответ — оставить систему как есть. Несколько сценариев, в которых модернизация плохо окупается.</p><p>Продукт, который уходит из эксплуатации. Если через год сервис будет выключен или заменён на покупное решение, тратить квартал на его рефакторинг бессмысленно. Достаточно стабилизировать то, что есть.</p><p>Модуль, который никто не трогает. Если код десятилетней давности продолжает работать, не падает, не требует изменений и не вызывает инцидентов, его «уродливость» — не повод его переписывать. Цель модернизации — упростить будущие изменения; если будущих изменений нет, цели тоже нет.</p><p>Регулируемые системы с тяжёлой ресертификацией. В банковских, медицинских и государственных контурах любое изменение в критичной системе может потребовать повторной сертификации, перепрохождения аудитов, обновления договорной обвязки. В таких условиях стоимость модернизации может на порядок превышать стоимость поддержки текущей реализации, и решение нужно принимать вместе с владельцем продукта и юристами, а не только инженерным составом.</p><p>Простой тест: если на вопрос «какой бизнес-сценарий мы откроем после миграции» нет внятного ответа — модернизацию имеет смысл отложить и заняться чем-то другим.</p><h2>Что получает команда</h2><p>Главный результат постепенной модернизации — управляемость. Команда начинает лучше понимать систему, контролировать изменения и снижать риск инцидентов.</p><p>Появляются понятные контракты, наблюдаемость, практика безопасных релизов, культура удаления старого кода. Разработчики перестают бояться legacy, потому что у них появляется метод, а не только желание «когда-нибудь всё переписать».</p><p>Для бизнеса это тоже выгодно. Продукт продолжает развиваться, сроки становятся более прогнозируемыми, риски крупных сбоев снижаются, а технический долг постепенно уменьшается.</p><h2>Модернизация — это процесс, а не проект</h2><p>Legacy нельзя «починить за квартал». Если система развивалась годами, она не станет простой после одного рефакторинга. Но её можно системно улучшать.</p><p>Strangler Fig Pattern, Branch by Abstraction, feature toggles, shadow testing и аккуратная миграция данных дают рабочую модель: выбрать ограниченный участок, описать контракт, реализовать новую версию, проверить её на реальном трафике, постепенно переключить пользователей и удалить старый код.</p><p>Это не самый быстрый путь. Зато он управляемый. А в зрелых продуктах управляемость важнее скорости.</p><p>Потому что цель модернизации — не написать красивую новую систему. Цель — сделать так, чтобы продукт продолжал развиваться, команда могла безопасно вносить изменения, а пользователи не становились участниками инженерного эксперимента.</p>]]></content:encoded>
    </item>
    <item>
      <title>OwnCloud – аналог Nextcloud: для чего использовать и как настроить облачное хранилище</title>
      <link>https://tproger.ru/articles/owncloud-analog-nextcloud-dlya-chego-ispolzovat-i-kak-nastroi</link>
      <comments>https://tproger.ru/articles/owncloud-analog-nextcloud-dlya-chego-ispolzovat-i-kak-nastroi?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Oksana Karelina]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/owncloud-analog-nextcloud-dlya-chego-ispolzovat-i-kak-nastroi</guid>
      <description><![CDATA[<p>ownCloud vs Nextcloud, что лучше? Какое облачное хранилище выбрать? Как может помочь связка S3 с ownCloud?
</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/owncloud-analog-nextcloud-dlya-chego-ispolzovat-i-kak-nastroi">OwnCloud – аналог Nextcloud: для чего использовать и как настроить облачное хранилище</a>»</p>]]></description>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Android]]></category>
      <category><![CDATA[Опрос]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[Конференции]]></category>
      <category><![CDATA[IDE]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Tor]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[HR]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[macOS]]></category>
      <category><![CDATA[ICO]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Маркетинг]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Мессенджер]]></category>
      <category><![CDATA[Персональные данные]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Дизайн]]></category>
      <category><![CDATA[Машин]]></category>
      <category><![CDATA[1C]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 07 May 2026 08:40:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Вы когда-нибудь задумывались, сколько информации производит человечество?</p><p>Если верить статистике, сейчас ежедневно создается около <a href="https://explodingtopics.com/blog/data-generated-per-day">402,74</a> миллионов терабайт данных.</p><p>Согласитесь, довольно внушительная цифра.</p><p>В этих реалиях, когда объем данных постоянно растет, у каждого из нас рано или поздно может возникнуть вопрос – где хранить рабочие и личные файлы, да еще и так, чтобы сохранить абсолютный контроль над ними.</p><p>Меня зовут Оксана, я маркетолог в Beget и в этой статье хочу поделиться решением, которое мы выбрали у себя в отделе для хранения файлов, когда заметили, что их стало слишком много.</p><p>Мы решили перейти на гибкое объектное хранилище S3 – чтобы централизовано хранить и управлять текстами, креативами, отчетами и другими маркетинговыми материалами с удобным доступом внутри команды, ведь S3 позволяет хранить файлы любого типа и объема и масштабируется автоматически. Осталось только выбрать ПО для хранения файлов в облаке, к которому можно подключить S3.</p><p>Ранее у нас был опыт использования Nextcloud, однако его функционал, подобный швейцарскому ножу (встроенные календарь, конференции, таск-трекер и т. д.), оказался слишком объемен для нашей, по сути, скромной задачи – удобного и стабильного хранения файлов.</p><p>Вот почему мы подыскали аналог Nextcloud – ownCloud. В отличие от более функционального <a href="https://beget.com/ru/cloud/marketplace/nextcloud">Nextcloud</a>, ownCloud заточен исключительно на работу с файлами. И при этом он поддерживает подключение облачного объектного хранилища S3. Поэтому для нас в сравнении Nextcloud vs ownCloud выбор был очевиден.</p><p>В этой статье я расскажу, какие возможности есть у ownCloud, почему это ПО может быть полезно и как настроить связку ownCloud и S3. Если вы хотите организовать безопасное, контролируемое хранение и обмен данными на работе или дома, то этот материал будет для вас полезен.</p><h2>Что может ownCloud</h2><p>Для начала – буквально несколько слов об ownCloud и его возможностях.</p><p>Это программное обеспечение с открытым исходным кодом для хранения, синхронизации и обмена файлами появилось в 2010 году благодаря усилиям разработчика KDE Франка Карличека, который <a href="https://ru.wikipedia.org/wiki/OwnCloud">стремился</a> создать бесплатную альтернативу коммерческим облачным сервисам хранения данных.</p><h3>OwnCloud позволяет:</h3><ol><li>получать доступ к данным из любой точки мира и хранить файлы на собственном сервере – под вашим полным контролем;</li><li>синхронизировать данные между устройствами – доступ к файлам возможен с компьютеров (Windows, macOS, Linux), смартфонов (iOS, Android) и через браузер, изменения на одном устройстве мгновенно появляются на всех остальных;</li><li>делиться файлами и папками по ссылке, настраивая права доступа, пароли и срок действия ссылок;</li><li>совместно работать с документами, отслеживать историю изменений и возвращаться к любой предыдущей версии файла.</li></ol><blockquote>Только ownCloud сочетает в себе полный контроль над данными с простыми в использовании функциями обмена файлами, делая совместную работу более эффективной и безопасной.</blockquote><p>Сегодня ownCloud используют <a href="https://owncloud.com/customers/">компании</a> (Philips, Nationwide, Zeppelin и др.) в самых разных сферах (IT, машиностроение, медицина и т. д.).</p><p>При этом решение подходит не только для работы, но и для личных целей, когда нужно обменяться фото и видео с родственниками и друзьями, ведь, по мнению пользователей, среди преимуществ ownCloud – <a href="https://www.capterra.com/p/176602/ownCloud/reviews/">простота настройки</a> и <a href="https://www.temjournal.com/content/102/TEMJournalMay2021_954_960.pdf">удобная синхронизация с различными гаджетами</a>.</p><blockquote>С ownCloud мне не нужно слепо доверять какой-то неопределенной организации. Я контролирую, как происходит обмен файлами, и ownCloud помогает мне на каждом этапе.</blockquote><p>OwnCloud позволяет решать самые разные задачи, связанные с работой с файлами, – расскажем на примере трех кейсов, как это облачное хранилище помогает нам в отделе маркетинга.</p><h2>Для каких задач мы используем ownCloud и S3</h2><h3>1. Централизованное управление материалами</h3><p>Мы часто работаем с текстами, изображениями и презентациями. Дизайнеры и авторы загружают эти материалы в ownCloud, файлы автоматически сохраняются в S3, а для удобства поиска у нас настроены теги.</p><p>В итоге каждый член команды может видеть версии файлов (это важно для правок), нет хаоса в почте и мессенджерах.</p><h3>2. Безопасное взаимодействие с подрядчиками</h3><p>Связка ownCloud и S3 позволяет выгружать внешним специалистам материалы и получать результаты работ без прямого доступа к внутренней сети компании. Мы создали папку с публичной ссылкой, но жесткими ограничениями – паролем, сроком жизни ссылки в течение нескольких дней и разрешением на загрузку файлов без права просмотра папки.</p><p>На практике это работает так: менеджер создает ссылку и отправляет подрядчику, подрядчик переходит по ссылке и загружает архив с готовыми материалами, файл попадает в ownCloud, а его содержимое сохраняется в S3. Таким образом, подрядчик не видит, какие еще файлы лежат в папке, а мы контролируем, кто, что и когда загрузил.</p><h3>3. Долгосрочный архив креативов и отчетов</h3><p>По закону (152-ФЗ в РФ или GDPR в Европе) компания обязана хранить персональные данные клиентов, а также отчеты о рассылках и рекламных акциях на протяжении определенного времени.</p><p>Для решения этой задачи мы настроили правило: файлы старше 90 дней автоматически перемещаются в S3 Glacier (холодное хранилище) – этот класс снижает стоимость хранения, а если, например, юристу понадобится скачать какой-нибудь отчет спустя 2–3 года, он просто выгрузит его из ownCloud буквально за 5–10 минут.</p><p>Теперь – в деталях и по шагам о том, как начать использовать ownCloud в связке с S3.</p><h2>Как развернуть ownCloud и подключить S3</h2><p>OwnCloud удобно использовать с объектным хранилищем S3 – таким образом можно:</p><ol><li>масштабировать систему – S3 расширяется автоматически и не имеет ограничений по объему и количеству размещаемых данных и файлов;</li><li>оптимизировать затраты – можно платить не за дорогую конфигурацию виртуального сервера с большим объемом диска, а лишь за фактически занимаемое место, по модели pay as you go (оплата по мере потребления);</li><li>повысить надежность хранения – за счет встроенной в S3 тройной репликации данных (файлы хранятся в 3 копиях и размещаются на независимых серверах в разных стойках для абсолютной сохранности данных).</li></ol><h3>Итак, разберем, как настроить связку ownCloud и S3.</h3><p>Разработчики ownCloud предлагают два варианта установки. Можно скачать ownCloud и установить его вручную или использовать Docker-контейнеры. Мы выберем второй вариант.</p><p>Для размещения ownCloud в нашем примере создадим виртуальный сервер на базе <a href="https://beget.com/ru/cloud/marketplace/docker">готового решения Docker</a>.</p><p>Можно подключиться к серверу по SSH или с помощью терминала в панели управления.</p><p>Для размещения файлов создайте бакет объектного хранилища S3. Реквизиты доступа к нему будут в карточке бакета в панели:</p><figure><img src="https://media.tproger.ru/user-uploads/105012/2026-04-29/ee49d00b-7e32-4b8d-8ad4-bbbaeb0c3bb0.webp" alt="" /></figure><p>Создайте директорию для размещения конфигурационных файлов проекта и перейдите в нее:</p><p>Затем вставьте в файл docker-compose.yml следующее содержимое с помощью любого текстового редактора:</p><p>После этого создайте файл .env, в котором будут храниться значения переменных. Шаблон файла следующий:</p><p>Теперь необходимо отредактировать эти строки:</p><ol><li>ownCloud_DOMAIN и ownCloud_TRUSTED_DOMAINS – укажите домен (так как ownCloud будет размещен за обратным прокси, указывать рабочий порт здесь не требуется);</li><li>ADMIN_USERNAME – логин администратора;</li><li>ADMIN_PASSWORD – пароль администратора.</li></ol><p><i>Обратите внимание! Изменение ADMIN_USERNAME и ADMIN_PASSWORD уже после развертывания контейнеров не возымеет эффекта. Изменить пароль администратора вы можете в настройках пользователя в веб-интерфейсе.</i></p><p>Далее необходимо указать параметры подключения к S3.</p><ul><li>ownCloud_OBJECTSTORE_BUCKET – имя бакета S3;</li><li>ownCloud_OBJECTSTORE_ENDPOINT – эндпоинт хранилища (например, https://s3.ru1.storage.beget.cloud);</li><li>ownCloud_OBJECTSTORE_REGION – регион (ru1 для Beget);</li><li>ownCloud_OBJECTSTORE_KEY – Access key бакета;</li><li>ownCloud_OBJECTSTORE_SECRET – Secret key бакета.</li></ul><p>Сохраните файл.</p><p>Остается лишь добавить файл конфигурации для Caddy – обратного прокси, через который пользователи будут получать доступ к ownCloud.</p><p>Создайте директорию config:</p><p>После чего создайте в ней файл конфигурации Caddyfile. Добавьте в него следующее содержимое, указав вместо ownCloud.betutorial.ru ваш домен ownCloud:</p><p><i>Обратите внимание! Caddy выпустит SSL-сертификат на домен автоматически.</i></p><p>Все запросы к домену будут проксироваться в контейнер ownCloud_server.</p><p>На этом настройка конфигурационных файлов завершена, можно запускать контейнеры:</p><p>Потребуется несколько минут, чтобы docker загрузил образ и развернул контейнеры.</p><p>После запуска перейдите по домену, чтобы проверить работу хранилища:</p><figure><img src="https://media.tproger.ru/user-uploads/105012/2026-04-29/934faf67-2939-40f3-840e-88af18ebfde3.webp" alt="" /></figure><p>Выполните вход со стандартными доступами.</p><p><i>Обратите внимание! Если ownCloud недоступен или вы получаете ошибку при входе со стандартными доступами, проверьте корректность конфигурационных файлов. После внесения изменений перезапустите контейнеры.</i></p><p>После входа вы попадете на главную страницу ownCloud. Перед началом работы мы крайне рекомендуем сменить стандартный пароль администратора. Сделать это можно, нажав на кнопку с именем пользователя в верхней правой части страницы и открыв раздел настроек.</p><figure><img src="https://media.tproger.ru/user-uploads/105012/2026-04-29/9f93c25a-3922-4223-b3bd-b45aaaf61edf.webp" alt="" /></figure><p>Теперь проверим работу объектного хранилища – перейдем на главную страницу и загрузим файлы:</p><figure><img src="https://media.tproger.ru/user-uploads/105012/2026-04-29/51a6e16f-ee9b-4e96-9a5c-7ef765f62286.webp" alt="" /></figure><p>Файлы также появились и в объектном хранилище:</p><figure><img src="https://media.tproger.ru/user-uploads/105012/2026-04-29/16e65ad7-28f9-462c-9af6-990d2cf3c878.webp" alt="" /></figure><p><i>Обратите внимание! Файлы, которые вы удалите в ownCloud, будут перемещены в корзину и останутся в S3. Для их полного удаления очистите корзину ownCloud.</i></p><p>Чтобы делиться паролями с новыми пользователями, необходимо настроить отправку почты в ownCloud, сделать это можно в разделе Settings&gt;General.</p><p>В нашем примере мы настроим отправку через SMTP:</p><figure><img src="https://media.tproger.ru/user-uploads/105012/2026-04-29/45e39a41-4bb1-4d19-9bfe-1e1db3afc4a6.webp" alt="" /></figure><p>После указания данных введите тестовый email и нажмите “Send email”. Если отправка успешна, вы получите уведомление об этом:</p><figure><img src="https://media.tproger.ru/user-uploads/105012/2026-04-29/5a22786d-3995-4e86-9b48-0a17363c8a18.webp" alt="" /></figure><p>А на почтовый ящик поступит письмо:</p><figure><img src="https://media.tproger.ru/user-uploads/105012/2026-04-29/f4043b49-9a17-42ed-a627-34502c2a16e8.webp" alt="" /></figure><p>На этом настройка завершена – можно начинать работать с файлами, используя связку ownCloud и S3.</p><h2>Заключение</h2><p>Если вы ловите себя на мысли, что данных стало настолько много, что поиск нужного файла порой происходит дольше, чем работа с ним (особенно если одни файлы хранятся на почте или в мессенджере, а другие – на ноутбуке или флешке), облачное хранилище может вам помочь.</p><p>Подобное ПО пригодится как для личных целей, так и для бизнеса – недаром в 2025 году в нашей стране был <a href="https://www.kommersant.ru/doc/8178724">зафиксирован</a> рост интереса крупного и среднего бизнеса к технологии облачного хранилища.</p><p>Надеюсь, эта статья была для вас полезна, а облачные хранилища помогут сделать ежедневную работу комфортнее.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как я написал E2EE-мессенджер на Spring Boot и WebCrypto — и почему сервер не видит сообщения</title>
      <link>https://tproger.ru/articles/kak-ya-napisal-e2ee-messendzher-na-spring-boot-i-webcrypto-i-poch</link>
      <comments>https://tproger.ru/articles/kak-ya-napisal-e2ee-messendzher-na-spring-boot-i-webcrypto-i-poch?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Василенков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-ya-napisal-e2ee-messendzher-na-spring-boot-i-webcrypto-i-poch</guid>
      <description><![CDATA[<p>Разбор архитектуры E2EE-мессенджера на Spring Boot 3, React и WebCrypto: X3DH, symmetric ratchet, AES-GCM, WebSocket, multi-device и ограничения реализации.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-ya-napisal-e2ee-messendzher-na-spring-boot-i-webcrypto-i-poch">Как я написал E2EE-мессенджер на Spring Boot и WebCrypto — и почему сервер не видит сообщения</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Android]]></category>
      <category><![CDATA[Stack Overflow]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Опрос]]></category>
      <category><![CDATA[IDE]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Tor]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[HR]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[MIT]]></category>
      <category><![CDATA[Алиса]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Мессенджер]]></category>
      <category><![CDATA[5g]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Взлом]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[1C]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Grafana]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 05 May 2026 05:35:02 GMT</pubDate>
      <content:encoded><![CDATA[<figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/240e412b-4307-49f9-b24f-bde7e0a7fd9a.webp" alt="" /></figure><p>Я Java-разработчик и в основном работаю с backend: Spring Boot, базы данных, интеграции, авторизация, WebSocket — всё то, что обычно находится за интерфейсом.</p><p>В какой-то момент я поймал себя на мысли: я каждый день пользуюсь мессенджерами, но плохо понимаю, как они устроены внутри. Окей, JWT, WebSocket, PostgreSQL, Redis — это понятно. Но что технически означает фраза "end-to-end encryption"? Как сервер доставляет сообщения, если он не должен их читать? Где живут ключи? Что хранится в базе? Что происходит, если у пользователя два устройства?</p><p>Решил разобраться через практику. Написал мессенджер с нуля. Назвал Chaos Messenger.</p><p>Сразу честно: криптографическую часть я изучал вместе с Claude и ChatGPT — читал спецификации X3DH и Double Ratchet, разбирал примеры, задавал вопросы, пока не сложилась цельная картина. Frontend тоже делался с активной помощью ChatGPT: я backend-разработчик, React для меня не основная среда. Но архитектура, backend, интеграция WebCrypto, модель конвертов, хранение сообщений и принципиальные решения — мои.</p><p>Для меня AI здесь был не заменой понимания, а инструментом — примерно как документация, Stack Overflow и ревью коллег. Без понимания threat model и архитектуры такой проект всё равно не собрать.</p><p>В статье расскажу, как работает E2EE изнутри: как устанавливается сессия через X3DH, как каждое сообщение получает отдельный ключ через Symmetric Ratchet, почему сервер хранит только зашифрованные конверты, и какие ошибки я допустил по дороге.</p><p>Стек: Spring Boot 3, React 18, WebCrypto API, PostgreSQL, Redis, WebSocket/STOMP, Prometheus, Grafana.</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/4b028f37-2de1-46ea-a247-567f74fb3081.webp" alt="" /></figure><h2>Важная оговорка про web-E2EE</h2><p>Когда я говорю, что сервер не может прочитать сообщения, я имею в виду backend, базу данных, WebSocket-слой и уже сохранённые ciphertext-конверты. У них нет ключей и plaintext.</p><p>Но у web-E2EE есть отдельная проблема: frontend-код тоже приходит с сервера. Теоретически скомпрометированный сервер может отдать изменённый JavaScript, который украдёт ключи или plaintext до шифрования. Это ограничение не конкретно моего проекта, а браузерной модели в целом.</p><p>Поэтому корректная формулировка такая: backend не получает ключи и не может расшифровать уже переданные или сохранённые сообщения. Защита от подмены клиентского кода — отдельный слой безопасности: подпись сборок, независимая верификация клиента, desktop/mobile-приложения, reproducible builds.</p><h2>Почему обычный подход не работает</h2><p>Большинство "мессенджеров" на GitHub выглядят примерно так:</p><p>Сервер знает всё. Видит каждое сообщение. Если БД утекла — утекла вся переписка. Если сервер взломали — читай что хочешь. Если завтра компания решит продать данные — технически ничего не мешает.</p><p>E2EE решает это радикально: backend не получает ключи и не хранит plaintext. Сообщение шифруется на устройстве отправителя до отправки в сеть, а расшифровывается только на устройстве получателя.</p><p>Это уже не вопрос политики конфиденциальности в стиле "мы обещаем не читать". Это архитектурное ограничение: если у сервера нет ключа, он не может превратить ciphertext обратно в текст.</p><p>Звучит как магия. На самом деле — два протокола и немного WebCrypto.</p><h2>Главная идея: конверты</h2><p>Представь что Алиса хочет написать Бобу. Вместо того чтобы положить письмо на стол и надеяться что никто не прочитает — она кладёт его в запечатанный конверт. Конверт может открыть только Боб своим ключом. Сервер просто передаёт конверт не заглядывая внутрь.</p><p>Именно так это работает в коде. В базе данных у меня это выглядит так:</p><p>Когда я впервые увидел</p><p>в своей БД вместо текста — стало понятно, что модель наконец работает правильно: сервер создал сообщение, доставил его, сохранил метаданные, но так и не узнал содержимое.</p><p>А вот что сервер возвращает при запросе списка чатов через API:</p><p>Не</p><p>. Не</p><p>. Буквально</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/b466ad91-20da-4e64-8b78-f6e08d2851d5.webp" alt="" /></figure><p>(DevTools → Network → ответ API с</p><p>)</p><h2>Откуда берутся ключи: X3DH</h2><p>Главный вопрос: как Алиса и Боб получают общий секрет, если они никогда раньше не общались? И как сделать это так, чтобы сервер только помог передать публичные данные, но сам не смог вычислить итоговый ключ?</p><p>Для этого используется X3DH — Extended Triple Diffie-Hellman, протокол из экосистемы Signal. Его задача — установить общий секрет между двумя устройствами, используя долгосрочные и временные ключи.</p><h2>Что хранится на сервере</h2><p>Когда пользователь регистрирует устройство, он загружает на сервер пакет публичных ключей:</p><p>На сервер уходят только публичные части. Приватные ключи сериализуются и хранятся локально в браузере — и никогда не покидают устройство в сеть.</p><p>Здесь важно сказать честно: хранение приватных ключей в</p><p>Более строгий вариант — использовать Web Crypto API с</p><p>, чтобы приватный ключ жил внутри браузерного crypto runtime и его нельзя было экспортировать в байты. Но у этого подхода есть практическая сложность: ключи нужно переживать между перезагрузками страницы, синхронизировать с IndexedDB, аккуратно восстанавливать состояние устройства и не сломать UX.</p><p>В браузерных E2EE-приложениях обычно приходится выбирать между несколькими вариантами:</p><ul><li>Сериализуемые ключи в localStorage или IndexedDB — проще реализовать, но нужно очень серьёзно относиться к XSS и целостности frontend-кода.</li><li>extractable: false + IndexedDB — безопаснее, но сложнее в реализации и восстановлении состояния.</li><li>Нативное secure storage вроде Android Keystore или iOS Secure Enclave — лучший вариант для мобильных клиентов, но он недоступен обычному web-приложению.</li></ul><p>В текущей версии Chaos Messenger используется первый вариант. Это осознанный компромисс для pet/open-source проекта и удобного запуска в браузере. Переход на non-extractable ключи и более строгую модель хранения стоит в roadmap.</p><p>Ключевой момент: backend всё равно не получает приватные ключи и не может расшифровать сохранённые ciphertext-конверты. Но защита ключей на клиенте — отдельная задача, и её нельзя честно замалчивать.</p><h2>Установка сессии</h2><p>Когда Алиса открывает переписку с Бобом впервые, происходит следующее:</p><p>В классическом X3DH четвёртая DH-операция с one-time prekey опциональна: она выполняется, если сервер выдал доступный OPK получателя. В моей реализации устройство публикует набор one-time prekeys при регистрации, поэтому первое сообщение обычно использует DH4. Если OPK закончились, сессию всё равно можно установить через остальные DH-компоненты, но это уже менее сильный вариант.</p><p>Боб, получив конверт с эфемерным публичным ключом Алисы, повторяет те же операции со своими приватными ключами и получает тот же самый</p><p>. Математика симметрична.</p><p>Сервер в этот момент видит только публичные ключи и зашифрованный конверт. Он помогает устройствам найти друг друга, но не участвует в вычислении секрета.</p><p>Получить</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/e7bbacf3-4fd2-4710-863a-2c710b1f6759.webp" alt="" /></figure><h2>Как шифруется каждое сообщение: Symmetric Ratchet</h2><p>X3DH даёт нам стартовый</p><p>. Но использовать один и тот же ключ для всех сообщений — плохая идея. Если использовать один ключ для всей переписки, компрометация этого ключа сразу открывает весь поток сообщений.</p><p>Решение — симметричный ratchet. После каждого сообщения цепочка ключей продвигается вперёд:</p><p>Визуально это выглядит так:</p><p>используется для шифрования одного сообщения через AES-GCM, после чего уничтожается. Если атакующий компрометирует</p><p>— он прочитает только второе сообщение.</p><p>В рамках такой симметричной цепочки это даёт forward secrecy назад по цепочке: зная текущий или отдельный</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/939a74f7-60c5-4816-85f0-05b5afdc9845.webp" alt="" /><figcaption>(диаграмма схемы chainKey → messageKey)</figcaption></figure><p>Само шифрование сообщения:</p><p>А вот что уходит на сервер — живой пример из DevTools:</p><p>Сервер получает</p><p>и</p><p>. Расшифровать без</p><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/b5e24cda-6fd9-4b15-9f27-3eb9b4f843b0.webp" alt="" /></figure><h2>Важная оговорка: это ещё не полный Double Ratchet</h2><p>В этом проекте реализован Symmetric Ratchet — цепочка, где из</p><p>для каждого сообщения выводится отдельный</p><p>Это защищает прошлые сообщения: если атакующий узнает текущий ключ или отдельный</p><p>, он не сможет откатить HMAC назад и получить старые ключи.</p><p>Но это не полный Double Ratchet из Signal Protocol.</p><p>В полном Double Ratchet есть ещё DH ratchet step: стороны периодически выполняют новый Diffie-Hellman обмен и обновляют root key. Это даёт break-in recovery — возможность восстановить безопасность будущих сообщений после компрометации части состояния.</p><p>В моей реализации DH ratchet step пока нет. Если атакующий получит актуальное состояние сессии на устройстве и сможет продолжать его читать, он сможет расшифровывать будущие сообщения до переустановки сессии. Это честное ограничение текущей версии, и оно стоит первым пунктом в roadmap.</p><h2>Мультиустройство: один пользователь, несколько конвертов</h2><p>Первый неочевидный момент: в E2EE сообщение адресуется не просто пользователю, а конкретным устройствам пользователя.</p><p>Если у Боба два устройства — телефон и ноутбук — нужен отдельный encrypted envelope для каждого устройства. Сервер не может взять один конверт, расшифровать его и "переупаковать" для второго устройства: у него нет ключей и он не знает plaintext.</p><p>Значит при отправке сообщения нужно зашифровать его отдельно для каждого устройства каждого участника чата.</p><p>Для чата где у каждого по 2 устройства — 4 конверта на одно сообщение. Для группы из 10 человек — потенциально 20 конвертов. Это нормально, это цена безопасности.</p><h2>Сервер: хранение и доставка конвертов</h2><p>На сервере сообщение создаётся с контентом</p><p>, а конверты сохраняются отдельно:</p><p>После сохранения — fanout по WebSocket. Каждое устройство получает свой конверт и только его:</p><p>Это важное отличие от обычного WebSocket-чата. В обычном чате сервер рассылает одно и то же событие всем участникам. В E2EE-чате сервер рассылает разные события разным устройствам: payload для каждого устройства содержит свой</p><p>Топик</p><p>— строго персональный. Устройство А не получает конверт устройства Б. Никакого broadcast — только адресная доставка.</p><h2>Архитектура целиком</h2><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/de0e3257-d893-459f-86ed-7ed8eace5d56.webp" alt="" /></figure><h2>Баг который долго не замечал</h2><p>В панели чатов показывается превью последнего сообщения. Я реализовал это через</p><p>Запускаю — в списке чатов у всех написано</p><p>.</p><p>Конечно. Сервер же не знает что там написано.</p><p>Я полчаса думал как решить это на сервере. Потом дошло: нельзя решить это на сервере — у него нет ключей. Решение только на клиенте.</p><p>После того как пользователь открыл чат и сообщения расшифровались — кешируем последнее в памяти:</p><p>Это хороший пример того, как E2EE меняет привычное мышление backend-разработчика. В обычном приложении preview — это поле в SQL-запросе. В E2EE-приложении preview — это локальное клиентское состояние, потому что только клиент видел plaintext.</p><p>Простое решение. Но чтобы к нему прийти нужно было полностью принять идею что сервер здесь просто не при делах — и перестать пытаться решить задачу на его стороне.</p><h2>Rate limiting: дыра которую легко не заметить</h2><p>Эндпоинт</p><p>отправляет SMS с кодом. Без защиты любой скрипт может дёргать его тысячи раз — это называется SMS pumping fraud, SMS стоят реальных денег.</p><p>Redis у нас уже был для хранения онлайн-статусов. Добавил rate limiting поверх него:</p><p>При превышении — HTTP 429 с заголовком</p><p>. Клиент знает через сколько секунд можно повторить.</p><p>Важный нюанс: в текущей реализации, если Redis недоступен, сервис не блокирует авторизацию полностью. Для pet-проекта это приемлемый компромисс: лучше рискнуть одним лишним SMS, чем положить вход в приложение.</p><p>В production я бы сделал строже: fallback in-memory лимит на инстанс, отдельные лимиты по IP и телефону, антифрод-логику и алерты на всплески отправки кодов.</p><h2>Авторизация WebSocket</h2><p>Отдельная история — авторизация WebSocket соединений. HTTP-эндпоинты защищены Spring Security автоматически, но WebSocket — другое дело. STOMP-соединение устанавливается один раз, и нужно проверять JWT при каждом подключении.</p><p>Отдельно важно не только проверить JWT, но и связать WebSocket-соединение с конкретным устройством. Пользователь может быть один, но устройств у него несколько, а encrypted envelope адресован именно</p><p>Поэтому при подключении я проверяю не только токен, но и</p><p>: устройство должно быть зарегистрировано и принадлежать текущему пользователю. Иначе легко случайно превратить per-device E2EE-доставку обратно в обычный broadcast по пользователю.</p><h2>Что получилось — живые скрины</h2><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/1fdfa006-f5c6-4731-a40f-50559be8832d.webp" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/137649/2026-04-29/c99164a5-021c-49ac-bcc4-3e38f9cd1c31.webp" alt="" /></figure><p>Что реализовано:</p><ul><li>E2EE-модель с per-device encrypted envelopes</li><li>X3DH session setup + Symmetric Ratchet + AES-GCM</li><li>Мультиустройство</li><li>Личные и групповые чаты</li><li>Realtime доставка через WebSocket/STOMP</li><li>Статусы SENT → DELIVERED → READ</li><li>Редактирование и soft delete сообщений</li><li>Online presence, typing indicator</li><li>Фото-вложения</li><li>Поиск пользователей</li><li>Rate limiting на SMS через Redis</li><li>Prometheus метрики + Grafana дашборд</li><li>Swagger UI с JWT авторизацией</li><li>24 backend-теста на Testcontainers, 12 frontend на Vitest, E2E на Playwright</li><li>GitHub Actions CI</li></ul><p>Что ещё не сделано:</p><ul><li>Полный Double Ratchet с DH ratchet step и break-in recovery</li><li>Ротация signed prekey и аккуратное пополнение one-time prekeys</li><li>Более строгая модель хранения приватных ключей на клиенте: non-extractable CryptoKey + IndexedDB</li><li>Защита от подмены frontend-кода: подпись сборок, независимая верификация клиента, reproducible builds</li><li>Android-клиент с Android Keystore</li><li>Реальный SMS-провайдер вместо кода в backend-логах</li><li>Push-уведомления без утечки содержимого сообщений</li><li>Более строгая metadata-модель для групповых чатов</li></ul><h2>Главный инсайт</h2><p>E2EE — это архитектурное решение, а не библиотека.</p><p>Нельзя взять обычный Spring Boot чат и просто "включить шифрование". Нужно с самого начала проектировать систему так, чтобы backend не был участником доверенной зоны: он не должен получать plaintext, не должен иметь ключи и не должен уметь пересобирать сообщение из данных в базе.</p><p>Это меняет почти всё:</p><ul><li>структуру БД — вместо текста появляются encrypted envelopes</li><li>API — сервер отдаёт [encrypted], а не preview сообщения</li><li>WebSocket — доставка идёт не по пользователю, а по конкретному устройству</li><li>мультиустройство — одно сообщение превращается в несколько ciphertext-конвертов</li><li>frontend — становится полноценной криптографической частью системы, а не просто UI</li></ul><p>Второй инсайт: мессенджер — это не "чат с WebSocket". В E2EE-модели это система доставки зашифрованных конвертов с адресацией по устройствам. Как только это принимаешь, многие странные на первый взгляд решения становятся логичными.</p><h2>Репозиторий</h2><p>Код открыт: <a href="https://github.com/vaazhen/chaos-messenger">github.com/vaazhen/chaos-messenger</a></p><p>В репозитории есть README на русском и английском, диаграммы, скриншоты, security audit, Docker Compose и запуск одной командой.</p><p>Проект не претендует на уровень production-криптомессенджера вроде Signal. Это учебный и инженерный open-source прототип, цель которого — показать, как E2EE меняет архитектуру backend, frontend и realtime-доставки.</p><p>Если вы делали что-то похожее — особенно интересно сравнить подходы к ротации prekey-ов, хранению non-extractable ключей в браузере и реализации DH ratchet step. Вопросы и критика приветствуются.</p>]]></content:encoded>
    </item>
    <item>
      <title>honker добавляет в SQLite очередь задач и pub/sub без Redis</title>
      <link>https://tproger.ru/news/raswirenie-honker-vstroilo-v-sqlite-ochered-zadach-i-pub-sub</link>
      <comments>https://tproger.ru/news/raswirenie-honker-vstroilo-v-sqlite-ochered-zadach-i-pub-sub?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/raswirenie-honker-vstroilo-v-sqlite-ochered-zadach-i-pub-sub</guid>
      <description><![CDATA[<p>honker — SQLite-расширение с Postgres-подобной NOTIFY/LISTEN, очередью задач с retries и pub/sub. Всё в одном .db-файле, без отдельного Redis или Celery.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/raswirenie-honker-vstroilo-v-sqlite-ochered-zadach-i-pub-sub">honker добавляет в SQLite очередь задач и pub/sub без Redis</a>»</p>]]></description>
      <category><![CDATA[Новости]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 24 Apr 2026 11:30:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Очередь фоновых задач и pub/sub — классические аргументы, чтобы рядом с SQLite-приложением поставить Redis и Celery. Расширение <a href="https://github.com/russellromney/honker">honker</a> кладёт очередь, pub/sub и durable-стримы в тот же .db-файл, в котором уже лежат ваши данные, и коммитит их в одной транзакции с бизнес-записью. По сути автор переносит на SQLite привычную Postgres-связку NOTIFY/LISTEN — механизм, когда слушатели подписываются на именованный канал и получают события без опроса.</p><ul><li>SQLite-расширение на Rust плюс биндинги для Python, Node.js, Rust, Go, Ruby, Bun, Elixir и C++. Ставится через pip install honker или из любого SQLite-клиента через SELECT load_extension('honker') — новый язык подключается одной SQL-командой, без ожидания биндинга.</li><li>Три примитива в одном файле БД: notify() для мгновенных событий без истории, durable-стрим с оффсетами (не теряется при рестарте) и очередь задач с at-least-once-доставкой (минимум один раз — значит обработчик должен быть идемпотентным), retries и dead-letter-таблицей.</li><li>Бизнес-запись и постановка задачи коммитятся в одной транзакции SQLite — при rollback пропадают вместе. Это транзакционный outbox по умолчанию: события и данные либо оба попадают в БД, либо оба откатываются, без сторонних библиотек и диспетчеров.</li><li>Cross-process уведомления идут за единицы миллисекунд: воркеры не опрашивают очередь, а ловят любой коммит в БД через PRAGMA data_version.</li><li>Лицензия Apache 2.0, статус «экспериментальный» — API ещё может меняться, в продакшен тащить стоит с оглядкой.</li></ul><h2>Зачем это нужно, если есть Redis</h2><p>Обычный ответ на «в SQLite-приложении нужна очередь задач» — добавить Redis и Celery. Это работает, но в обмен вы получаете второй сервис: его приходится отдельно бэкапить, следить, чтобы запись в бизнес-таблицу и постановка задачи в очередь не разъехались при падении, и держать брокер в живом состоянии. Автор honker <a href="https://github.com/russellromney">Рассел Ромни</a> предлагает другой подход: если SQLite — основная база, очередь должна жить в том же файле.</p><p>В коде это выглядит так: INSERT INTO orders и queue.enqueue(...) коммитятся в одной транзакции. Rollback откатывает обе операции. Очередь — это просто строки в таблице с частичным индексом по состоянию, без дополнительного диспетчера или отдельной таблицы очередей. Это <a href="https://brandur.org/job-drain">классический паттерн transactional outbox</a> — когда события и бизнес-запись кладутся в одну транзакцию, — но по умолчанию и без сторонних библиотек.</p><h2>Как уведомления приходят за миллисекунду</h2><p>У SQLite нет сетевого протокола, поэтому серверного push быть не может — клиент должен сам читать. honker <a href="https://github.com/russellromney/honker#design">обходит это через</a> PRAGMA data_version — монотонный счётчик, который SQLite увеличивает на каждом коммите из любого соединения. Один поток в процессе опрашивает его каждую миллисекунду; сам запрос стоит единицы микросекунд, так что 1000 проверок в секунду — это миллисекунды CPU, а не проценты. Счётчик меняется в любом режиме журнала и виден между процессами, поэтому работает одинаково на Linux, macOS и Windows.</p><p>Когда счётчик сдвинулся, подписчики просыпаются и делают один короткий SELECT ... WHERE id &gt; last_seen по частичному индексу. Часть из них ничего полезного не найдёт — их канал не менялся. Автор сознательно оставляет эти лишние пробуждения: отфильтровать в SELECT дешевле, чем пропустить нужное событие. Итог — end-to-end задержка доставки между процессами по медиане одна–две миллисекунды на современном ноутбуке.</p><h2>Три примитива в одном файле БД</h2><p>Как выглядит минимальный пример на Python — постановка задачи в очередь атомарно с бизнес-записью:</p><p><strong>notify</strong> — мгновенный pub/sub без истории. Слушатели подключаются к db.listen("orders") и получают новые события с момента подписки. История до старта слушателя не воспроизводится — offline-подписчик пропустит удалённые записи. Полезно, когда важно низколатентное «что-то произошло» без гарантии доставки.</p><p><strong>stream</strong> — durable-pub/sub с гарантиями: события переживают рестарт воркера. Каждый именованный потребитель ведёт собственный offset в таблице _honker_stream_consumers, поэтому после рестарта продолжает с места остановки. Доставка at-least-once; оффсет можно сохранять автоматически каждые N событий или M секунд, а можно вручную через save_offset в той же транзакции, что и ваша запись, — тогда offset и бизнес-изменение либо оба закоммитятся, либо оба откатятся.</p><p><strong>queue</strong> — собственно очередь задач. Захват строки воркером — это UPDATE ... RETURNING по частичному индексу, подтверждение — один DELETE. Если воркер упал в середине обработки, взятая им задача считается протухшей по visibility-таймауту (по умолчанию пять минут на обработку) и возвращается в очередь — другой воркер её переподхватит. После трёх неудачных попыток задача уезжает в таблицу _honker_dead, которую претенденты не сканируют. Благодаря этому горячий путь не зависит от истории очереди.</p><h2>Что уже встроено</h2><p>Что honker умеет помимо трёх базовых примитивов:</p><ul><li>Cross-process pub/sub на одном файле БД.</li><li>Очередь с приоритетами, отложенными задачами, декларативными retries и экспоненциальным backoff.</li><li>Dead-letter-таблица для исчерпавших попытки задач.</li><li>Таймауты обработчиков, время жизни задач, именованные локи и rate-limiting.</li><li>Crontab-подобные периодические задачи с выбором лидера-планировщика.</li><li>Опциональное хранение результатов: enqueue возвращает id, воркер пишет ответ, вызывающий ждёт его через queue.wait_result(id).</li><li>Durable-стримы с per-consumer оффсетами и настраиваемым интервалом флаша.</li><li>Работа внутри соединения, которым владеет ORM: SQLAlchemy, SQLModel, Django, Drizzle, Kysely, sqlx, GORM, ActiveRecord, Ecto.</li></ul><p>В README явно сказано, чего в honker специально не будет: пайплайнов/цепочек/групп задач (как у Celery), multi-writer-репликации и оркестрации workflow через DAG. Автор берёт за образец Postgres-мир — <a href="https://github.com/timgit/pg-boss">pg-boss</a>, <a href="https://hexdocs.pm/oban/Oban.html">Oban</a>, <a href="https://www.postgresql.org/docs/current/sql-notify.html">pg_notify</a> — и SQLite-проект <a href="https://huey.readthedocs.io/en/latest/">Huey</a>.</p><h2>Где придётся остановиться</h2><p>Первое ограничение — архитектурное: SQLite рассчитан на один файл, одного писателя и один хост. Два сервера, пишущие в один .db через NFS, рискуют получить повреждённую базу. Если нужна репликация между машинами, вариантов два: либо шардировать по файлам (отдельная .db на клиента или инстанс сервиса), либо переходить на Postgres.</p><p>Рекомендуемый режим журнала — WAL (Write-Ahead Log, отдельный файл журнала рядом с базой): он даёт конкурентных читателей при одном писателе и эффективный батчинг fsync. Другие режимы работают, но теряется именно конкурентное чтение при записи. Корректность wake-сигнала от PRAGMA data_version от WAL не зависит.</p><p>Если у вас монорепа с SQLite, stand-alone-приложение или небольшой SaaS на одном-двух серверах — honker закрывает тот самый класс задач, ради которого обычно тянут Redis: очередь, pub/sub, периодические задачи, распределённые локи и rate-limiting. Если у вас уже кластер из десятка нод — это не для вас: SQLite упрётся в одного писателя и один хост. Код и документация — <a href="https://github.com/russellromney/honker">в репозитории на GitHub</a>, обсуждение <a href="https://news.ycombinator.com/item?id=47874647">собралось на Hacker News</a>, архитектуру <a href="https://simonwillison.net/2026/Apr/24/honker/">разобрал</a> Саймон Уиллисон у себя в блоге.</p>]]></content:encoded>
    </item>
    <item>
      <title>ИИ-агенты самостоятельно написали C-компилятор, способный собрать Linux</title>
      <link>https://tproger.ru/news/ii-agenty-samostoyatelno-napisali-c-kompilyator--sposobnyj-sobrat</link>
      <comments>https://tproger.ru/news/ii-agenty-samostoyatelno-napisali-c-kompilyator--sposobnyj-sobrat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/ii-agenty-samostoyatelno-napisali-c-kompilyator--sposobnyj-sobrat</guid>
      <description><![CDATA[<p>ИИ-агенты Anthropic самостоятельно создали C-компилятор на Rust, способный собирать Linux 6.9, показав пределы автономной разработки</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/ii-agenty-samostoyatelno-napisali-c-kompilyator--sposobnyj-sobrat">ИИ-агенты самостоятельно написали C-компилятор, способный собрать Linux</a>»</p>]]></description>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[Язык Си]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[ARM]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 06 Feb 2026 05:03:48 GMT</pubDate>
      <content:encoded><![CDATA[<p>Исследователь Anthropic Николас Карлини <a href="https://www.anthropic.com/engineering/building-c-compiler">рассказал</a> об эксперименте, в ходе которого группа ИИ-агентов без постоянного участия человека написала полноценный <b>C-компилятор</b>, способный собрать ядро Linux.</p><p>Компилятор написан на Rust, насчитывает <b>около 100 000 строк кода</b> и может собирать Linux 6.9 для архитектур x86, ARM и RISC-V.</p><h2>Как это вообще произошло</h2><p>В эксперименте использовалась модель <b>Claude Opus 4.6</b>, запущенная в режиме так называемых agent teams. Это подход, при котором несколько экземпляров ИИ работают параллельно над одним репозиторием и сами решают, какие задачи брать дальше.</p><p>Всего в проекте одновременно работали <b>16 агентов</b>, каждый из которых запускался в отдельном контейнере и синхронизировался через Git.</p><p>Чтобы агенты не мешали друг другу, они «блокировали» задачи с помощью файлов-локов — если один агент уже взялся за парсинг if, другой был вынужден выбрать другую часть компилятора.</p><p>Процесс длился почти две недели и включал <b>около 2000 сессий Claude Code</b>. Общая стоимость эксперимента составила <b>примерно $20 000</b>.</p><h2>Что умеет получившийся компилятор</h2><p>На выходе получился рабочий, хоть и экспериментальный инструмент:</p><ul><li>собирает Linux 6.9;</li><li>компилирует крупные проекты вроде SQLite, Redis, FFmpeg и QEMU;</li><li>проходит около 99% тестов из GCC torture test suite;</li><li>способен скомпилировать и запустить DOOM — неофициальный, но показательный бенчмарк.</li></ul><p>Важный момент: у ИИ не было доступа к интернету, то есть делалось в рамках имеющейся «базы знаний» модели.</p><h2>Где начинаются ограничения</h2><p>Несмотря на впечатляющий результат, компилятор далек от промышленного использования:</p><ul><li>нет собственного ассемблера и линковщика — они частично заимствуются у GCC;</li><li>кодовая база нестабильна: новые изменения часто ломают старые части;</li><li>16-битный x86-код (нужный для загрузки Linux) реализован «читерски» — через вызов GCC.</li></ul><p>Сами авторы также подчеркнули, что модель <b>уперлась в потолок своих возможностей</b> — дальнейшее развитие компилятора становится все менее предсказуемым.</p>]]></content:encoded>
    </item>
    <item>
      <title>Брокеры сообщений 2025: Kafka vs Pulsar vs RabbitMQ vs NATS + JetStream vs Redis Streams — когда что выбирать</title>
      <link>https://tproger.ru/articles/message-brokers-2025--kafka-vs-pulsar-vs-rabbitmq-vs-nats---jetstream-vs-redis-streams---kogda-chto-vybirat</link>
      <comments>https://tproger.ru/articles/message-brokers-2025--kafka-vs-pulsar-vs-rabbitmq-vs-nats---jetstream-vs-redis-streams---kogda-chto-vybirat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Ислам Виндижев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/message-brokers-2025--kafka-vs-pulsar-vs-rabbitmq-vs-nats---jetstream-vs-redis-streams---kogda-chto-vybirat</guid>
      <description><![CDATA[<p>В этой статье мы обсудим популярные брокеры сообщений, их характеристики и как выбрать правильный для своего проекта. Мы прикинем, как можно примерно оценить стоимость поддержки брокера и каких ресурсов они могут требовать на реальных проектах.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/message-brokers-2025--kafka-vs-pulsar-vs-rabbitmq-vs-nats---jetstream-vs-redis-streams---kogda-chto-vybirat">Брокеры сообщений 2025: Kafka vs Pulsar vs RabbitMQ vs NATS + JetStream vs Redis Streams — когда что выбирать</a>»</p>]]></description>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 24 Dec 2025 12:10:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Давным-давно от монолитных систем начали давно переходить к распределённым. Эта концепция позволяет строить более гибкие, отказоустойчивые и безопасные системы. Только компонентам систем понадобился способ «общения», который позволит по полной использовать преимущества распределённого подхода. Здесь на помощь инженерам пришли брокеры сообщений.</p><p>Брокер сообщений — это архитектурный паттерн и специальное ПО, которое принимает сообщения от разных компонентов системы и потом распределяет их по адресатам. Брокер также отвечает за надёжность доставки и сохранность сообщений в случае сбоев.</p><p>Как итог, брокеры позволяют сервисам общаться асинхронно, балансируют нагрузку между получателями сообщений, повышают гибкость системы и помогают интегрировать разные технологии (например, несколько микросервисов, написанных на разных языках).</p><p>В этой статье мы попробуем разобраться в какой ситуации выбрать определённый брокер и на какие параметры стоит обратить внимание. Параметров много и про каждый брокер можно написать отдельную статью, поэтому мы сосредоточимся на базовых характеристиках, а глубоко погружаться в особенности конкретного брокера вам придётся уже самостоятельно.</p><p>Цель статьи — сделать компактное сравнение и подсветить особенности разных брокеров, а не сделать подробный технический гайд. Ещё мы разберём как выбрать подходящий брокер и как прикинуть стоимость его поддержки.</p><p>Перед сравнением брокеров давайте разберём их характеристики. Их смысл очень важен для понимания темы.</p><p><b>Пропускная способность (throughput)</b> — количество сообщений, которое брокер может обрабатывать в секунду.</p><p>Нужно понимать, что пропускная способность в статье теоретическая и была получена по сути в «вакуумных», синтетических условиях. И всё равно теоретические показатели пропускной способности позволяют оценить разницу между брокерами вполне достоверно.</p><p><b>Задержка (latency)</b> — время от отправки сообщения до получения и обработки потребителем.</p><p>Так же как и с пропускной способностью в статье указаны более синтетические показатели. В реальной системе они могут отличаться в зависимости от разных настроек брокера и задержке сети.</p><p><b>Гарантии доставки (delivery guarantees)</b>  — определяют что произойдёт с сообщением при сетевых ошибках, падении отправителя, получателя или самого брокера: будет ли оно потеряно, продублировано или доставлено строго один раз. Соответственно, различают гарантии «at most once», «at least once», «exactly once».</p><p><b>Репликация (replication)</b> — механизм, который позволяет для надёжности хранить сообщения/данные в нескольких местах. Все брокеры так или иначе поддерживают репликацию, в статье указаны способы или принципы её реализации.</p><p><b>Сложность эксплуатации (operational difficulty)</b> — относительная оценка того, насколько сложно внедрить и поддерживать брокер на проекте.</p><p><b>Ресурсы</b> — вычислительные ресурсы, необходимые для работы брокера. В этой статье указаны ресурсы для одного простейшего и минимального production-узла с одним брокером.</p><p>Количество ресурсов для брокеров может значительно отличаться в зависимости от требований и размера проектов. Сначала мы обсудим базовые характеристики, реальные production-системы могут использовать значительно больше ресурсов.</p><h2>RabbitMQ</h2><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/39eeeecd-019d-4cec-8e63-7f88b6c6aa53.png" alt="" /></figure><p><a href="https://www.rabbitmq.com/">RabbitMQ</a> — самый старый из рассматриваемых, но до сих пор популярный программный брокер с поддержкой протокола AMQP (и не только, конечно). Изначально проект был задуман как масштабируемый брокер сообщений с поддержкой таких функций, как очереди сообщений, маршрутизация и долговременное хранение. Со временем клиентские библиотеки стали поддерживать конкурентность, а производительность повысилась. Позже апгрейд получили и очереди. Новые функции, такие как, например, quorum queues, были добавлены для обеспечения высокой доступности и сохранности данных за счёт репликации, основанной на алгоритме консенсуса Raft.</p><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/85cb1143-67cb-4da0-9faa-af553e75fe15.png" alt="" /></figure><p><b>Характеристики:</b></p><p>Пропускная способность: 50 000–100 000.</p><p>Задержка: 5–20 мс.</p><p>Гарантии доставки: «at least once».</p><p>Репликация: quorum queues (Raft).</p><p>Сложность эксплуатации: средняя.</p><p>Ресурсы: 4 ядра CPU, 4 Гб RAM, 4-8 Гб SSD, не разворачивать на одном сервере с сервисами, которые активно используют сетевой ввод/вывод.</p><p>RabbitMQ хорошо подойдёт для сложных сценариев маршрутизации сообщений, интеграции legacy-систем, которым нужен AMQP, а также для работы со сложными паттернами сообщений. Ещё один случай, когда его стоит выбрать — большая экспертиза команды в работе с RabbitMQ и брокерами с классической очередью.</p><h2>Kafka</h2><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/5d00f3e4-168a-4d79-b6ca-9a42c289c926.png" alt="" /></figure><p><a href="https://kafka.apache.org/">Apache Kafka</a> остаётся безусловным лидером для высоконагруженных сценариев стриминга событий и сейчас, по факту, промышленный стандарт. Пропускная способность — 500 000–1 000 000+ сообщений в секунду. Kafka доминирует в смысле горизонтального масштабирования, пропускной способности, репликации и отказоустойчивости.</p><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/e37991bc-6233-45ff-a7e4-032a3acaa19b.png" alt="" /></figure><p><b>Характеристики:</b></p><p>Пропускная способность: 500 000–1 000 000+ сообщений в секунду.</p><p>Задержка: 10-50 мс.</p><p>Гарантии доставки: «at least once», «exactly once».</p><p>Репликация: configurable replication factor (ISR).</p><p>Сложность эксплуатации: высокая.</p><p>Ресурсы: 4 ядра CPU, 4 Гб RAM, 8 Гб SSD.</p><p>Kafka — ваш выбор для высокопроизводительного стриминга событий с воспроизведением сообщений, обработки огромных объёмов данных, агрегации логов, а также масштабных, сложных распределённых систем. Например, Valve использует Kafka, а игры — прекрасный пример таких высоконагруженных систем.</p><h2>Redis Streams</h2><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/f5364b36-1f96-4208-9bef-8ac52ff5f918.png" alt="" /></figure><p><a href="https://redis.io/docs/latest/develop/data-types/streams/">Redis Streams</a> не вполне можно назвать брокером. По-хорошему, это структура данных, которая появилась в Redis 5.0. На её базе можно строить брокеры сообщений и это прекрасный выбор для систем, которые уже используют Redis как кэш и/или базу данных.</p><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/184c7791-113c-4088-aac7-681ce980a861.png" alt="" /></figure><p><b>Характеристики:</b></p><p>Пропускная способность: 100 000–500 000 сообщений в секунду.</p><p>Задержка: 1-10 мс.</p><p>Гарантии доставки: «at least once».</p><p>Репликация: Redis Cluster, Master-Slave.</p><p>Сложность эксплуатации: низкая.</p><p>Ресурсы: 4 ядра CPU, 8 Гб RAM, 8 Гб SSD.</p><p>Redis Streams подойдёт в первую очередь для систем, в которых уже есть Redis. Это оптимальный выбор для проектов, в которых нужна высокая производительность (за счёт in-memory операций) в реальном времени с относительно небольшим объёмом данных и нет требований к высокой пропускной способности.</p><h2>NATS + JetStream</h2><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/273be322-f357-42aa-a526-7b79c356fbef.png" alt="" /></figure><p><a href="https://nats.io/">NATS</a> зарекомендовал себя как легковесная, масштабируемая облачно-ориентированная система обмена сообщениями. Его как раз и проектировали для облачных сред и IoT-систем с большим количеством подключений. JetStream расширяет возможности базового NATS: добавляет сохранность сообщений и «exactly once» гарантию доставки. NATS набирает популярность и в последние годы активно завоёвывает рынок.</p><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/e3d7e942-24db-49b6-9a05-58057af1f656.png" alt="" /></figure><p><b>Характеристики:</b></p><p>Пропускная способность: 200 000–400 000 сообщений в секунду.</p><p>Задержка: 1-5 мс.</p><p>Гарантии доставки: «at most once», «at least once», «exactly once» (с JetStream).</p><p>Репликация: R3 clustering (Raft).</p><p>Сложность эксплуатации: низкая.</p><p>Ресурсы: 2 ядра CPU, 4 Гб RAM, 4 Гб SSD.</p><p>IoT-проекты с большим количеством элементов, облачные среды, высокая скорость работы в реальном времени, простота применения — если это факторы, которые важны, NATS — то, что нужно.</p><h2>Pulsar</h2><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/79f2f3f1-5994-49fa-8f84-3b603dc6a144.png" alt="" /></figure><p><a href="https://pulsar.apache.org/">Apache Pulsar</a> — распределённая платформа обмена сообщениями с унифицированной моделью стриминга и очередей. Проект растёт и его называют главным конкурентом Kafka. Pulsar изначально проектировался для крупномасштабных проектов и предлагает бесшовную встроенную георепликацию.</p><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/8f4534b7-8e00-461d-89a8-eb7e24e11118.png" alt="" /></figure><p><b>Характеристики:</b></p><p>Пропускная способность: 1 000 000–2 600 000 сообщений в секунду.</p><p>Задержка: 5–20 мс.</p><p>Гарантии доставки: «at most once», «at least once», «exactly once».</p><p>Репликация: георепликация, слои хранилищ данных.</p><p>Сложность эксплуатации: высокая.</p><p>Ресурсы: 4 ядра CPU, 4 Гб RAM, 8 Гб SSD.</p><p>Pulsar подойдёт для систем, которые обрабатывают большие объёмы данных и при этом требуют универсальности очередей. Георепликация и высокая масштабируемость также делают его привлекательным выбором для определённых сценариев.</p><h2>Сравнение характеристик брокеров сообщений</h2><p>Куда же без краткой и наглядной таблицы. Напомню, что реальные характеристики могут значительно отличаться из-за настроек, условий и других факторов.</p><figure><img src="https://media.tproger.ru/user-uploads/99854/2025-12-26/e473f9a5-07d1-4645-9f50-85a54a3a0a63.jpeg" alt="" /></figure><h2>Как выбрать брокер сообщений и сколько стоит его поддерживать?</h2><p>Выбор брокера сообщений зависит, конечно, от требований конкретного проекта и его особенностей. Вы могли заметить, что у каждого брокера есть некоторая ниша, в которой его используют активнее. Например, как NATS с IoT-решениями, а Kafka — при огромных нагрузках. На нишу в первую очередь и можно сориентироваться при выборе.</p><p>Второй момент — технические требования к системе. Например, какое количество сообщений нужно обрабатывать и какого они будут размера — это о пропускной способности. Если вы знаете размер сообщений в своей системе и знаете частоту обмена сообщениями, то прикинуть это число достаточно просто.</p><p>Отталкиваясь от других требований к системе по характеристикам брокеров, которые мы уже обсудили выше, можно понять, какой подойдёт именно в вашем случае.</p><p>Интересное начнётся когда мы попробуем посчитать сколько будет стоить внедрение и обслуживание. Эта цифра складывается из трудозатрат и расходов на вычислительные ресурсы, читайте — сервера.</p><p>Давайте начнём с серверов. Я уже говорил, что мы попробуем посмотреть на цифры, которые могут быть на реальных проектах. В таблице ниже ресурсы, которые могут требовать брокеры сообщений. Я не буду вдаваться в подробности сценариев, скажу только, что в каждой категории проектов сценарий одинаковый и можно примерно сравнить порядок ресурсов, который могут потреблять разные брокеры. При этом нужно не забывать, что закладывается запас по ресурсам порядка 30% для пиковых нагрузок, внезапной необходимости масштабирования и других непредвиденных ситуаций.</p><figure><img src="https://media.tproger.ru/user-uploads/133609/2025-12-17/a0fd51be-66b3-4208-b9db-febfea2d724f.png" alt="" /></figure><p>Для базового уровня надёжности разворачивается хотя бы 2-3 экземпляра брокера. Если растут нагрузки, размер сообщений, требования к длительности их хранения, то систему пора масштабировать, а вместе с этим вырастет и объём потребляемых ресурсов.</p><p>В таблице довольно условное разделение, оно приведено для наглядности порядка используемых вычислительных мощностей.</p><p>Давайте для примера попробуем рассчитать расходы на оборудование для самого простого сценария из таблицы у Kafka.</p><p>Для простоты я просто выберу VDS у одного из популярных провайдеров. Конфигурация одного VDS с характеристиками «8 ядер, 32 Гб RAM, 20 Гб SSD» обойдётся порядка 10 тысяч рублей в месяц. Нам нужно три, соответственно, получится 30 тысяч в месяц. Это очень поверхностный рассчёт, но схема в целом всегда такая.</p><p>VDS я выбираю просто для простоты и наглядности рассчёта. Вариантов аренды или покупки вычислительных мощностей много: облако, bare metal, в конце концов свои сервера on-premise. У них тоже есть свои плюсы и минусы, но это не предмет данной статьи.</p><p>Трудозатраты — тоже большая часть расходов, о которой иногда совсем забывают. Для внедрения брокера систему с ним нужно спроектировать, его нужно внедрить, настроить, протестировать, обслуживать во время эксплуатации.</p><p>Если перевести часы работы специалистов в деньги, и прибавить их к стоимости аренды ресурсов, то получится итоговая стоимость поддержки брокера сообщений.</p><h2>Итоги: выбирайте инструмент под задачу</h2><p>Сейчас брокеры сообщений не особенно стараются толкаться локтями, посматривают друг на друга и стараются занять свою нишу. Выбор брокера сообщений зависит от специфических требований вашего проекта, поэтому выбирать брокер стоит опираясь на конкретный проект, требования и доступные вам ресурсы.</p><p>В этой статье мы, конечно, не смогли охватить все характеристики и нюансы брокеров сообщений. Изучать их, читать документацию, разворачивать и настраивать придётся уже вам. Надеюсь, что эта статья помогла вам немного разобраться в том, что такое брокеры, какие у них есть характеристики и как подступиться к выбору и оценке подходящего именно для вас.</p><p><i>Если вас заинтересовала какая-то тема, которая связана со статьёй — пишите в комментариях.</i></p>]]></content:encoded>
    </item>
    <item>
      <title>Вышел Bun 1.3: full-stack рантайм, поддержка Redis и новый SQL API. Разобрались, что еще нового</title>
      <link>https://tproger.ru/news/vywel-bun-1-3--full-stack-rantajm--podderzhka-redis-i-novyj-sql-api--razobralis--chto-eshhe-novogo</link>
      <comments>https://tproger.ru/news/vywel-bun-1-3--full-stack-rantajm--podderzhka-redis-i-novyj-sql-api--razobralis--chto-eshhe-novogo?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/vywel-bun-1-3--full-stack-rantajm--podderzhka-redis-i-novyj-sql-api--razobralis--chto-eshhe-novogo</guid>
      <description><![CDATA[<p>Bun 1.3 стал full-stack рантаймом с Redis, SQL API, поддержкой MySQL и PostgreSQL, новым тест-раннером и ускорением сборки до 2,5 раз</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/vywel-bun-1-3--full-stack-rantajm--podderzhka-redis-i-novyj-sql-api--razobralis--chto-eshhe-novogo">Вышел Bun 1.3: full-stack рантайм, поддержка Redis и новый SQL API. Разобрались, что еще нового</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 13 Oct 2025 04:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Команда <b>Oven</b> <a href="https://bun.com/blog/bun-v1.3">представила</a> <b>Bun 1.3</b> — крупнейший релиз в истории JavaScript-рантайма.</p><p>Теперь Bun официально позиционируется как <b>full-stack платформа</b> для фронтенда и бэкенда. И она объединяет сервер, сборщик, менеджер пакетов вместе с тест-раннером в одном инструменте.</p><p>В новую версию добавлены десятки ключевых функций: встроенные клиенты для <b>Redis</b>, <b>MySQL</b>, <b>PostgreSQL</b> и <b>SQLite</b>, единый <b>SQL API</b>, улучшенные <b>WebSocket-модули</b>, переработанный <b>тест-раннер</b> и поддержка <b>VSCode Test Explorer</b>.</p><h2>Full-stack по-умному</h2><p>Главное новшество — режим <b>full-stack Bun.serve()</b> с поддержкой роутинга, cookies и WebSockets.</p><p>Теперь фронтенд и бэкенд можно запускать в одном процессе без проблем с CORS, а приложение собрать в <b>единый исполняемый файл</b> с помощью bun build --compile.</p><p>Разработчики могут напрямую импортировать HTML, запускать React-приложения с хот-перезагрузкой и собирать проект одной командой bun init --react. По данным команды, скомпилированные React-приложения в Bun работают <b>до 1,8 раза быстрее, чем через nginx</b>.</p><h2>Новый SQL и встроенный Redis</h2><p>Bun 1.3 представил унифицированный <b>Bun.SQL API</b> — теперь один и тот же код работает с MySQL, PostgreSQL, SQLite и MariaDB. Добавлен хелпер sql.array() для работы с массивами в PostgreSQL, улучшена поддержка JSON и Unix-сокетов.</p><p>Кроме того, в рантайм встроен <b>Redis-клиент</b>, который поддерживает 66 команд, автоматическое переподключение, очереди сообщений и Pub/Sub. По данным разработчиков, он <b>значительно быстрее ioredis</b>, а поддержка кластеров и Lua-скриптов появится в будущих релизах.</p><h2>Новые возможности</h2><p>Среди прочих улучшений — <b>Zstandard-сжатие</b>, нативная поддержка <b>YAML</b>, API для безопасного хранения секретов (<b>Bun.secrets</b>), и серьезный прирост производительности: операции с криптографией ускорены <b>до 400х</b>, установка пакетов — <b>до 2,5х</b>.</p><p>Также обновлен менеджер пакетов с <b>интерактивным bun update</b>, изолированными установками и API для проверки безопасности зависимостей.</p><h2>Почему это важно</h2><p>Bun 1.3 превращает экспериментальный рантайм в <b>полноценную платформу для веб-разработки</b>, способную заменить Node.js, Vite и Redis-CLI одновременно.</p><p>Разработчики называют релиз «началом новой эпохи», цель которой — сделать Bun лучшим способом писать и развертывать JavaScript-приложения.</p>]]></content:encoded>
    </item>
    <item>
      <title>Redis предупреждает о критической уязвимости RediShell: под угрозой сотни тысяч серверов по всему миру</title>
      <link>https://tproger.ru/news/redis-preduprezhdaet-o-kriticheskoj-uyazvimosti-redishell--pod-ugrozoj-sotni-tysyach-serverov-po-vsemu-miru</link>
      <comments>https://tproger.ru/news/redis-preduprezhdaet-o-kriticheskoj-uyazvimosti-redishell--pod-ugrozoj-sotni-tysyach-serverov-po-vsemu-miru?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Даровская Маша]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/redis-preduprezhdaet-o-kriticheskoj-uyazvimosti-redishell--pod-ugrozoj-sotni-tysyach-serverov-po-vsemu-miru</guid>
      <description><![CDATA[<p>Redis выпустил экстренные исправления для уязвимости CVE-2025-49844, которая позволяет удалённое выполнение кода и ставит под угрозу сотни тысяч серверов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/redis-preduprezhdaet-o-kriticheskoj-uyazvimosti-redishell--pod-ugrozoj-sotni-tysyach-serverov-po-vsemu-miru">Redis предупреждает о критической уязвимости RediShell: под угрозой сотни тысяч серверов по всему миру</a>»</p>]]></description>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Информационная безопасность]]></category>
      <category><![CDATA[Уязвимость]]></category>
      <category><![CDATA[Кибербезопасность]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 08 Oct 2025 12:09:49 GMT</pubDate>
      <content:encoded><![CDATA[<p>6 октября 2025 года команда безопасности Redis выпустила экстренные обновления для устранения уязвимости максимальной степени опасности — CVE-2025-49844, получившей оценку CVSS 10.0. Ошибка позволяет злоумышленникам выполнять произвольный код на серверах Redis и получать полный контроль над системой.</p><h2>Что произошло</h2><p>Redis — это высокопроизводительное хранилище данных с открытым исходным кодом, применяемое как кэш, брокер сообщений и база данных. Оно используется примерно в 75% облачных инфраструктур по всему миру.</p><p>Новая уязвимость, прозванная исследователями RediShell, <a href="https://cwe.mitre.org/data/definitions/416.html">связана</a> с 13-летней ошибкой типа use-after-free в интерпретаторе Lua. При успешной эксплуатации она позволяет аутентифицированному злоумышленнику выйти за пределы «песочницы» Lua, установить обратное соединение и добиться удалённого выполнения кода (RCE) на хосте Redis.</p><h2>Потенциальные последствия</h2><p>После компрометации сервера атакующий может:</p><ul><li>похищать учётные данные и конфиденциальные данные из памяти Redis;</li><li>внедрять вредоносное ПО и криптомайнеры;</li><li>перемещаться по внутренней сети жертвы;</li><li>использовать полученные токены доступа для атак на другие облачные сервисы.</li></ul><p>Исследователи из Wiz, впервые представившие уязвимость на конференции Pwn2Own Berlin 2025, <a href="https://www.bleepingcomputer.com/news/security/hackers-exploit-vmware-esxi-microsoft-sharepoint-zero-days-at-pwn2own/">отметили</a>, что она «предоставляет злоумышленнику полный доступ к хостовой системе, включая возможность стирания, шифрования и перехвата данных».</p><h2>Масштаб угрозы</h2><p>По данным Wiz, в сети обнаружено около 330 000 экземпляров Redis, из которых не менее 60 000 не требуют аутентификации, что делает их уязвимыми к немедленной атаке.</p><p>Уязвимость затрагивает все версии Redis, включая OSS/CE и Stack-редакции. Исправления уже доступны в версиях:</p><ul><li>7.22.2-12 и выше,</li><li>7.8.6-207 и выше,</li><li>7.4.6-272 и выше,</li><li>7.2.4-138 и выше,</li><li>6.4.2-131 и выше,<br />а также в OSS/CE релизах 8.2.2+, 8.0.4+, 7.4.6+, 7.2.11+ и Stack-релизах 7.4.0-v7+ и 7.2.0-v19+.</li></ul><h2>Рекомендации для администраторов</h2><p>Redis и Wiz настоятельно советуют:</p><ul><li>немедленно обновить все экземпляры Redis;</li><li>включить аутентификацию и ограничить доступ доверенным сетям;</li><li>отключить выполнение Lua-скриптов, если оно не требуется;</li><li>запускать Redis без прав root;</li><li>включить журналирование и мониторинг активности;</li><li>использовать сетевые фильтры, VPN и брандмауэры для защиты.</li></ul><p>Исследователи представили <a href="https://youtu.be/yOBt8irvao0">видео об уязвимости</a>, где можно посмотреть подробности о возможных угрозах и мерах безопасности.</p><h2>Исторический контекст</h2><p>Redis уже не раз становился целью атак. В 2024 году ботнет P2PInfect использовал уязвимые экземпляры для майнинга Monero, а ранее вредоносные программы Redigo, HeadCrab и Migo внедряли криптомайнеры и отключали защиту на серверах Redis.</p><p>По словам Wiz, сочетание широкого распространения Redis, небезопасных конфигураций по умолчанию и критичности уязвимости создаёт «острую необходимость в немедленном обновлении».</p>]]></content:encoded>
    </item>
    <item>
      <title>Redis против Postgres в роли кэша: неожиданные итоги бенчмарка</title>
      <link>https://tproger.ru/news/redis-protiv-postgres-v-roli-kewa--neozhidannye-itogi-benchmarka</link>
      <comments>https://tproger.ru/news/redis-protiv-postgres-v-roli-kewa--neozhidannye-itogi-benchmarka?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/redis-protiv-postgres-v-roli-kewa--neozhidannye-itogi-benchmarka</guid>
      <description><![CDATA[<p>Бенчмарк показал: Redis быстрее в роли кэша, но PostgreSQL с unlogged-таблицами выдаёт до 7400 rps и подходит для многих проектов</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/redis-protiv-postgres-v-roli-kewa--neozhidannye-itogi-benchmarka">Redis против Postgres в роли кэша: неожиданные итоги бенчмарка</a>»</p>]]></description>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 25 Sep 2025 10:00:51 GMT</pubDate>
      <content:encoded><![CDATA[<p>Разработчик <a href="https://dizzy.zone/2025/09/24/Redis-is-fast-Ill-cache-in-Postgres/">провел</a> собственное исследование, сравнив производительность Redis и PostgreSQL в роли кэша.</p><p>Он ожидал увидеть очевидное превосходство Redis, но результаты оказались не такими однозначными — особенно с учетом unlogged-таблиц в Postgres.</p><h2>Как проводили тест</h2><ul><li>Был написан простой HTTP-сервер на Go с двумя эндпоинтами: GET и SET.</li><li>В качестве кэша использовались: Redis (через go-redis) и PostgreSQL с unlogged-таблицей (через pgx).</li><li>Оба сервиса запускались в k8s с лимитами: 2 CPU и 8 ГБ ОЗУ.</li><li>Генерация нагрузки — с помощью k6, с предзаполнением кэша 30 млн записей.</li><li>Тестировались три типа нагрузки: только чтение, только запись, смешанная (80% чтения и 20% записи).</li></ul><h2>Что показали тесты</h2><h3>1. Redis быстрее почти по всем метрикам</h3><ul><li>При чтении Redis обработал более 11 000 запросов в секунду против 7400 у PostgreSQL.</li><li>Задержки в Redis в среднем были на 20–30 мс ниже.</li><li>При записи разрыв еще больше: Redis справлялся с ~10 700. запросов в секунду, а PostgreSQL — с ~6000.</li></ul><h3>2. Unlogged-таблицы действительно ускоряют PostgreSQL</h3><ul><li>При использовании обычных таблиц производительность записи в Postgres падала почти в 3 раза.</li><li>На смешанной нагрузке unlogged-таблицы дали прирост примерно в 25%.</li></ul><h3>3. Redis стабильно использовал меньше ресурсов</h3><ul><li>Redis не превышал 1.3 CPU и держал стабильное потребление памяти (~4–4.5 ГБ).</li><li>PostgreSQL упирался в лимит CPU и требовал до 6 ГБ ОЗУ при нагрузке.</li></ul><h2>Когда стоит выбрать Postgres</h2><p>Автор поста подчеркивает: несмотря на более высокую производительность Redis, он лично предпочитает Postgres в небольших и средних проектах:</p><ul><li>База данных все равно уже используется в проекте.</li><li>Нет нужды в сложных TTL или автоудалении.</li><li>Не хочется тянуть в проект еще одну зависимость.</li><li>От 7000 до 8000 запросов в секунду — более чем достаточно для большинства задач.</li></ul><p>7425 запросов в секунду — это больше полумиллиарда в день. Все на 10-летнем железе и обычном PostgreSQL. Мало кто работает на таких нагрузках.</p>]]></content:encoded>
    </item>
    <item>
      <title>Куда двигаться после изучения Django: советы для Python-разработчиков</title>
      <link>https://tproger.ru/articles/kuda-dvigatsya-posle-izucheniya-django--sovety-dlya-python-razrabotchikov-257299</link>
      <comments>https://tproger.ru/articles/kuda-dvigatsya-posle-izucheniya-django--sovety-dlya-python-razrabotchikov-257299?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгения Епихина]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kuda-dvigatsya-posle-izucheniya-django--sovety-dlya-python-razrabotchikov-257299</guid>
      <description><![CDATA[<p>В статье разбираемся, почему Django — далеко не финиш в карьере, и в каких направлениях можно двигаться Python-разработчику.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kuda-dvigatsya-posle-izucheniya-django--sovety-dlya-python-razrabotchikov-257299">Куда двигаться после изучения Django: советы для Python-разработчиков</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Парсинг]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[Машинное обучение]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Raspberry Pi]]></category>
      <category><![CDATA[DevOps]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[Навыки]]></category>
      <category><![CDATA[Django]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Data Science]]></category>
      <category><![CDATA[NoSQL]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[аналитик]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Neo4j]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 12 Aug 2025 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Django — это веб-фреймворк на языке Python, который позволяет быстро создавать сложные веб-приложения. Он включает в себя готовые компоненты для работы с базами данных, маршрутизацией URL, обработкой форм, аутентификацией пользователей и админ-панелями, что значительно ускоряет разработку и упрощает поддержку проектов.</p><p>Владение Django — это старт, а не финиш. Чтобы оставаться востребованным, нужно постоянно расширять знания и навыки. В этой статье разберем пути и направления для улучшения своих компетенций.</p><h2>Почему владение Django — не предел для разработчика</h2><h2>Особенности Django</h2><p>Django используют для разработки веб-приложений разной сложности: при работе с большими базами данных, для создания сервисов, способных обслуживать большое количество пользователей. На нём создают соцсети, новостные сайты, веб-версии приложений, онлайн-магазины.</p><p>Основные плюсы:</p><ul><li><b>Полноценный стек</b>: ORM для работы с базой, мощная система маршрутизации URL, шаблоны для рендеринга, встроенная админка, формы, система аутентификации и авторизации.</li><li><b>Архитектура MTV (Model-Template-View)</b>: похожа на классический MVC, но с особенностями, которые упрощают разделение логики, представления и данных.</li><li><b>Безопасность</b>: Django автоматически защищает от CSRF, XSS, SQL-инъекций и других распространенных атак. Не нужно писать много дополнительного кода.</li><li><b>Активное сообщество и экосистема</b>: тысячи сторонних пакетов, расширений и готовых решений.</li><li><b>Поддержка нескольких баз данны</b>х: PostgreSQL, MySQL, SQLite, Oracle и др.</li></ul><p>Ограничения:</p><ul><li><b>Синхронная природа Django</b>.</li><li><b>Монолитность</b>: архитектура фреймворка ориентирована на создание крупных приложений, но в микросервисах может быть избыточна.</li><li><b>Ограниченная гибкость ORM</b>: нестандартные SQL-запросы иногда сложно выразить средствами ORM, приходится использовать raw SQL или сторонние библиотеки для запросов.</li><li><b>Строгие правила организации кода</b>: требуют дисциплины и могут ограничивать свободу в архитектурных решениях.</li><li><b>Недостаточная производительность</b>: уступает лёгким асинхронным фреймворкам (например, FastAPI), особенно под высокими нагрузками. Но для большинства проектов пока это не критично.</li></ul><h2>В каком направлении двигаться после изучения Django</h2><blockquote>Задача — создавать продукт, который будет нужен конечному потребителю.</blockquote><h3>Первое направление для развития — расширить инструментарий для решения разных задач в веб-разработке</h3><p>Возможные пути:</p><ul><li>Изучить другие веб-фреймворки (Flask, FastAPI)</li><li>Углубиться в асинхронное программирование (asyncio, aiohttp)</li><li>Работать с API и микросервисами</li></ul><h4>Flask и FastAPI</h4><p>Flask — минималистичный микрофреймворк, даёт полную свободу в выборе компонентов. Используют для небольших приложений и микросервисов.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-08-12/bbe7c040-14de-426d-9a10-c556f7319bee.png" alt="" /><figcaption>Пример простой команды на Flask</figcaption></figure><p>FastAPI — современный асинхронный фреймворк, ориентирован на создание высокопроизводительных API. Поддерживает стандарт OpenAPI и автоматическую генерацию документации. Он быстрее Flask и Django благодаря asyncio и Pydantic.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-08-12/33a27277-5136-4a03-8af2-f436db42fa99.png" alt="" /><figcaption>Пример простого API на FastAPI</figcaption></figure><h4>Асинхронное программирование</h4><p>Веб-разработка всё активнее использует асинхронные технологии. Django не всегда справляется с задачами высокой конкурентной нагрузки.</p><p>Поэтому изучение asyncio — стандартной библиотеки Python для асинхронного программирования — откроет перед вами новые возможности. Вместе с aiohttp или тем же FastAPI вы сможете создавать приложения, которые обрабатывают тысячи одновременных соединений. Это особенно важно для real-time сервисов, чат-приложений и систем с интенсивным обменом данными.</p><h3>Второе направление — расширить навыки в смежных областях</h3><p>Можно пойти по пути расширения компетенций за пределы основной специализации. Важно не только уметь писать код, но и понимать, как приложения разворачиваются и работают в продакшене. Знание DevOps-практик помогает наладить эффективное взаимодействие между разработкой и эксплуатацией.</p><h4>Изучение DevOps и контейнеризации</h4><p>Контейнеры позволяют запускать приложения в изолированной среде, это упрощает настройку и развертывание. Например, Docker помогает упаковать приложение с зависимостями в один контейнер, а Kubernetes — управлять такими контейнерами в продакшене. Знание этих технологий улучшит взаимодействие с операционной командой и ускорит выпуск новых версий приложений.</p><h4>CI/CD и автоматизация процессов</h4><p>Непрерывная интеграция (Continuous Integration) и непрерывное развертывание (Continuous Deployment) — ключевые практики современной разработки ПО. Они позволяют автоматизировать сборку, тестирование и доставку приложений, масштабировать процессы.</p><p>Инструменты CI/CD (например, Jenkins, GitLab CI/CD, GitHub Actions) помогают настроить автоматические пайплайны, которые обеспечивают быструю обратную связь и минимизируют человеческий фактор в релизах. Автоматизация процессов снижает количество ошибок и позволяет сосредоточиться на разработке новых функций.</p><p>Настройка непрерывной интеграции и доставки (Continuous Integration / Continuous Delivery) снижает поток ошибок при релизах и экономит время. Пример: GitHub Actions для автоматического запуска тестов и сборки проекта при каждом коммите.</p><h4>Больше знаний в области баз данных</h4><p>Помимо классических реляционных баз данных (PostgreSQL, MySQL), современные приложения часто используют NoSQL для специфичных задач. MongoDB, Redis, Cassandra обеспечивают гибкость в хранении данных, горизонтальное масштабирование и высокую производительность при работе с большими объемами информации.</p><p>Графовые базы данных (Neo4j, ArangoDB) предназначены для эффективного хранения и анализа связей между объектами, что важно для социальных сетей, рекомендательных систем и других приложений с богатой структурой.</p><p>Так, Redis хорошо подходит для кэширования данных, а Neo4j — для сложных связей между объектами.</p><h3>Третье направление — переход к другим аспектам Python-разработки</h3><p>Рассмотрим четыре варианта карьерного развития для Python-программиста: Data Science и машинное обучение, автоматизация бизнес-процессов, разработка десктопных приложений и встраиваемые системы (IoT).</p><p>Почему стоит попробовать?</p><ul><li>Высокий спрос на специалистов. Они востребованы в банках и инвестиционных компаниях, в сфере медицины и биотехнологии, в консалтинге,  автомобильной промышленности и т.д..</li><li>Широкий набор библиотек: pandas, NumPy, scikit-learn, TensorFlow, PyTorch.</li><li>Возможность работать с реальными задачами: от бизнеса до науки.</li></ul><h4>Автоматизация и скрипты для бизнеса</h4><p>Python часто используется для автоматизации рутинных задач: парсинга данных, обработки файлов, интеграции систем, генерации отчетов. Создание скриптов для автоматизации бизнес-процессов помогает повысить эффективность работы и снизить количество ошибок.</p><p>Знание таких библиотек, как openpyxl (работа с Excel), requests (HTTP-запросы), BeautifulSoup и Scrapy (парсинг веб-страниц), а также умение писать скрипты под конкретные задачи, делают разработчика ценным специалистом в корпоративной среде.</p><p>Примеры задач:</p><ul><li>Автоматическая загрузка данных из Excel и их преобразование</li><li>Скрипты для отправки email-рассылок</li><li>Интеграция с CRM и другими сервисами через API</li></ul><h4>Разработка десктопных приложений (PyQt, Kivy)</h4><p>Хотя сейчас популярность уходит к вебу и мобильным платформам, десктопные приложения на Python востребованы в таких сферах: инструменты для анализа, редакторы, утилиты.</p><p>Инструменты для создания:</p><ul><li>PyQt — мощный фреймворк для создания кроссплатформенных GUI.</li><li>Kivy — библиотека для разработки приложений с поддержкой сенсорных экранов.</li></ul><p>Этот путь подходит тем, кто хочет создавать удобные инструменты с графическим интерфейсом для пользователей на Windows, macOS или Linux.</p><h4>Встраиваемые системы и IoT</h4><p>В области IoT и встроенных систем Python набирает популярность благодаря легкости освоения и поддержке на маломощных устройствах. Помогают в этом  платформы по типу Raspberry Pi и MicroPython.</p><p>Изучение этого направления открывает возможности работы с аппаратным обеспечением, созданием прототипов и внедрением инновационных решений в промышленности и бытовой технике.</p><p>Например, с помощью Python на Raspberry Pi можно  разрабатывать датчики для мониторинга состояния оборудования на производстве и разрабатывать прототипы носимых устройств для сбора данных о здоровье.</p><blockquote>Если рассматривать профессию “Python-разработчик на Django”, то сразу получится сужение до конкретной библиотеки на конкретном языке. Если же в резюме у специалиста стоит, что он “разработчик Python”, возможностей сильно больше. Если написать про себя “разработчик”, будет не понятно, разработчик чего. Но изменив резюме на “DevOps инженера”, становится понятен карьерный трек.</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Безопасное исполнение ненадёжного кода</title>
      <link>https://tproger.ru/articles/bezopasnoe-ispolnenie-nenadyozhnogo-koda</link>
      <comments>https://tproger.ru/articles/bezopasnoe-ispolnenie-nenadyozhnogo-koda?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Межов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/bezopasnoe-ispolnenie-nenadyozhnogo-koda</guid>
      <description><![CDATA[<p>Методы безопасного исполнения ненадёжного кода. Рассматриваются уровни изоляции кода, методы ограничения ресурсов процесса, проблемы жёсткого лимитирования и подходы к их решению. Обсуждаются вопросы управления песочницами, а также использование инструментов контейнеризации.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/bezopasnoe-ispolnenie-nenadyozhnogo-koda">Безопасное исполнение ненадёжного кода</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Опрос]]></category>
      <category><![CDATA[Практика]]></category>
      <category><![CDATA[.NET]]></category>
      <category><![CDATA[DevOps]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Kubernetes]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Машин]]></category>
      <category><![CDATA[микро]]></category>
      <category><![CDATA[Песочница]]></category>
      <category><![CDATA[1C]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[CI/CD]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 06 Jul 2025 09:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы привыкли к тому, что ведем разработку, используя лучшие инженерные практики, включая настройку CI/CD-конвейера. Сначала код проходит многоэтапные стадии проверки и тестирования, а только потом попадает в production-среду.</p><p>Давайте представим ситуацию, что нужно запустить код, минуя все эти стадии. Прям в production-среде. На первый взгляд — бред! Но если подумать, то на самом деле, не такая уж редкость. Например, некоторые системы предоставляют своим пользователям возможность расширять функциональность за счет прикладных скриптов. Наш любимый CI/CD-конвейер зачастую построен на пользовательских скриптах.</p><p>С одной стороны, для большинства подобная постановка вопроса — крайность. С другой, появляется возможность рассмотреть проблему с разных ракурсов. Уверен, что какие-то части общего решения, о котором пойдёт речь далее, могут быть использованы повторно и в других проектах.</p><p>Предлагаю по частям разобрать проблему безопасного исполнения ненадёжного кода. Последовательно рассмотрим вопросы, ответы на которые поворотные в выборе целевой архитектуры. Большая часть статьи касается разработки, но в конце сделаны важные акценты относительно администрирования и развертывания.</p><h2>Ненадёжный код</h2><p>Для начала определимся, что же считать ненадёжным кодом? На самом деле ответ зависит от решаемой задачи, правил и процессов, принятых в компании:</p><ul><li>Код, который не прошел CI, review и т.п.</li><li>Код из ненадёжного или неизвестного источника.</li><li>Закрытый (проприетарный) код.</li><li>Код, содержащий уязвимости.</li><li>Код, использующий запрещенные функции.</li><li>Любой код, который написал коллега:)</li></ul><p>Чтобы отделять код разрабатываемого приложения от ненадёжного, первый буду называть кодом приложения, а второй — <i>ненадёжным</i> или <i>внешним кодом</i>. Необходимость запуска ненадёжного кода в некоторых случаях буду называть <i>задачей</i>.</p><h2>Уровни изоляции кода</h2><p>Можно выделить три варианта запуска внешнего кода — три уровня изоляции. Каждый следующий увеличивает дистанцию между кодом приложения и запускаемым кодом. Чем выше уровень изоляции, тем меньше вероятность, что запускаемый код нанесет вред приложению и системе.</p><h3>Уровень 1: тот же процесс</h3><p>Запуск внешнего кода в адресном пространстве процесса приложения.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/191fd454-6837-4e91-abbf-c6d4a1fb6121.png" alt="Запуск внешнего кода в адресном пространстве процесса приложения." /><figcaption>Запуск внешнего кода в адресном пространстве процесса приложения.</figcaption></figure><p>Такой способ определяет самый слабый уровень изоляции, поскольку запущенный код теоретически имеет доступ ко всему тому, к чему имеет доступ код самого приложения.</p><p>Примером может служить использование интерпретаторов скриптов (<a href="https://github.com/mozilla/rhino">Rhino</a>, <a href="https://github.com/IronLanguages/ironpython3">IronPython</a>, <a href="https://github.com/jython/jython">Jython</a> и т.п.), визуальных языков программирования (workflow-движков) или подключение модулей расширения (плагинов).</p><p>Способов защиты на этом уровне не так много. Пожалуй, самым эффективным выступает (self-sandboxing), при котором приложение делает самозапрет на доступ к некоторым ресурсам системы. Например, сразу после инициализации — самозапрет на доступ к файловой системе.</p><p>Дополнительно запускаемый код можно подвергать строгому (синтаксическому) анализу, запрещая использование определенных функций, модулей, пакетов и т.п. Некоторые интерпретаторы имеют точки расширения, которые позволяют контролировать процесс исполнения. Если такой возможности нет, можно воспользоваться одной из техник самоизоляции — <a href="https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html">фильтрацией системных вызовов</a>.</p><p>Что же касается плагинов, то они призваны расширять возможности приложения, поэтому их использование изначально не предполагает сильной изоляции. Здесь можно предложить усилить контроль взаимодействия на уровне контракта (API). В идеале — если плагины будут публиковаться в некоторый центральный репозиторий, которому вы доверяете и который может производить дополнительные проверки и тестирование до этапа запуска кода плагина.</p><h3>Уровень 2: отдельный процесс</h3><p>Запуск внешнего кода на той же машине, но в отдельном процессе ОС.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/e0bf62de-65a9-4370-9986-ed21c6e63b8e.png" alt="Запуск внешнего кода на той же машине, но в отдельном процессе ОС." /><figcaption>Запуск внешнего кода на той же машине, но в отдельном процессе ОС.</figcaption></figure><p>Поскольку и приложение, и внешний код взаимодействуют в рамках одного узла, используя локальные ресурсы ОС (оперативная память, файловая система и т.п.), скорость межпроцессного взаимодействия очень высокая.</p><p>Этот уровень изоляции предполагает использование широкого арсенала возможностей. Как минимум, внешний код может быть запущен от имени менее привилегированного пользователя, с ограниченным доступом к ресурсам ОС. Сильные способы изоляции ограничивают ресурсы с помощью средств ОС или инструментов контейнеризации. Однако, чем сильнее контроль, тем больше накладных расходов на запуск и исполнение процесса, что при решении некоторых задач неприемлемо дорого или неоправданно сложно.</p><h3>Уровень 3: отдельная машина</h3><p>Запуск внешнего кода на отдельной машине — песочнице.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/5e48bc50-75c9-4789-9dc7-3bbf2c1659a7.png" alt="Запуск внешнего кода на отдельной машине — песочнице." /><figcaption>Запуск внешнего кода на отдельной машине — песочнице.</figcaption></figure><p>Это максимальный уровень изоляции из всех возможных. Здесь появляется возможность ограничить ресурсы самой песочницы (CPU, память, дисковое пространство, доступ к сети и т.п.). Если в результате исполнения внешнего кода песочница выйдет из строя, приложение продолжит свою работу.</p><p>Самый главный недостаток этого подхода — необходимость сетевого взаимодействия между узлом, на котором работает приложение, и песочницей. Передача входных данных в песочницу, запуск процесса внутри, ожидание окончания его исполнения, получение выходных данных — всё это сетевые обращения. Так существенно замедляется процесс исполнения, а само взаимодействие подвержено сетевым сбоям, что ведёт к нестабильности системы и получаемых результатов.</p><h2>Использование песочницы</h2><p>Предположим, требуется максимальный уровень изоляции ненадёжного кода, следовательно, нужно остановиться на варианте запуска на отдельной машине. Если так, то для принятия последующих архитектурных решений нужно ответить на следующую пару вопросов.</p><h3>Пересоздание или переиспользование песочницы</h3><p>Песочницу требуется пересоздавать перед исполнением каждой задачи, если требуется особенное окружение (например, определенная версия ОС, пакетов или ресурсов) или идентичность этого окружения (для стабильности получаемых результатов). Схожие вопросы возникают, например, при интеграционном тестировании: каждому тесту нужны свои предустановки.</p><p>Переиспользование песочницы становится возможным, если задачи могут исполняться в одном окружении и не оказывают влияния друг на друга (предыдущая задача не портит результаты последующей). Продолжая аналогию с интеграционным тестированием: всем тестам нужны одинаковые предустановки, и тесты могут запускаться повторно на одном стенде, демонстрируя один и тот же результат.</p><p>Основным преимуществом пересоздания песочницы выступает стабильность получаемых результатов. К недостаткам относится медленный запуск и перерасход ресурсов. На пересоздание песочницы уходят десятки секунд или даже минут, следовательно, большая часть ресурсов будет тратиться именно на это. Существует множество техник ускорения пересоздания, благодаря которым можно сократить время запуска. Прежде всего, сюда можно отнести backup/restore (snapshot песочницы, базы данных и т.п.). Также если поток задач небольшой и ресурсы позволяют, можно попробовать организовать пул песочниц и создавать их заранее.</p><p>Ставка на переиспользование делается в случае, когда поток задач большой и нужно сократить время ожидания их запуска. При этом возрастает вероятность получения нестабильных результатов и, возможно, требуется производить какую-то очистку окружения до или после исполнения очередной задачи.</p><h3>Последовательное или параллельное исполнение</h3><p>Теперь осталось ответить на вопрос, как именно можно или нужно исполнять задачи: последовательно или параллельно. Последовательное исполнение требуется в следующих случаях:</p><ul><li>важен порядок следования и исполнения задач;</li><li>задачам нужен эксклюзивный доступ к определенному ресурсу;</li><li>задачи ёмкие и их совместное исполнение вызовет нехватку ресурсов;</li><li>задачи могут мешать исполнению друг друга из-за борьбы за ресурсы.</li></ul><p>Например, шаги установки и настройки ПО; шаги CI/CD-конвейера; рендеринг изображения на GPU; интенсивные вычисления. Все эти задачи, скорее всего, придётся исполнять <b>последовательно</b>.</p><p>В остальных случаях допустимо <b>параллельное исполнение</b>. Яркой аналогией может служить одна из лучших практик в тестировании: тесты не должны оказывать влияние друг на друга, а порядок их запуска не должен иметь значения.</p><p>Последовательное исполнение обеспечивает стабильность получаемых результатов, однако приводит к низкой пропускной способности и дороговизне масштабирования (песочница обходится дороже процесса ОС). Параллельное исполнение, напротив, увеличивает пропускную способность системы и улучшает утилизацию ресурсов песочницы, но одновременно повышает вероятность нестабильных результатов. Более того, при параллельном исполнении появляется шанс перегрузить песочницу или вывести её из строя таким образом, что приведет к увеличению времени исполнения всех запущенных задач или потере результатов их работы.</p><p>На практике было замечено, что при параллельном исполнении, несмотря на увеличенную общую пропускную способность, время исполнения каждой отдельной задачи увеличивается. Если уровень параллелизма становится больше числа CPU-ядер, время исполнения начинает деградировать намного сильней.</p><h2>Управление песочницами</h2><p>Допустим, переиспользование песочниц возможно. В таком случае необходимо определить способ управления ими. Можно выделить два подхода, основанные на принципах микросервисной архитектуры, но адаптированные к специфике рассматриваемой проблемы.</p><h3>Оркестрация</h3><p>Оркестрация предполагает, что приложение совмещает две роли: оркестратор исполнения и оператор песочниц. Оркестратор координирует процесс исполнения кода: выбор подходящей песочницы, загрузка в неё входных данных, запуск удалённого процесса, получение результатов его работы и т.п. Оператор, в свою очередь, отслеживает доступные песочницы и их состояние.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/93758cab-2542-4a80-b47a-d65b04ef4f14.png" alt="Оркестрация песочниц." /><figcaption>Оркестрация песочниц.</figcaption></figure><p>Основное преимущество оркестрации в контексте решаемой проблемы — её простота и ясность. Код легко читается и сосредоточен в одном месте. Однако у этого решения есть и недостатки. Рассмотрим их в порядке от простого к сложному.</p><ul><li><i>Синхронное взаимодействие.</i> Так или иначе, для результата приложение вынуждено ожидать окончания исполнения задачи. Для продуктивного использования ресурсов приходится прибегать к техникам асинхронного программирования: пока задача исполняется, приложение будет занято полезной работой. Это малозаметный недостаток в языках со встроенной поддержкой концепции асинхронного программирования. Для упрощения работы с асинхронным кодом в Java я создал небольшую вспомогательную библиотеку <a href="https://github.com/AlexMAS/asynchronizer">asynchronizer</a>, снабдив её подробной <a href="https://github.com/AlexMAS/asynchronizer/blob/main/docs/README.ru.md">документацией</a>.</li></ul><ul><li><i>Отслеживание доступности песочниц.</i> Поскольку хотелось бы, чтобы количество песочниц менялось в зависимости от нагрузки на систему, придётся отслеживать их доступность. Это прямая обязанность оператора песочниц, которую можно выделить в отдельный discovery-сервис (например, на базе <a href="https://github.com/spring-cloud/spring-cloud-netflix">Netflix Eureka</a>), либо реализовать как часть приложения с использованием инфраструктурных механизмов (например, <a href="https://github.com/fabric8io/kubernetes-client">Kubernetes API</a>). Важно отметить, что оператор песочниц не имеет отношения к бизнес-логике приложения.</li></ul><ul><li><i>Отслеживание загруженности песочниц.</i> Оркестратор исполнения должен выбрать подходящую <a href="https://samwho.dev/load-balancing/">стратегию балансировки</a>, основанную на состоянии песочниц, предоставляемых оператором. На практике наилучшую эффективность демонстрирует алгоритм Least connections, с помощью которого можно выбирать наименее загруженные песочницы. Для этого достаточно вести учёт количества задач, исполняемых каждой песочницей. Конечно, это не серебряная пуля, а лишь частное наблюдение, поэтому в идеале нужно предусмотреть несколько стратегий балансировки и выбрать наилучшую по результатам нагрузочного тестирования.</li></ul><ul><li><i>Неопределённость результата, если нет ответа от песочницы.</i> Песочница может быть недоступна по различным причинам, включая не только проблемы с сетью, но и падения песочницы из-за ненадёжного кода. К сожалению, в общем случае эта проблема не имеет решения, так как делать повторные запуски (retries) может быть опасно. Всё, что остаётся, это использовать таймауты и откладывать неуспешную задачу на потом.</li></ul><p>Ещё один существенный минус, который стоит упомянуть, это возможный побочный эффект, возникающий при масштабировании системы и проявляющийся в виде перегрузки песочниц. Для наглядности рассмотрим конкретный пример.</p><p>Для отслеживания нагрузки на песочницы экземпляр приложения ориентируется на количество задач в каждой из доступных песочниц. Предположим, что было принято решение увеличить количество экземпляров приложения. Этот экземпляр исполняет 5 задач в двух песочницах (3 процесса в одной и 2 в другой). Известно, что каждая песочница может вынести максимум 4 параллельных задачи.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/623f7e7c-71d0-41da-8681-731ef43d3716.png" alt="Экземпляр исполняет 5 задач в двух песочницах (3 процесса в одной и 2 в другой)." /><figcaption>Экземпляр исполняет 5 задач в двух песочницах (3 процесса в одной и 2 в другой).</figcaption></figure><p>Добавив новый экземпляр приложения, неизвестно, сколько задач исполняет каждая песочница. Такая ситуация может произойти по разным причинам.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/7131d310-7c0d-44b8-b031-055436bd64d8.png" alt="Новый экземпляр приложения не знает, сколько задач исполняет каждая песочница." /><figcaption>Новый экземпляр приложения не знает, сколько задач исполняет каждая песочница.</figcaption></figure><p>Вполне очевидно, что новый экземпляр приложения направит очередную задачу в первую попавшуюся песочницу, чем может спровоцировать её перегрузку. В итоге результат исполнения будет испорчен или потерян из-за падения песочницы.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/c65090e5-020f-4eff-826b-566d3dc6da5c.png" alt="Очередная задача направляется в первую песочницу и провоцирует её перегрузку." /><figcaption>Очередная задача направляется в первую песочницу и провоцирует её перегрузку.</figcaption></figure><p>В качестве решения можно предложить два способа, каждый из которых уменьшает вероятность возникновения перегрузок, но не избавляет от них.</p><ul><li><i>Для контроля количества исполняемых задач в песочнице использовать распределённый счётчик</i> (например, на базе Redis). Проблема в том, что распределённый счётчик имеет латентность и на момент запуска задач может выдать устаревшее значение. Кроме того, в системе появляется еще один инфраструктурный компонент, который не несёт бизнес-пользы.</li></ul><ul><li><i>Выделить каждому экземпляру приложения эксклюзивное подмножество песочниц.</i> Подобное решение существенно усложнит deployment-скрипты и процесс масштабирования, а также снизит степень утилизации выделенных ресурсов, ведь нет никаких гарантий того, что экземпляр приложения сможет хорошо нагрузить все выделенные ему песочницы.</li></ul><p>Кстати, после доклада на TechLeadConf 2025 мне задали интересный вопрос: <i>Можно ли при балансировке нагрузки на песочницы учитывать не только количество исполняемых задач, но и процент загрузки CPU, памяти и прочих ресурсов? </i>Если у кого-то возник такой же вопрос, то отвечу, что это не имеет смысла, поскольку ситуация в песочнице может поменяться мгновенно. Полученный практический опыт и нагрузочное тестирование показали, что простой подсчёт задач работает эффективно.</p><h3>Самоорганизация</h3><p>В микросервисной архитектуре подобный подход принято называть хореографией, однако чтобы не возникало неправильных ассоциаций, предлагаю использовать термин <i>самоорганизация</i>.</p><p>Ключевой момент в архитектуре — это появление двух очередей: очередь задач на исполнение (Task Queue) и очередь результата их исполнения (Result Queue). Все поступающие задачи приложение направляет в первую очередь, а результаты — во вторую. Дополнительно появляется роль агента — микросервиса, который исполняется в рамках узла песочницы и координирует исполнение поступающих задач.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/0d449846-e767-49bd-bd50-d1c77c49cf10.png" alt="Самоорганизация песочниц." /><figcaption>Самоорганизация песочниц.</figcaption></figure><p>Основной недостаток самоорганизации — распределённый процесс обработки задач. Учитывая простоту алгоритма обработки, это не так существенно. Стоит отметить преимущества этой архитектуры.</p><ul><li><i>Максимальная изоляция ненадёжного кода.</i> Ненадёжный код, как и в случае с оркестрацией, по-прежнему работает в песочнице в рамках отдельного процесса ОС.</li></ul><ul><li><i>Скорость и стабильность взаимодействия.</i> Никаких проблем с сетью  из-за локальности взаимодействия между агентом и песочницей.</li></ul><ul><li><i>Контролируемая нагрузка на песочницы.</i> Агент, выступая в роли консюмера очереди задач, может точно контролировать степень параллелизма и выбирать новые задачи только тогда, когда он закончил обрабатывать предыдущие.</li></ul><ul><li><i>Минимум инфраструктурного кода.</i> Очереди избавляют от необходимости иметь оператор песочниц, отслеживать их состояние и осуществлять балансировку нагрузки.</li></ul><ul><li><i>Простота масштабирования.</i> Приложение и песочницы масштабируются независимо друг от друга без негативных побочных эффектов.</li></ul><h2>Запуск процесса ОС</h2><p>К запуску процесса ОС, в рамках которого будет исполняться ненадёжный код, нужно подойти с особой осторожностью. Здесь важно ответить как минимум на три вопроса.</p><ul><li><i>Как ограничить права доступа к ресурсам.</i> Самое простое решение — запуск процесса от имени пользователя с ограниченными правами (на доступ к ресурсам ОС).</li></ul><ul><li><i>Как ограничить объем используемых ресурсов.</i> Для запускаемого процесса нужно определить доступные ресурсы и возможные действия.</li></ul><ul><li><i>Как осуществлять анализ поведения и результатов исполнения.</i> Наличие и решение этой проблемы целиком и полностью зависит от специфики проекта. Здесь невозможно предложить универсального решения.</li></ul><p>Рассмотрим варианты ограничения ресурсов процесса ОС.</p><h3>Ограничение ресурсов процесса</h3><p>Ресурсы процесса могут быть ограничены на трех уровнях:</p><ul><li><i>Лимиты узла.</i> Физические ограничения машины, на которой исполняется процесс. В частном случае можно говорить об инфраструктурных лимитах, определённых для Docker/Kubernetes контейнера.</li></ul><ul><li><i>Лимиты контейнера.</i> Программные лимиты, задаваемые выбранным инструментом контейнеризации (cgroup, Docker, <a href="https://dzen.ru/a/Z8sdaQvf5w96SRes">Bubblewrap</a>, <a href="https://github.com/AlexMAS/ProcessSandbox">ProcessSandbox</a> и т.п.).</li></ul><ul><li><i>Лимиты процесса.</i> Программные лимиты, задаваемые средствами ОС. На этом уровне можно осуществлять гибкую настройку вариантов запуска и исполнения.</li></ul><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/d11a1ee8-1780-4ca6-b826-a09d4d95ed0d.png" alt="Уровни лимитирования ресурсов процесса." /><figcaption>Уровни лимитирования ресурсов процесса.</figcaption></figure><p>Можно использовать все три уровня лимитирования, либо какой-то определенный.</p><p>Между тем, важно отметить некоторые трудности, которые могут возникнуть при использовании инструментов контейнеризации.</p><p>Например, для использования cgroup или Docker внутри Kubernetes-контейнера нужно эскалировать привилегии контейнера, что в общем случае небезопасно в контексте исполнения ненадёжного кода. Более того, практика показала, что легковесных rootless-средств, предоставляемых ОС, вполне достаточно, чтобы снять большую часть рисков. В частности, Linux API позволяет не только лимитировать CPU и память, но и блокировать доступ к некоторым возможностям самой ОС. Например, можно наложить фильтр, который запретит вызов определённых системных функций.</p><h3>Проблемы жёсткого лимитирования</h3><p>Рассмотренные выше способы лимитирования задают жёсткие границы (hard limit), нарушение которых замедляет исполнение процесса, либо приводит к его принудительному завершению. При этом поведение наблюдаемого процесса и системы сильно варьируется в зависимости от того, какой лимит был превышен. Например, превышение по использованию CPU может привести к троттлингу (throttling), приостановке работы или принудительному завершению; превышение по использованию памяти заканчивается принудительным завершением со стороны ОС (OOM Killer) либо самостоятельным падением процесса (с ошибкой Out Of Memory).</p><p>Подобная вариативность осложняет <i>анализ поведения и результатов исполнения.</i> В этом случае можно использовать подход с программной мягкой границей (watchdog limit). Суть заключается в запуске дополнительного следящего потока (или процесса) ОС, который контролирует поведение и расход ресурсов у наблюдаемого. Как только детектируется превышение одного из лимитов, производится принудительное завершение наблюдаемого процесса, но уже не со стороны ОС, а со стороны приложения.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/e38eb138-04f3-439f-89b5-9b810f112a69.png" alt="Программная мягкая граница (watchdog limit)." /><figcaption>Программная мягкая граница (watchdog limit).</figcaption></figure><p>Такой подход имеет несколько преимуществ.</p><ul><li><i>Точное определение причин принудительного завершения.</i> Жёсткие лимиты чуть выше мягких, благодаря чему для исполняемого кода создаётся иллюзия отсутствия каких-либо лимитов. Между тем, если лимиты всё-таки нарушаются, процесс всё равно будет завершен (либо со стороны приложения, либо гарантированно со стороны ОС). Но подобный дополнительный контроль со стороны приложения оставляет для него гораздо больше шансов понять причину принудительного завершения наблюдаемого процесса.</li></ul><ul><li><i>Возможность гибкого лимитирования ресурсов.</i> Приложение (или агент), ответственное за запуск наблюдаемого процесса, может обратиться к средствам ОС (в частности, к Linux API) и гибко настроить параметры запуска и исполнения. Как минимум, жёстко определить лимиты по CPU и памяти; наложить ограничения на объем I/O; создать запрет на вызов некоторых системных функций (например, запрет использования сетевых операций или файловой системы) и т.п.</li></ul><p>Такой подход я назвал watchdog и в целях иллюстрации реализовал его в виде .NET-библиотеки <a href="https://github.com/AlexMAS/ProcessSandbox">ProcessSandbox</a>. Помимо прочего, на странице проекта подробно рассмотрена проблематика контроля и анализа поведения процесса ОС со стороны прикладного кода.</p><p>Итоговая схема лимитирования может выглядеть так, как показано на рисунке ниже. Вместо тяжеловесных инструментов контейнеризации используется легковесный rootless-инструмент (watchdog) на базе средств ОС и только.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/2c5e74e4-125b-467d-8b27-30b005364523.png" alt="Лимитирование ресурсов процесса с помощью watchdog." /><figcaption>Лимитирование ресурсов процесса с помощью watchdog.</figcaption></figure><p>Для задач, где не нужен анализ поведения процесса и тонкая настройка лимитов, можно воспользоваться готовым инструментом — утилитой.</p><h2>Многоконтейнерные поды</h2><p>Зная, что контейнеры одного Kubernetes-пода работают на одном и том же узле, можно попытаться решить проблему нестабильности сетевого взаимодействия приложения и песочницы, разместив их контейнеры в одном поде.</p><figure><img src="https://media.tproger.ru/user-uploads/115455/2025-06-18/62400b6a-9c5b-4285-bff8-b0adb5ac8868.png" alt="Размещение контейнера приложения и песочницы в одном Kubernetes-поде." /><figcaption>Размещение контейнера приложения и песочницы в одном Kubernetes-поде.</figcaption></figure><p>Несмотря на всю заманчивость данной идеи, она несёт ряд недостатков.</p><ul><li><i>Плохая масштабируемость.</i> Соотношение приложение-песочница всегда один к одному. Однако не исключено, что в некоторых случаях это вполне приемлемо.</li></ul><ul><li><i>Плохая утилизация ресурсов.</i> Сможет ли приложение достаточно нагрузить песочницу, если количество песочниц будет в избытке; и наоборот, нужно ли столько же экземпляров приложения, сколько и песочниц.</li></ul><ul><li><i>Возможность перегрузки песочницы.</i> В распоряжении экземпляра приложения только одна песочница, которая может не справиться с потоком задач, обрабатываемых приложением.</li></ul><ul><li><i>Риск нарушить работоспособность приложения.</i> Выход песочницы из строя скорее всего приведет к перезапуску всего пода. Более того, если приложение и песочница обмениваются файлами через общий раздел (<a href="https://kubernetes.io/docs/tasks/access-application-cluster/communicate-containers-same-pod-shared-volume/">shared volume</a>), это может стать уязвимым местом.</li></ul><h2>Образ для песочницы</h2><p>Основные моменты, которые следует учесть при создании (Docker) образов песочниц:</p><ul><li><i>Заменить init-процесс</i> на <a href="https://github.com/krallin/tini">tini</a>, чтобы не превысить лимит по PIDs.</li></ul><ul><li><i>Создать непривилегированного пользователя,</i> ограничив ему права на доступ к ресурсам.</li></ul><ul><li><i>Регулярно сканировать версии образов</i> и пакетов на наличие уязвимостей.</li></ul><h2>Инфраструктура исполнения</h2><p>Основные моменты, которые следует учесть при настройке инфраструктуры исполнения:</p><ul><li><i>Обеспечить быстрый (пере)запуск песочниц.</i> Нужно быть готовым к тому, что песочницы будут падать. Если речь идет о Kubernetes, то улучшить время запуска может подходящая настройка <a href="https://kubernetes.io/docs/concepts/containers/images/">Image Pull Policy</a>. При этом лучше не использовать тег `latest`, а указывать конкретную версию или хэш-код образа, чтобы не тратить время на попытки определения последней версии при каждом запуске.</li></ul><ul><li><i>Установить приемлемый <a href="https://kubernetes.io/docs/concepts/policy/pid-limiting/">лимит на PIDs</a>.</i> Необходимо контролировать число активных процессов в системе. Особо вредоносный код может попытаться создать очень много дочерних процессов, поэтому при отсутствии лимита на PIDs узел быстро будет выведен из строя. Важно отметить, что лимит задаётся для пользователя, а не для запускаемого процесса. По этой причине он должен быть разумно большим.</li></ul><ul><li><i>Установить <a href="https://kubernetes.io/docs/concepts/policy/resource-quotas/">лимиты на ресурсы узла</a>.</i> В Kubernetes для каждого контейнера нужно указать, как минимум, лимиты по CPU и памяти. Значения лимитов лучше всего определить в ходе нагрузочного тестирования или путём сбора метрик приложения.</li></ul><h2>Заключение</h2><p>Как можно заметить, задача исполнения ненадёжного кода всегда решается в комплексе, начиная с анализа, продолжая разработкой и заканчивая вопросами уровня DevOps. Думаю, что многие техники и инструменты применимы и к коду самого приложения.</p><p>Я постарался показать последовательность шагов по направлению к целевой архитектуре, которая будет отвечать требованиям бизнеса и справляться с ненадёжным кодом. <i>Если вам интересна данная тематика, подписывайтесь на мой Telegram-канал Архитектоника в ИТ (@arch_and_dev). Буду рад поделиться опытом. </i></p>]]></content:encoded>
    </item>
    <item>
      <title>5 инструментов, которые используют айтишные команды</title>
      <link>https://tproger.ru/articles/5-instrumentov--kotorye-ispolzuyut-ajtiwnye-komandy</link>
      <comments>https://tproger.ru/articles/5-instrumentov--kotorye-ispolzuyut-ajtiwnye-komandy?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Ельцова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/5-instrumentov--kotorye-ispolzuyut-ajtiwnye-komandy</guid>
      <description><![CDATA[<p>Показываем, какими инструментами пользуются внутри айтишных команд и какие можно использовать для себя здесь и сейчас или внедрить в свою команду.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/5-instrumentov--kotorye-ispolzuyut-ajtiwnye-komandy">5 инструментов, которые используют айтишные команды</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Статистика]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[Ruby]]></category>
      <category><![CDATA[Отладка]]></category>
      <category><![CDATA[IDE]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[DevOps]]></category>
      <category><![CDATA[Ruby on Rails]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Сервисы]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[Adobe]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Маркетинг]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Аналитика]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Kubernetes]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Grafana]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[CI/CD]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 26 Jun 2025 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>В статье собрали 5 решений — от трекеров задач и онлайн-досок до комплексных платформ для управления проектами. Инструменты помогут закрывать горящие дедлайны, ускорять разработку и четко распределять задачи — все то, что используют в больших командах. Рассказываем, что делать с этими фичами и как их использовать.</p><h2>1. МояДоска</h2><p><a href="https://moyadoska.com/">«МояДоска»</a> — это российский SaaS-сервис для совместной работы и визуализации идей. У онлайн-доски бесконечный размер: это значит, что вы можете размещать сколько угодно элементов и никогда не упретесь в границу. Так, команды, преподаватели и креативные специалисты могут проводить брейнштормы, планирования, презентации и обучение в одном пространстве.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/4fe2215f-6b29-4d0c-a490-281b2d045ddc.jpeg" alt="" /></figure><h3>Что под капотом</h3><p>Фронт написан на React + PixiJS, а бэк — Node.js + PostgreSQL. Это обеспечивает быстрый и понятный интерфейс и стабильную работу даже при большом объёме объектов на доске. Команда выпускает обновления несколько раз в месяц, а о новинках можно узнать в <a href="https://t.me/moyadoska">Telegram-канале</a> сервиса. Например, в недавнем апдейте появилась возможность превратить фрейм в таблицу или тетрадь за пару кликов, а еще задать нужное число столбцов, колонок, толщину границ и цвет.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/0f1a8489-ffe3-4003-8ecc-09d25bbba3b3.png" alt="" /></figure><p><b>Из главных функций:</b></p><ul><li>Понятный интерфейс</li><li>Совместная работа в реальном времени</li><li>Привычные инструменты: фигуры, стрелки, стикеры, текст, загрузка файлов, маркер, карандаш</li><li>Обрезка фото прямо на доске, воспроизведение аудио, поддержка PDF</li><li>Гибкое управление доступом</li><li>Возможность повторного использования шаблонов</li><li>Поддержка фреймов и создание логичных пространств для разных задач</li></ul><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/21a6ecbd-a6b5-40c9-ae19-71a85b919c46.png" alt="" /></figure><h3>Переезд на сервис</h3><p>«МояДоска» интегрируется в процессы на разных этапах, упрощая жизнь команде. Например, сама команда сервиса перешла с Miro на свою доску для стратегического планирования. С помощью стикеров и стрелок они построили дерево решений, чтобы лучше расставить приоритеты. А одно маркетинговое агентство перевело все документы и сессии планирования на доску, отказавшись от Google Docs и таблиц. Это ускорило принятие решений: сотрудники работали с данными в одном формате, точно собрались в одном кабинете перед маркерной доской.</p><h2>2. METEOR</h2><p><a href="https://u-meteor.ru">METEOR</a> — инструмент управления проектами. Это трекер задач с дашбордами, досками канбан, диаграммами Ганта и API, который работает в виде веб-приложения как в облаке, так и на своих серверах. Продукт создавался как универсальный центр управления задачами: он объединяет в себе все — от разработки и тестирования до маркетинга и поддержки. Сейчас его используют более 250 команд и свыше 2000 пользователей ежедневно.</p><h3>Что под капотом</h3><p>В основе — стек Ruby on Rails, React и TypeScript, PostgreSQL и Redis, плюс современная инфраструктура на Docker и Kubernetes.</p><p>Система разбита на микросервисы — за фоновую обработку, нотификации и работу с файлами отвечает отдельный функционал. Авторизация построена через OAuth 2.0 (Google, Yandex), а для аналитики используется Posthog и ELK-стек. Мониторинг реализован на Prometheus + Grafana. Обновления выходят каждую неделю, а обратная связь приходит разработчикам METEOR через Telegram-бот.</p><p><b>Вот главные функции:</b></p><ul><li>Гибкие доски задач (Kanban, Scrum) — 6 видов.</li><li>Списки задач с группировками и иерархией.</li><li>Автоматические отчеты (ежедневные/еженедельные сводки).</li><li>Умные напоминания (Telegram-бот, email).</li><li>Глубокая аналитика (время выполнения задач, загрузка команды).</li><li>Потоковая автоматизация процессов</li></ul><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/60e0eb0a-c1b1-473b-b343-1c4fb1b3fbd6.png" alt="" /><figcaption>Упрощенная схема сущностей системы</figcaption></figure><p>METEOR — мощный инструмент для автоматизации процессов, с триггерами, серверными функциями и ИИ-аналитикой. Он позволяет гибко настраивать сложные операции.</p><p>Все данные анализируются, и внутри самого сервиса можно понять, как работает команда: кто загружен, сколько времени уходит на задачи, где стопперы в процессе.</p><p>Разработчики используют METEOR для линковки задач с pull-requests и контроля бэклога. Менеджеры получают отчеты автоматически и не тратят часы, чтобы собрать всю информацию вручную. QA ведут тест-кейсы и баги в удобных досках, а DevOps отслеживают инциденты и шаги деплоя. Даже HR подключаются — через систему проходят кандидаты и стажеры.</p><h3>Переезд на сервис</h3><p>После внедрения METEOR команды замечают, что продуктивность их работы сильно повышается:</p><ul><li>скорость выполнения задач увеличивается в среднем на 25% — благодаря автоматическим напоминаниям и чётким процессам;</li><li>количество потерянных задач снижается на 70% — исчезают хаотичные чаты и забытые письма;</li><li>на составление отчетов и сбор метрик уходит не 5 часов, а всего 30 минут в неделю;</li><li>а экономия времени — около 75 часов в месяц на команду из 10 человек.</li></ul><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/f3f086f0-5846-4a35-adba-dc4fbe17f1c0.png" alt="" /><figcaption>Карточка задачи</figcaption></figure><p>METEOR не просто помогает контролировать задачи — он становится частью операционного «ядра» команды, избавляя от хаоса, ускоряя работу и делая все немного спокойнее.</p><h2>3. Gitlife</h2><p><a href="https://gitlife.ru">GitLife</a> — это универсальная платформа для хостинга репозиториев и построения полного DevOps-процесса внутри компании. Разработана с прицелом на закрытые контуры, импортозамещение и гибкость — подходит как для современных Git-проектов, так и для инфраструктур, где все еще используется SVN.</p><p>Помимо самого Gitlife, разработчики делают Gitlife AI, в котором есть доступ к топовым ИИ-моделям и инструментам, чтобы разрабатывать ИИ-решения.</p><h3>Что под капотом</h3><p>Внутри Gitlife собраны модули для:</p><ul><li>Кода — репозитории, ветвление.</li><li>Задач — бэклоги, спринты, дашборды.</li><li>Документации — совместное редактирование документов, управление правами.</li><li>Аналитики — карты потока ценности, качество кода, метрики по инженерам.</li><li>Пайплайны — модуль «Конвейер» управляет сборками и CI/CD.</li></ul><p>Gitlife используется ИТ-отделами и R&amp;D-подразделениями в компаниях с высокими требованиями к информационной безопасности. Подходит как для небольших команд, так и для распределённых корпораций с десятками проектов.</p><p>Что решает:</p><ul><li>Безопасный и полностью локальный хостинг исходного кода (Git + SVN)</li><li>Управление задачами и CI/CD в одном месте</li><li>Централизованный доступ, права, аудиты и история изменений</li><li>Полная поддержка DevOps-процессов на российском ПО</li><li>Альтернатива GitHub, GitLab и Bitbucket в условиях ограниченного доступа и санкционных рисков</li></ul><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/bb3f815f-ce07-4c9d-9243-7ace4b14d5ba.png" alt="" /><figcaption>Создание пайплайна</figcaption></figure><h3>Переезд на сервис</h3><p>Инструмент подходит практически всем командам — от инди-разработчиков до крупных проектов с множеством ролей. Например, гейм-девелопер может разрабатывать персональный проект и хранить код бесплатно, стартап — разрабатывать MVP, а крупный бизнес — управлять большими командами и проектами максимально безопасно.</p><h2>4. Cerebro</h2><p><a href="https://cerebrohq.com/ru/">Cerebro</a> — профессиональная система для совместной работы и управления проектами. Она помогает ставить задачи, планировать этапы, следить за выполнением, обмениваться файлами и комментировать их прямо в системе. Особенно полезна для команд, которые делают VFX, 3D, анимацию или дизайн — но при этом легко адаптируется и под другие команды.</p><p>Платформа охватывает полный цикл — от первых идей и планирования до комментирования финальных шотов (отдельных сцен или кадров в видео/анимации) и соблюдения дедлайнов. Такой подход помогает команде ускорить работу на 20%, сократить время на правки и держать весь процесс под контролем — от начала до сдачи проекта.</p><p>Cerebro подходит для команд от 1 до 1000+ человек с задачами на проекте от 1 до 10000+. Сервис работает с 2009 года, сейчас им пользуются более 400 команд в России и СНГ.</p><h3>Что под капотом</h3><p>Cerebro поддерживает десктоп (Windows, Mac и Linux), веб-версию и мобильное приложение, локальное, облачное и гибридное развертывание. Инструмент построен на клиент-серверной архитектуре — она гибкая, поэтому можно настраивать конфиги исходя из потребностей бизнеса.</p><p>Из технологий:</p><ul><li><b>Backend:</b> C, C++, Python, SQL</li><li><b>Frontend:</b> JS, TypeScript, ReactJS, Qt, PyQt</li><li><b>Мобильные клиенты:</b> React Native</li><li><b>БД: </b>PostgreSQL с проприетарными расширениями + SQLite</li><li><b>Файловое хранилище:</b> Cargador</li><li><b>Плагины: </b>Tentaculo (встраивается в производственные программы)</li></ul><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/d51c709f-2b9c-4e0b-95c0-2b78d61f5c1e.png" alt="" /></figure><p>Cerebro покрывает весь пайплайн — от идеи до финала. Вот основные возможности сервиса:</p><ul><li>Множественные уровни вложенности задач — подходит для сложных иерархий продакшена</li><li>Планирование с помощью диаграммы Ганта и специального инструмента «План»</li><li>Канбан-доска для визуального контроля задач</li><li>Инструмент «Моё пространство» — для персонализированной фильтрации и отбора задач</li><li>Уровни доступа и ролевое управление</li><li>Расширенная статистика по проектам, командам, сотрудникам</li><li>Совместная работа над большими файлами (видео, изображения, 3D) — Mirada позволяет комментировать, делать подрисовки, оставлять голосовые заметки</li><li>Интеграция с пакетами Adobe, Autodesk и мессенджерами, возможность встраивать в любые пайплайны</li><li>Удобное подключение фрилансеров</li></ul><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/5411ad9d-a74b-46ed-9774-61d60ff69885.png" alt="" /><figcaption>Навигатор и форум</figcaption></figure><h3>Переезд на сервис</h3><p>Обычно внедрение начинается еще на этапе препродакшена — во время разработки концепции и четкого плана действий. Затем система сопровождает проект на всех стадиях, вплоть до постпродакшена (финального тестирования и релиза), где она закрывает 80-100% задач. Cerebro позволяет собирать всё в одном месте: задачи, правки, сроки, бюджеты, загрузку сотрудников и статус проекта в целом.</p><p>После переезда снимается много рутинных задач: больше не нужно вручную назначать исполнителей, комментировать медиаконтент, передавать файлы между программами и так далее. В среднем проекты выполняются на 20% быстрее, без потери качества. В больших студиях объем выпускаемых шотов может вырасти до 20+ тысяч — это уже работает у других клиентов, среди которых СберМаркетинг, Sinners, Black Point и другие. Cerebro также помогает переехать с других такс-трекеров и систем для управления проектами.</p><h2>5. Replit Teams</h2><p><a href="https://replit.com/teams">Replit Teams</a> — это платформа для совместной разработки в реальном времени. По сути, это интегрированная IDE + git-репозиторий + система управления задачами — и все доступно через браузер. Подходит как для командной работы в стартапах, так и для образовательных проектов, хакатонов и небольших продуктовых команд.</p><p>А главная фишка — встроенный ИИ, к которому можно обращаться прямо во время написания кода. Инструмент разработан с акцентом на простоту входа, командную работу и быстрое прототипирование. Поддерживает более 50 языков программирования и позволяет запускать полноценные веб-приложения в облаке.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-26/70b00e25-9ec7-4858-911c-b2d7a181104e.png" alt="" /></figure><h3>Что под капотом</h3><p>Внутри Replit Teams есть интерфейсы для:</p><ul><li>Кода — редактор с автокомплитом, подсветкой синтаксиса, терминалом и интеграцией с Git.</li><li>Задач — встроенный таск-менеджер с распределением задач по участникам.</li><li>Общения — встроенные комментарии в коде, возможность ревью и обсуждений.</li><li>CI/DevOps — запуск и отладка приложений без настройки окружения.</li><li>Доступа — гибкие роли, приглашения по ссылке, настройки приватности.</li></ul><h3>Переезд на сервис</h3><p>Replit Teams позволяет моментально начать работу: не нужно ставить зависимости, конфигурировать CI или закупать сервера. Подходит как для быстрой прокачки навыков, так и для реальных командных проектов в продакшене.</p><p>Сейчас инструмент активно используют в стартапах — для быстрого MVP, парного программирования, хакатонов и удаленных командах — как замена локальным IDE и конфигурациям.</p><p>Рассказывайте в комментариях, какими сервисами пользуетесь вы.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как мы строим агрегатор финансовых продуктов в Казахстане: история Finance.kz</title>
      <link>https://tproger.ru/articles/kak-my-stroim-agregator-finansovyh-produktov-v-kazahstane--istoriya-finance-kz</link>
      <comments>https://tproger.ru/articles/kak-my-stroim-agregator-finansovyh-produktov-v-kazahstane--istoriya-finance-kz?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Жандос Байдильденов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-my-stroim-agregator-finansovyh-produktov-v-kazahstane--istoriya-finance-kz</guid>
      <description><![CDATA[<p>Как из обычного сайта-витрины вырастить финтех-продукт? Расскажу, как строится агрегатор финансовых продуктов в Казахстане.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-my-stroim-agregator-finansovyh-produktov-v-kazahstane--istoriya-finance-kz">Как мы строим агрегатор финансовых продуктов в Казахстане: история Finance.kz</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[SEO]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[HR]]></category>
      <category><![CDATA[Сервисы]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Финтех]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Техподдержка]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Дизайн]]></category>
      <category><![CDATA[Машин]]></category>
      <category><![CDATA[аналитик]]></category>
      <category><![CDATA[микро]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 22 Jun 2025 09:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Я — Жандос, руководитель проекта <a href="https://finance.kz">Finance.kz</a>. Последний год активно развиваем агрегатор финансовых продуктов в Казахстане, и за это время рынок изменился кардинально. Хочу поделиться опытом создания финтех-решения с нуля и рассказать, как мы конкурируем с зарубежными игроками за счёт глубокой локализации.</p><h2>Откуда началось: мой путь в финтех</h2><p>До Finance.kz я работал в казахстанских финтех-проектах — bai.kz и finbee.kz. Там понял, что рынок агрегаторов в Казахстане находится в зачаточном состоянии. Пользователи мучились, сравнивая предложения банков вручную, а сами финансовые организации не умели эффективно работать с цифровыми каналами привлечения клиентов.</p><p>Finance.kz существовал уже более 3 лет, но работал как классический сайт-витрина. Когда я пришёл в проект год назад, стало ясно: нужно полностью переосмыслить подход и создать технологическое решение нового поколения.</p><h2>Состояние рынка: от монополии к жёсткой конкуренции</h2><p>Казахстанский рынок агрегаторов финансовых продуктов переживает бум. Ещё два года назад здесь было 2-3 локальных игрока с простейшим функционалом. Сегодня ситуация кардинально изменилась — зашли зарубежные команды: vbr.kz, fintree.kz, moneypanda.com и другие.</p><p>Эти компании привозят готовые решения, которые хорошо работали в России или других рынках. На первый взгляд их продукты выглядят более зрелыми — красивые интерфейсы, отработанные воронки конверсии. Но у импортных решений есть критический недостаток: они плохо адаптированы под специфику казахстанского рынка.</p><h2>Технологические вызовы: интеграция с госсервисами и банками</h2><p>Главная боль при разработке агрегатора в Казахстане — это интеграции. В отличие от рынков СНГ, здесь нельзя обойтись простым партнёрским трафиком. Пользователи хотят получить продукт онлайн, полностью, без походов в офисы.</p><h3>API-интеграции с банками и МФО</h3><p>За год мы реализовали прямые API-интеграции с крупнейшими игроками рынка. Это означает, что пользователь может не просто сравнить условия кредитов, но и подать заявку, пройти скоринг и получить одобрение, не покидая наш сайт.</p><p>Техническая сложность — в разнородности API. Каждый банк использует собственные протоколы, форматы данных и методы аутентификации. Пришлось создать унифицированный слой-адаптер, который «переводит» наши запросы в формат, понятный конкретной финансовой организации.</p><h3>Интеграция с госорганами</h3><p>Finance.kz интегрируется с государственными сервисами eGov и Палаты предпринимателей (ПКБ). Это позволяет автоматически подтягивать справки о доходах, данные о трудоустройстве и другие документы, необходимые для получения кредита.</p><p>Работа с госAPI — отдельная история. Протоколы меняются, документация часто устаревает, а техподдержка работает в режиме «как получится». Но результат того стоит: время оформления кредита сокращается с нескольких дней до 15-30 минут.</p><h2>Архитектура продукта: ставка на микросервисы</h2><p>При проектировании архитектуры Finance.kz мы отказались от монолитного подхода в пользу микросервисов. Это решение диктовалось спецификой агрегатора — нам нужно было обеспечить высокую доступность при интеграции с десятками внешних систем.</p><h3>Backend</h3><p>Основные сервисы написаны на Node.js с использованием Express.js. Для работы с базами данных используем PostgreSQL для транзакционных данных и Redis для кэширования. Очереди сообщений реализованы через RabbitMQ — это критично для обработки заявок пользователей.</p><p>Отдельно выделили сервис интеграций, который отвечает за взаимодействие с внешними API. Он построен по принципу circuit breaker — если один из банков не отвечает, это не ломает работу всего агрегатора.</p><h3>Frontend</h3><p>Фронтенд построен на React с использованием Next.js для серверной отрисовки. Это важно для SEO — львиная доля трафика приходит из поисковых систем по запросам типа «кредит онлайн».</p><h2>Стратегия «единого окна»: от поиска до получения</h2><p>Наша главная идея — превратить агрегатор из витрины в полноценный финтех-сервис. Пользователь должен получить продукт полностью онлайн: от сравнения условий до перечисления денег на карту.</p><p>Для этого мы реализовали несколько ключевых функций:</p><ul><li>Умный подбор продуктов на основе данных пользователя и машинного обучения</li><li>Предварительный скоринг для оценки вероятности одобрения ещё до подачи заявки</li><li>Автоматическое заполнение анкет с подтягиванием данных из госсервисов</li><li>Отслеживание статуса заявки в реальном времени</li></ul><h2>Монетизация: CPS как основа устойчивого бизнеса</h2><p>Мы работаем по модели CPS (Cost Per Sale) — получаем комиссию только за успешно выданные продукты. Для кредитов это 2% от суммы, для микрозаймов — фиксированная сумма от 7 до 15 тысяч тенге.</p><p>Такая модель выгодна всем участникам: банки платят только за результат, пользователи получают продукт бесплатно, а мы мотивированы повышать качество трафика и конверсию.</p><h2>Конкурентные преимущества: локализация против красивых интерфейсов</h2><p>Главное отличие Finance.kz от зарубежных конкурентов — интеграция в казахстанскую финтех-экосистему. Пока они тратят месяцы на адаптацию готовых решений, мы изначально строили продукт под местную специфику.</p><h3>Что даёт локальный подход:</h3><ul><li>Знание рынка: мы понимаем особенности работы казахстанских банков и МФО</li><li>Связи с регуляторами: легче договариваться об интеграциях с госорганами</li><li>Техническая экспертиза: наша команда уже прошла путь интеграции с местными API</li><li>Гибкая настройка продукта под изменения законодательства и требований ЦБ</li></ul><h3>Результаты и метрики</h3><p>За год активного развития мы достигли:</p><ul><li>150 000 уникальных пользователей в месяц</li><li>Интеграция с 15+ финансовыми организациями</li><li>Конверсия заявка-выдача на уровне 35-40% (против 15-20% у конкурентов)</li><li>Средний чек по кредитам — 1,2 млн тенге, по микрозаймам — 85 тысяч</li></ul><h2>Планы на будущее: от агрегатора к экосистеме</h2><p>Ближайшие 1-2 года будут критичными для всего рынка. Мы видим несколько ключевых направлений развития:</p><h3>Расширение продуктовой линейки</h3><p>Планируем добавить страхование, депозиты и инвестиционные продукты. Цель — стать единой точкой входа для всех финансовых потребностей пользователя.</p><h3>Внедрение ИИ и машинного обучения</h3><p>Работаем над персонализацией предложений и предиктивной аналитикой. Хотим научиться предлагать продукты до того, как пользователь их осознанно найдет.</p><h3>Международная экспансия</h3><p>Рассматриваем возможность выхода на рынки Узбекистана и Кыргызстана. Наша технологическая платформа легко масштабируется на соседние рынки.</p><h3>Собственные финансовые продукты</h3><p>В долгосрочной перспективе планируем получить лицензию МФО и предлагать собственные займы наиболее качественным клиентам.</p><h2>Уроки и выводы</h2><p>Главный урок последнего года: в финтехе побеждает не тот, у кого красивее интерфейс, а тот, кто лучше решает реальные проблемы пользователей. Красивый дизайн можно скопировать за месяц, а на создание качественной технологической интеграции уходят годы.</p><p>Второй важный момент — команда решает всё. Мы собрали людей, которые понимают как технологии, так и специфику финансового рынка. Это даёт нам фору перед конкурентами, которые пытаются адаптировать готовые решения силами удалённых команд.</p><p>Рынок агрегаторов в Казахстане только формируется, и впереди ещё много интересных вызовов. Уверен, что следующие два года покажут, кто готов строить долгосрочный бизнес, а кто просто пытается заработать на хайпе.</p>]]></content:encoded>
    </item>
    <item>
      <title>Почему ваше приложение тормозит: архитектурные bottlenecks, которые никто не замечает</title>
      <link>https://tproger.ru/articles/pochemu-vawe-prilozhenie-tormozit--arhitekturnye-bottlenecks--kotorye-nikto-ne-zamechaet</link>
      <comments>https://tproger.ru/articles/pochemu-vawe-prilozhenie-tormozit--arhitekturnye-bottlenecks--kotorye-nikto-ne-zamechaet?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Татьяна Жукова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/pochemu-vawe-prilozhenie-tormozit--arhitekturnye-bottlenecks--kotorye-nikto-ne-zamechaet</guid>
      <description><![CDATA[<p>Как найти и устранить архитектурные bottleneck'и: причины тормозов, типовые ошибки и пошаговая методика диагностики.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/pochemu-vawe-prilozhenie-tormozit--arhitekturnye-bottlenecks--kotorye-nikto-ne-zamechaet">Почему ваше приложение тормозит: архитектурные bottlenecks, которые никто не замечает</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Парсинг]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[JSON]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Аналитика]]></category>
      <category><![CDATA[Дата-центр]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[аналитик]]></category>
      <category><![CDATA[микро]]></category>
      <category><![CDATA[OpenTelemetry]]></category>
      <category><![CDATA[Тимлид]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 19 Jun 2025 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Ваше приложение тормозит, хотя сервер мощный, код вроде нормальный, а метрики — не очень-то и загружены? Возможно, вы столкнулись с архитектурным bottleneck'ом — скрытым ограничением, которое убивает производительность под нагрузкой. Вместе с Никитой Ульшиным, тимлидом команды разработки архитектурных инструментов в Т-Банк и автором тг-каналов <a href="https://t.me/ulshinblog">Никита Ульшин про IT</a> и <a href="https://t.me/tech_fit">ТехнофITнес | Никита Ульшин</a>, разбираем типовые узкие места в системах, от неэффективной аллокации до однопоточных участков, и показываем пошаговый подход к их поиску и устранению.</p><h2>Почему «тормоза» — это не всегда про код</h2><p>Когда пользователь жалуется на «медленное приложение», большинство разработчиков по привычке лезет в код. Профилируем, ищем неэффективные циклы, оптимизируем до запятых. Но в реальности причина часто не в логике, а в том, что её окружает.</p><p>Современное приложение — это сложный организм: десятки микросервисов, базы данных, кэши, брокеры, сети. И тормозить может любой из этих компонентов. А ты в это время профилируешь ни в чём не виноватый for.</p><p>Вот что тормозит, когда код ни при чём:</p><p><b>Архитектура</b></p><ul><li>Цепочки вызовов: каждый хоп — +latency.</li><li>Синхронные зависимости: завис один сервис — тормозят все.</li><li>Общие ресурсы: shared state, глобальные очереди, блокировки.</li></ul><p><b>Базы данных</b></p><ul><li>Нет индексов или плохой план запроса.</li><li>N+1-запросы — особенно при неосторожной ORM.</li><li>Частый доступ к одним таблицам — гонка за IO и блоки.</li><li>Нет шардинга или репликации в масштабируемой нагрузке.</li></ul><p><b>Сеть</b></p><ul><li>Высокая задержка между регионами (особенно multi-cloud).</li><li>Потери пакетов, DNS-проблемы, нестабильность.</li><li>Отсутствие connection reuse: TLS-handshake на каждый вызов.</li></ul><p><b>Очереди и брокеры</b></p><ul><li>Один медленный потребитель тормозит всех.</li><li>Нет защиты от от backpressure или дедупликации.</li><li>Неправильные batch- и ack-настройки.</li></ul><p><b>Аллокаторы и GC</b></p><ul><li>Фрагментация памяти, нагрузка на GC.</li><li>Финалайзеры, слабые ссылки, плохо настроенный heap.</li></ul><p><b>Конфигурации</b></p><ul><li>Маленький пул соединений — пул заканчивается, запросы ждут.</li><li>Агрессивные retry-политики — система перегружается.</li><li>CPU throttling в Kubernetes — контейнеру не хватает ресурсов.</li></ul><p><b>Сериализация и форматы</b></p><ul><li>Перегруженные JSON/Protobuf.</li><li>Вложенные base64 + gzip + JSON — боли сериализации.</li></ul><h3>Почему bottlenecks остаются невидимыми до продакшена</h3><p>Многие из них —  не баги, а последствия архитектурных решений. В дев-среде всё летает, потому что:</p><ul><li>мало данных,</li><li>нет конкуренции за ресурсы,</li><li>нет распределённой нагрузки.</li></ul><p>В проде начинается хаос. Рассмотрим на конкретных примерах:</p><ul><li>На деве — 2 запроса в секунду, в проде — миллион. Кто-то запустил маркетинговую рассылку, аналитик тащит big report, а клиент ретраит ошибки.</li><li>Архитектура «в лоб»: всё синхронно, каждый сервис вызывает по цепочке десяток других. Пока нагрузка мала — все живет. Как только одно звено не выдерживает — перестает жить.</li><li>Иллюзия масштабируемости: до 100 пользователей —  всё окей, на 1000 — каскадная деградация.</li><li>«Невидимая боль» между сервисами: каждый по отдельности быстрый, но блокирует друг друга на базе.</li></ul><h2>Слои архитектуры и где в них зарыты проблемы</h2><p>Когда приложение тормозит, мы часто копаемся в отдельных компонентах — фронте, бэке, базе. Но bottleneck может быть не внутри компонента, а между ними: в конфигурации, связях, сетевых задержках, очередях вызовов.</p><p>Чтобы понять, где искать узкое место, нужно пройтись по всей цепочке — от клиента до внешних сервисов.</p><h3>На уровне клиента</h3><ul><li>Тяжёлые бандлы. Огромный JS, куча аналитики, трекеры — и вот страница загружается 8 секунд на старом телефоне.</li></ul><ul><li>Чрезмерные запросы. После загрузки — 10 fetch в секунду. API захлёбывается, браузер — тоже.</li></ul><ul><li>Нет кеша. Даже статичные справочники каждый раз грузятся заново.</li></ul><ul><li>Лаги рендеринга. Бэкенд дал ответ за 100ms, но интерфейс лагает — сложные таблицы, графики, reflow.</li></ul><h3>На уровне API Gateway / BFF</h3><ul><li>Непрозрачная маршрутизация —  скрытые таймауты, ошибки ретраев.</li><li>Сборка ответов из микросервисов — цепочка из 5 вызовов на каждый клиентский запрос.</li><li>Синхронные вызовы без деградации — если один микросервис недоступен, падает весь endpoint.</li></ul><h3>На уровне Backend / бизнес-логики</h3><ul><li>Цепочки вызовов. Один сервис вызывает другой, тот — ещё один… И так далее.</li><li>Ограниченный пул соединений —  сервис готов работать, но все worker-ы ждут ресурсы.</li><li>Логика в цикле по данным —  N+1-запросы, сериализация, дублирование работы.</li></ul><h3>На уровне базы данных / кэша</h3><ul><li>Full table scan. Работает быстро на 1000 строках. Умирает на 10 миллионах.</li></ul><ul><li>Локи и гонка за ресурсы. Один UPDATE лочит строку, остальные ждут.</li></ul><ul><li>Кэш не промахивается, но и не помогает —  stale данные, повторные чтения, инвалидация не работают, как ожидалось.</li></ul><p><b>На уровне внешних сервисов (API, брокеры, платёжки)</b></p><ul><li>Без timeout и fallback. Внешний API залип — вы вместе с ним.</li><li>QPS-лимиты. Вы не знали, а вас уже зарейт-лимитили.</li><li>Нестабильная сеть. DNS тормозит, соединения рвутся, задержки скачут между регионами.</li></ul><p>Оптимизация кода важна. Но если система тормозит, искать нужно по всей цепочке —  от кнопки на фронте до брокера в соседнем дата-центре.</p><blockquote>Есть архитектурные паттерны риска, которые часто приводят к тормозам через полгода. Во-первых, общий ресурс на всех: один Redis, один Kafka-топик, одна таблица. Пока нагрузка маленькая — ок. Потом начинается бойня за доступ. Во-вторых, микросервисный монолит: формально разделены, но живут как один. Масштабировать невозможно. В-третьих, цепочки вызовов: 5–6 сервисов на путь одного запроса. Один подвис — и вся цепочка тормозит.</blockquote><h2>Базы данных: когда даже SELECT тормозит всё</h2><p>В бэкенде одна из самых коварных фраз — «ну это же просто SELECT, что с ним будет». Ответ — сначала ничего. Но со временем появляется 10 миллионов строк, и ваш innocuous SELECT превращается в full scan, который лочит диск, ест CPU и тормозит всё, что движется.</p><p>Типичная ситуация: в таблицу пишут JSONB без нормализации и индексов. Пока строк мало — жить можно. Но при росте объёмов каждый запрос превращается в медленное последовательное чтение. Это не просто «долго», а начинает мешать соседним транзакциям: подгружаются stale данные, вытесняется кэш, система начинает проседать вся — вплоть до API.</p><p>Отдельная ловушка — смешение OLTP и OLAP нагрузки. Днём — INSERT и UPDATE от клиентов, ночью — фоновая аналитика, высчитывающая отчёт за год с десятком JOIN и чтением всей таблицы. Если все запросы идут в один инстанс, происходит конкуренция за ресурсы: аналитика сбивает кеш, вызывает блокировки, а продакшн-метрики начинают сыпаться.</p><h3>Как понять, что тормоза идут из базы, а не из бэкенда?</h3><p>Вот несколько типичных признаков:</p><ul><li>Бэкенд «висит» на запросах к базе — в логах видно, что приложение ждёт результат запроса (долгий await, query(), findMany() и т. д.).</li></ul><ul><li>Latency растёт, а CPU и память в норме — сервис сам по себе не перегружен, но ответ приходит медленно.</li></ul><ul><li>В базе фиксируются медленные запросы — например, SELECT или JOIN занимают секунды, хотя раньше проходили за миллисекунды.</li></ul><p>Чтобы подтвердить гипотезу, можно:</p><ul><li>Включить логирование SQL-запросов с таймингом;</li><li>Посмотреть трейс: если шаг DB заметно длиннее остальных — это тревожный сигнал;</li><li>Запустить EXPLAIN ANALYZE на типовые запросы и проверить, где зарыта сложность.</li></ul><h3>Архитектура доступа к данным: как закладываются тормоза</h3><p>Архитектура доступа к данным — фундамент, на котором держится производительность всей системы. Даже идеальный алгоритм или быстрый backend не спасут, если данные извлекаются медленно, неэффективно или избыточно.</p><p>На что здесь стоит обратить внимание:</p><ul><li><b>Количество и характер обращений. </b>Один пользовательский запрос не должен порождать десятки обращений к базе. Планирование кэшей, агрегаций, prefetch, нормализация — всё это часть архитектуры.</li><li><b>Уровень абстракции над данными.</b> ORM может быть удобным, но часто скрывает десятки неэффективных SELECT’ов. Нужно понимать, какие запросы реально идут в базу, и не превращать каждый use case в потенциальный bottleneck.</li><li><b>Стратегия хранения и агрегаций.</b> Если вы пытаетесь на лету агрегировать 100 миллионов строк без шардинга и партиционирования — тормоза неизбежны. Архитектура должна учитывать нагрузку, частоту агрегаций, структуру данных.</li></ul><h2>Очереди и событийные системы: скрытые замедления</h2><p>Событийные архитектуры и очереди вроде Kafka или RabbitMQ часто воспринимаются как универсальное средство от всех проблем: «Сделаем асинхронно — и всё полетит». Но это не серебряная пуля. Очередь не решает проблему — она её откладывает. А иногда сама становится bottleneck’ом.</p><p>Типовые ситуации, когда очередь тормозит систему:</p><ul><li><b>Медленные консьюмеры.</b> Очередь принимает сообщения с высокой скоростью, но обработчики не успевают, и начинается накопление беклога. В результате задержки накапливаются, SLA летит, а вы только недоумеваете, почему всё так тормозит.</li><li><b>Неправильный размер batch’ей.</b> В Kafka и аналогах размер и частота batch'ей критичны. Слишком большие — ждём, пока соберётся партия. Слишком маленькие — тратим ресурсы на лишнюю загрузку и пересылку. В итоге получаем либо высокую задержку, либо низкую производительность.</li><li><b>Неравномерное партиционирование</b>. Если сообщения распределяются по партициям неравномерно, то часть узлов будет простаивать, а часть захлёбываться под нагрузкой. Одна горячая партиция может стать bottleneck’ом всей системы.</li><li><b>Проблемы с retry.</b> Если нет чёткой стратегии повторных попыток, логирования и DLQ (dead-letter queue), битое событие может крутиться в системе бесконечно, тормозя всё остальное. Также если что-то пошло не так и включились retry-механизмы, задержка может вырасти в разы. Особенно если есть backoff или дедлоки.</li></ul><h3>Почему «асинхронно» ≠ «мгновенно»</h3><p>Асинхронность — это не про скорость, а про отложенность. Событие отправлено — но когда оно будет обработано, никто не знает. Через секунду, через минуту, через час?</p><p>Типичные причины задержек в асинхронной обработке:</p><ul><li>Очередь перегружена, и событие просто стоит в хвосте.</li><li>Обработчик падает или рестартится, и событие ждёт.</li><li>Политики повторной обработки не настроены — событие крутится бесконечно.</li><li>Слишком много этапов внутри одного воркера: валидация, запись в БД, вызов API — всё это задержки, которые суммируются.</li></ul><h3>Что происходит, когда очередь переполнена — и почему это тяжело заметить</h3><p>Когда очередь переполняется, события в ней начинают задерживаться или теряться, в зависимости от конфигурации. Но хуже всего то, что это происходит тихо. Формально система работает: события принимаются, воркеры их обрабатывают. Но обработка начинает всё сильнее и сильнее отставать.</p><p>Очередь продолжает принимать события, но обрабатываются они с огромным отставанием. Новое сообщение встаёт в хвост и ждёт своей очереди десятки секунд или минут. Такую ситуацию легко не заметить, если у вас не настроен грамотный мониторинг.</p><p>Для защиты от переполнения очереди нужно мониторить несколько важных показателей:</p><ul><li>Глубина очереди (в Kafka — consumer lag, в RabbitMQ — queue.messages_ready или queue.messages): показывает, сколько сообщений накопилось и ждёт обработки.</li><li>Publish rate: сколько сообщений в секунду публикуется.</li><li>Processing rate: сколько сообщений в секунду обрабатывается.</li><li>Утилизация ресурсов воркеров (CPU, memory, I/O).</li><li>End-to-end latency: сколько времени проходит от публикации события до его обработки.</li></ul><blockquote>Есть самые частые причины накопления сообщений:<br /><br />1) Медленные потребители, которые не успевают обработать весь поток сообщений. <br />2) Шумная архитектура: 10 сообщений там, где можно было обойтись и одним. <br />3) Неправильное масштабирование брокера (например, неправильно выбранный ключ партиционирования в Kafka).</blockquote><h2>API и микросервисы: где тормозит взаимодействие</h2><p>Микросервисная архитектура на бумаге выглядит идеально: каждый сервис автономен, команды независимы, масштабирование прозрачно. Но на практике все часто превращается в болото синхронных вызовов, где каждый сервис делает запросы в пять других.</p><h3>Что тормозит в микросервисах?</h3><p><b>Много лишних вызовов</b></p><p>Один пользовательский запрос вызывает лавину внутренних HTTP/gRPC-запросов. Каждый из них — это:</p><ul><li>сетевые задержки (latency),</li><li>сериализация/десериализация данных,</li><li>ретраи при сбоях,</li><li>риск таймаутов и обрывов.</li></ul><p>Если таких вызовов много, задержка накапливается каскадом.</p><p><b>Цепочка зависимостей</b></p><p>Классика жанра: заказ зависит от оплаты, оплата — от биллинга, биллинг — ещё от трёх микросервисов.</p><p>Если тормозит хотя бы один, сыплется всё. Получается эффект домино и каскадные отказы, где неполадка в одном звене рвёт всю цепочку.</p><p><b>Нет fallback'а и таймаутов</b></p><p>Если вызов к нужному сервису не отвечает, а у вас нет fallback'а или таймаута — всё встаёт. Особенно плохо, если вызов синхронный и блокирует поток.</p><p><b>Наносервисы</b></p><p>Иногда архитекторы увлекаются и дробят сервисы до абсурда. Начинается с «давайте переиспользуем», а заканчивается 10 API-вызовами ради одного действия.</p><p>Вместо бизнес-логики система начинает заниматься оркестрацией — координирует сама себя.</p><h2>Кэш и CDN: почему «ускорители» тоже могут тормозить</h2><p>Кэш традиционно считается палочкой-выручалочкой для производительности. «Добавим кэш — и всё полетит», — думают команды, особенно под нагрузкой. Но в реальности кэш может не только не ускорить, но и наоборот — замедлить или даже уронить систему, если использовать его без понимания.</p><p>Вот несколько типичных ошибок, которые приводят к деградации производительности:</p><h3>Невалидные или слишком агрессивные стратегии</h3><p>Если кэш живёт слишком долго — пользователи получают устаревшие данные. Если обновляется слишком часто — создаются лишняя нагрузка на базу и почти постоянные «промахи».</p><p>А если кэш сбрасывается при каждом изменении, он вообще не успевает выполнять свою роль.
В итоге: нестабильное поведение, непредсказуемая производительность и раздражённые пользователи.</p><h3>Перегрузка кэша</h3><p>Когда кэш настолько загружен, что сам по себе начинает тормозить — это уже не помощь, а вред. Такое часто случается с Redis, Memcached или in-memory решениями, особенно если они:</p><ul><li>работают на одной машине без шардинга;</li><li>содержат тяжёлые объекты;</li><li>используются как универсальный ответ на все проблемы.</li></ul><p>В таких случаях кэш из «ускорителя» может падать под нагрузкой быстрее, чем база данных.</p><h3>Кэш-миссы и лавины запросов</h3><p>Классическая проблема: TTL у популярных ключей истекает одновременно, и тысячи запросов идут мимо кэша в базу.
Это может случиться после деплоя, сброса кэша или при пиковой нагрузке.</p><p>Вместо одного запроса вы получаете 10 000, и тормозит всё. Особенно критично это для систем с CDN, где внезапное истечение кэша популярных страниц может вызвать DDoS-подобную нагрузку.</p><h3>Несогласованность кэшей</h3><p>Если в системе несколько уровней кэширования (CDN, фронтовый кэш, Redis) и они обновляются по-разному, возникает несогласованность. Один пользователь видит новые данные, другой — старые. Один сервис работает с актуальной версией, другой — с устаревшей. Начинается охота за фантомами: баги вроде есть, но не у всех.</p><h2>Вычисления и память: что не так с аллокацией</h2><p>Почему мощные сервера с десятками ядер и гигабайтами памяти тормозят? Потому что производительность упирается не в «железо», а в то, как именно это железо используется. От плохого управления памятью до блокирующих операций — в системе может быть множество узких мест, которые незаметно душат throughput.</p><h3>Частые и крупные аллокации</h3><p>Каждый раз, когда создаётся новый объект, память выделяется из кучи. При интенсивной работе (например, при парсинге JSON или обработке больших структур) это может происходить слишком часто, и тогда запускается сборщик мусора (GC).</p><p>Сборка мусора — это не бесплатная операция: она останавливает выполнение программы. Частые GC-паузы создают «плавающую» производительность, когда всё работает быстро, но время от времени подвисает даже на секунду.</p><h3>GC-паузы</h3><p>Автоматическое управление памятью — благо, но оно же и ловушка. В языках вроде Go, Java или Python невозможно точно контролировать, когда сработает сборщик. Даже задержка в 50 миллисекунд может ощутимо ударить по RPS или нарушить real-time обработку.</p><p>Некоторые компании придумывают обходные пути. Так, в Twitch <a href="https://blog.twitch.tv/en/2019/04/10/go-memory-ballast-how-i-learnt-to-stop-worrying-and-love-the-heap/">использовали</a> технику memory ballast для Go-приложений, чтобы стабилизировать поведение GC и избежать всплесков пауз.</p><h3>Синхронные и блокирующие операции</h3><p>Если операции с файлами, сетью или базой данных выполняются синхронно, поток блокируется — и в это время не может обрабатывать другие задачи. Это особенно критично, если такой поток обслуживает пользовательские запросы.</p><p>Классический пример — однопоточный web-сервер, который просто ждёт ответ от другой системы. Или библиотека, использующая mutex.Lock() в горячем участке, из-за чего десятки горутин встают в очередь.</p><h3>Ограниченные ресурсы и очереди</h3><p>Даже в многопоточном приложении можно легко создать узкое место. Например:</p><ul><li>доступ к объекту синхронизирован через mutex или RW-lock;</li></ul><ul><li>пул соединений имеет жёсткий лимит;</li></ul><ul><li>очередь задач не может масштабироваться под нагрузку.</li></ul><p>В таких случаях нагрузка растёт, а пропускная способность — нет. Всё упирается в искусственное ограничение, введённое «на всякий случай».</p><h3>Однопоточные горячие участки</h3><p>Даже в многопоточной архитектуре может оказаться, что 80% времени тратится в одном месте, которое не масштабируется — например, в процессе сериализации, расчёта или шифрования. Иногда этот участок реализован однопоточно (особенно в Node.js, Python или старом Go-коде) и становится главным тормозом системы. Под высокой нагрузкой это становится особенно заметно.</p><h2>Что делать: подход к поиску и устранению bottlenecks</h2><p>Когда система тормозит, возникает соблазн сразу «что-то сделать» — добавить кэш, увеличить ресурсы, ускорить базу. Но без системного подхода можно попасть в замкнутый круг, где проблема будет возвращаться снова и снова. Чтобы избежать этого, лучше использовать пошаговую стратегию: зафиксировать симптомы, локализовать проблему, подтвердить и только потом оптимизировать.</p><h3>Шаг 1. Зафиксировать симптомы и контекст</h3><p>Важно понять, что именно «тормозит» и в каких условиях. Типовые вопросы:</p><ul><li>Когда проявляется проблема: под нагрузкой, в пиковые часы, у конкретных пользователей?</li><li>Что именно работает медленно: API, отчёты, фоновая обработка?</li><li>Как это влияет на систему: задержки, таймауты, ошибки?</li></ul><p>Пример: пользователи жалуются, что отчёты стали строиться дольше. Вчера — 5 секунд, сегодня — 20.</p><h3>Шаг 2. Проверить очевидные метрики</h3><p>Начинаем с того, что уже доступно в мониторинге:</p><ul><li>латентность API (p50, p95, p99);</li><li>загрузка CPU, использование памяти и диска;</li><li>глубина очередей и backlog;</li><li>паузы GC;</li><li>кэш hit/miss ratio.</li></ul><p>Если латентность растёт, а CPU загружен на 20%, это может указывать на проблемы в синхронных вызовах, аллокации или неэффективной работе кэша.</p><h3>Шаг 3. Сузить область через трейсинг и логирование</h3><p>Чтобы выяснить, не «тормозит» ли другой сервис, используйте распределённый трейсинг (Jaeger, OpenTelemetry, Sentry Performance) или логирование таймингов внутри запроса.</p><p>Пример (Go, обёртка для http.RoundTripper):</p><p>Так можно увидеть, что, например, сторонний сервис отвечает 3 секунды, или Redis стал отдавать ответ за 300 мс вместо обычных 3 мс.</p><h3>Шаг 4. Воспроизвести нагрузку</h3><p>Если проблема зависит от объёма данных, воспроизведите реальный сценарий или нагрузку:</p><ul><li>используйте инструменты вроде k6, Locust, Artillery;</li><li>проверьте реальные условия (например, отчёт за 30 дней, а не за 3).</li></ul><p>Если латентность растёт нелинейно, это может быть признаком N+1-запросов, неоптимального SQL или проблем с аллокацией.</p><h3>Шаг 5. Подтвердить проблему профилированием</h3><p>Если есть подозрение на перегрузку CPU или утечку памяти, используйте профилировщики:</p><ul><li>Go — pprof;</li></ul><ul><li>Java — JFR, VisualVM;</li></ul><ul><li>Node.js — clinic.js, chrome://inspect.</li></ul><p>Пример (Go, локальный pprof):</p><p>Профиль покажет, где тратится CPU, как распределяются аллокации, как часто запускается GC и в каком коде.</p><h3>Шаг 6. Оптимизировать, перепроверить и зафиксировать</h3><p>После подтверждения проблемы можно переходить к исправлениям:</p><ul><li>кэшировать внешний вызов;</li></ul><ul><li>заменить тяжёлую сериализацию (например, на easyjson в Go);</li></ul><ul><li>убрать блокирующую операцию;</li></ul><ul><li>перейти на batch-обработку.</li></ul><p>После изменений обязательно перепроверьте метрики, чтобы убедиться, что оптимизация действительно дала эффект — и не добавила новых узких мест.</p><h2>Итоги: как не попасть в архитектурную ловушку</h2><p>Bottleneck’и возникают даже в самых технологичных проектах. Это не всегда следствие «плохого кода» — куда чаще они появляются из-за архитектурных просчётов: недооценили нагрузку, не предусмотрели масштабирование, сделали ставку на неправильное решение.</p><p>Главная проблема в том, что узкие места проявляются не сразу, а под нагрузкой. В разработке и тестах всё летает, потому что ресурсов хватает. А вот в проде — особенно при росте аудитории — на поверхность всплывает всё, что не выдерживает реального объёма данных или конкуренции за ресурсы.</p><p>Чтобы избежать таких проблем, важно не просто писать чистый код, а мыслить системно: проектировать архитектуру так, чтобы она могла жить под давлением. Вот несколько принципов, которые помогут.</p><h3>Думайте об объёмах и росте</h3><p>Архитектура должна выдерживать не только текущую нагрузку, но и разумный рост. Строить систему на «миллиард пользователей» с первого дня — избыточно. Но если вы рассчитываете, что пользователей станет в 5 раз больше через год — закладывайте это сейчас. Узкие места не любят сюрпризов.</p><h3>Настраивайте метрики с первого дня</h3><p>Без данных вы работаете вслепую. Любой анализ будет превращаться в гадание: виноват код или Redis, где тормозит — непонятно. С самого начала следите за ключевыми метриками: latency, очереди, GC, hit/miss в кэше, ошибки. Это не «доработка потом», а часть живой системы.</p><h3>Проектируйте с учётом деградации</h3><p>Любая система может начать тормозить — вопрос в том, как она себя при этом поведёт. Важно предусмотреть мягкую деградацию: таймауты, очереди, резервные сценарии, ограничение входящего трафика.</p><h3>Не усложняйте архитектуру без повода</h3><p>Микросервисы, шины, очереди, event-driven — всё это мощные инструменты. Но сложность должна быть оправданной. Простая, понятная архитектура с мониторингом и масштабируемыми точками зачастую выигрывает у перегруженной модульной схемы, которую никто не может отладить.</p><p>Bottleneck’и — это не баг, а симптом. И хороший архитектор — это не тот, кто устраняет тормоза, а тот, кто их не допускает.</p><p>Больше боли коддеров — <a href="https://t.me/+ezugB7gnIEsxNGMy">тут</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как разработать простое приложение и тратить на него 2000$ в месяц</title>
      <link>https://tproger.ru/articles/kak-razrabotat-prostoe-prilozhenie-i-tratit-na-nego-2000--v-mesyac</link>
      <comments>https://tproger.ru/articles/kak-razrabotat-prostoe-prilozhenie-i-tratit-na-nego-2000--v-mesyac?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Ельцова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-razrabotat-prostoe-prilozhenie-i-tratit-na-nego-2000--v-mesyac</guid>
      <description><![CDATA[<p>Разработчик показывает, как из простого приложения сделать дорогостоящее — за счет трат на сервера, инфраструктуру и многое другое.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-razrabotat-prostoe-prilozhenie-i-tratit-na-nego-2000--v-mesyac">Как разработать простое приложение и тратить на него 2000$ в месяц</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[Cloudflare]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Kubernetes]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Grafana]]></category>
      <category><![CDATA[VPS]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 06 Jun 2025 12:31:20 GMT</pubDate>
      <content:encoded><![CDATA[<p>Да, из простого приложения можно создать такую архитектуру, которая будет стоить более $2000 в месяц. В этом <a href="https://www.youtube.com/watch?v=nYlv0I9Ips0">видео</a> разработчик показывает, как пошагово усложнять инфраструктуру простого приложения для задач, добавляя все больше и больше компонентов. А чтобы вам было проще сориентироваться — мы адаптировали его на русский.</p><h2>Этап 1: Простой MVP за $0</h2><p>Изначально нужно создать максимально простое приложение для управления задачами: веб-страница с несколькими категориями и возможностью добавлять таски. На фронте разработчик использует библиотеку Shad CN UI, а на бэке — Flask. Данные хранятся просто в словаре Python. Все приложение — это один файл. Оно запускается в Docker-контейнере через docker compose.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-06/b4963af4-3c83-4697-a8be-848850a1ee82.png" alt="" /></figure><p>Это MVP работает локально на компьютере разработчика и рассчитано на одного пользователя. Такой подход позволяет всё упростить, но при перезапуске контейнера все данные будут утеряны. Правда, чтобы получить прибыль, этого недостаточно.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-06/cced47f6-e8b2-40dd-a540-476a233e217e.png" alt="" /></figure><h2>Этап 2: Инфраструктура на $30</h2><p>Чтобы приложение стало более удобным и приближенным к продакшену, разраб добавляет несколько главных компонентов:</p><ul><li><b>База данных:</b> PostgreSQL.</li><li><b>Веб-сервер:</b> Nginx для проксирования запросов и избавления от портов.</li><li><b>Аутентификация: </b>авторизация через JWT и интерфейсы входа и регистрации на фронтенде.</li></ul><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-06/e903bb53-7168-4485-9d3a-a11f68015901.png" alt="" /></figure><p>Все эти компоненты подключаются к существующему приложению через docker-compose.yml. Благодаря этому теперь можно запускать приложение на localhost, а не по порту, и все будет работать как полноценное веб-приложение. Размещение такой системы на бесплатном облачном тарифе или VPS обойдётся в пределах 30 долларов.</p><h2>Этап 3: Допиливание до $100</h2><p>Разработчик добавляет допфункции:</p><ul><li><b>Уведомления по email:</b> вместо стороннего API — система очередей с RabbitMQ и воркером, который отправляет письма.</li><li><b>Обновления в реальном времени:</b> WebSocket’ы для синхронизации задач на разных устройствах.</li><li><b>Кэширование:</b> Redis для хранения данных в памяти, что значительно ускоряет работу.</li></ul><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-06/4a4ace77-7e6c-4e2f-b8a3-03943a57254a.png" alt="" /></figure><p>Также подключается Docker Build Cloud — сервис для параллельной сборки контейнеров и шаринга кеша между проектами. Это повышает производительность и ускоряет разработку.</p><p>Теперь в системе работает очередь заданий, кеш, WebSocket-сервер, SMTP и полноценная база данных. Все эти компоненты увеличивают инфраструктурные затраты до примерно $100 в месяц.</p><h2>Этап 4: Мониторинг и масштабирование за $500</h2><p>Разработчик внедряет:</p><ul><li><b>Логирование:</b> стек ELK (Elasticsearch, Logstash, Kibana).</li><li><b>Мониторинг:</b> Prometheus и Grafana.</li><li><b>Горизонтальное масштабирование:</b> добавляются реплики некоторых сервисов и балансировка нагрузки через Nginx.</li></ul><p>Эти инструменты позволяют отслеживать метрики, визуализировать логи, следить за состоянием приложений и быстро выявлять сбои. Инфраструктура становится ближе к корпоративному уровню. Стоимость такого комплекса достигает уже около 400–500 долларов в месяц.</p><h2>Этап 5: Целых $2000 долларов</h2><p>Разработчик решает перейти на Kubernetes и развернуть приложение в разных дата-центрах по всему миру. Для управления трафиком используется глобальный балансировщик нагрузки (например, Cloudflare). А чтобы все работало стабильно, внедряются:</p><ul><li>Postgres с репликацией в реальном времени,</li><li>Redis для быстрой отдачи данных.</li></ul><p>Сейчас без ИИ приложение уже не имеет смысла. Поэтому разработчик решает добавить серверлесс-функции с категоризацией задач, приоритетами и предложениями на основе поведения пользователей, а также с использованием обработки естественного языка для создания тасков. Также подключаются правовые ограничения — поддержка GDPR и CCPA.</p><p>Для мониторинга:</p><ul><li>Jaeger — для распределённой трассировки,</li><li>ELK Stack — для логов,</li><li>Grafana + PagerDuty — для алертов и инцидентов.</li></ul><p>Вишенка на торте — план восстановления после катастроф: бэкапы, автоматическое переключение на другие регионы и подготовка команды.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-06-06/399735e5-c8d1-4003-9ed2-91d9e5b83764.png" alt="" /></figure><p>Хотя вся система началась с одного Python-файла и простого UI, шаг за шагом разработчик добавлял в нее компоненты, которые делают её пригодной для реального продакшена: защита, масштабируемость, отказоустойчивость, мониторинг, кеширование и очереди. И оно вполне может стоить несколько сотен или даже тысяч долларов в месяц при развертывании в облаке.</p>]]></content:encoded>
    </item>
    <item>
      <title>Valkey оказался быстрее Redis: до +37 % в SET и −60 % по задержкам в GET</title>
      <link>https://tproger.ru/news/valkey-okazalsya-bystree-redis--do--37---v-set-i--60---po-zaderzhkam-v-get-256233</link>
      <comments>https://tproger.ru/news/valkey-okazalsya-bystree-redis--do--37---v-set-i--60---po-zaderzhkam-v-get-256233?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/valkey-okazalsya-bystree-redis--do--37---v-set-i--60---po-zaderzhkam-v-get-256233</guid>
      <description><![CDATA[<p>Valkey обогнал Redis: до +37% в SET и −60% по задержкам в GET — Amazon помог форку выжать максимум из многопоточности и сетевых оптимизаций</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/valkey-okazalsya-bystree-redis--do--37---v-set-i--60---po-zaderzhkam-v-get-256233">Valkey оказался быстрее Redis: до +37 % в SET и −60 % по задержкам в GET</a>»</p>]]></description>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Amazon]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 04 Jun 2025 12:22:50 GMT</pubDate>
      <content:encoded><![CDATA[<p>Независимое тестирование последних версий Redis 8.0 и форка Valkey 8.1 <a href="https://www.gomomento.com/blog/valkey-turns-one-how-the-community-fork-left-redis-in-the-dust/">показало</a>: форк теперь не просто не уступает оригиналу, но и значительно его опережает.</p><p>Благодаря переданному Amazon коду для многопоточной обработки I/O, Valkey добился серьезного прироста производительности.</p><h2>Что показали тесты</h2><p>Тесты проводились на AWS-инстансе Graviton4 c8g.2xlarge с 8 виртуальными ядрами. Valkey 8.1.1 продемонстрировал:</p><ul><li><b>999,8 тыс SET-запросов в секунду</b> — против 729,4 тыс у Redis;</li><li><b>+37% к производительности в SET и +16% в GET</b>;</li><li><b>−30% по задержкам в SET и −60% в GET</b>.</li></ul><p>Особенно заметен прирост при увеличении числа I/O-потоков: при шести потоках Valkey выдает 678 тыс SET-запросов/сек против 563 тыс у Redis (при 256 соединениях). А при 400 соединениях — уже 832 тыс.</p><figure><img src="https://media.tproger.ru/user-uploads/111041/2025-06-04/cfcd6260-5e21-488c-ba71-1d9645da8c5f.jpeg" alt="" /></figure><h2>Как достигли такого результата</h2><p>Оптимизация прошла не только на уровне кода, но и на уровне системной настройки:</p><ul><li>Сократили количество переключений контекста, выделив два ядра под обработку прерываний.</li><li>Остальные шесть ядер были закреплены за I/O-потоками Redis и Valkey.</li><li>Использовали ethtool и smp_affinity, чтобы точно задать, какие ядра обрабатывают сетевые IRQ.</li></ul><p>В итоге система позволила выжать максимум из архитектуры и достичь почти <b>миллиона SET-запросов в секунду</b>.</p><h2>Вывод</h2><p>Valkey — больше не просто «альтернатива Redis». Это полноценный, производительный форк с активной поддержкой сообщества и промышленной оптимизацией от крупных игроков вроде Amazon.</p><p>И если раньше Valkey рассматривали как запасной аэродром после смены лицензии Redis, то теперь он становится приоритетным выбором для высоконагруженных систем.</p>]]></content:encoded>
    </item>
    <item>
      <title>Вышел Redis 8: популярный инструмент для кэширования снова open-source</title>
      <link>https://tproger.ru/news/vywel-redis-8--populyarnyj-instrument-dlya-kewirovaniya-snova-open-source</link>
      <comments>https://tproger.ru/news/vywel-redis-8--populyarnyj-instrument-dlya-kewirovaniya-snova-open-source?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/vywel-redis-8--populyarnyj-instrument-dlya-kewirovaniya-snova-open-source</guid>
      <description><![CDATA[<p>Redis 8 стал снова по-настоящему open-source — с лицензией AGPLv3, новыми Vector Sets и апгрейдом ядра</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/vywel-redis-8--populyarnyj-instrument-dlya-kewirovaniya-snova-open-source">Вышел Redis 8: популярный инструмент для кэширования снова open-source</a>»</p>]]></description>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 02 May 2025 04:23:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>Redis снова стал open-source. Новая версия, которая вышла накануне, перешла на лицензию AGPLv3, положив конец спорной эпохе SSPL.</p><p>Больше новостей — в нашем тг-канале «<a href="https://t.me/your_tech">Представляешь»</a></p><p>Это не просто технический апдейт — это возвращение Redis к своим корням и открытости, которая сделала его культовым инструментом среди разработчиков.</p><h2>Почему это важно</h2><p>SSPL (Server Side Public License) была официально признана не открытой лицензией. Многие разработчики и дистрибутивы отвергали проекты с SSPL, что усложняло распространение Redis и вызывало вопросы у сообществ и корпоративных пользователей.</p><p>Переход на AGPLv3 означает, что Redis снова можно свободно использовать, модифицировать и распространять — в духе настоящего open-source.</p><h2>Ветеран Redis: «Я просто хочу снова писать код для людей»</h2><p>Один из разработчиков, участвовавших в создании Redis 8, рассказал, что лично лоббировал возвращение к open-source. Он хотел, чтобы написанный им новый тип данных — Vector Sets — выходил под открытой лицензией.</p><p>«Я слишком давно в open-source, чтобы писать что-то закрытое. Это просто неправильно. Я счастлив, что Redis снова принадлежит всем.»</p><h2>Что нового в Redis 8</h2><p>Redis 8 — это не просто смена лицензии. В релизе:</p><ul><li>Новый тип данных Vector Sets</li><li>Улучшения производительности в ядре</li><li>Оптимизации для современных серверов и высоконагруженных систем</li><li>Множество улучшений по стабильности и расширяемости</li></ul><p>Подробнее — в<a href="https://redis.io/blog/redis-8-ga/"> официальном блоге</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Discord раскрыл секрет индексации триллионов сообщений на платформе</title>
      <link>https://tproger.ru/news/discord-raskryl-sekret-indeksacii-trillionov-soobshhenij-na-platforme</link>
      <comments>https://tproger.ru/news/discord-raskryl-sekret-indeksacii-trillionov-soobshhenij-na-platforme?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/discord-raskryl-sekret-indeksacii-trillionov-soobshhenij-na-platforme</guid>
      <description><![CDATA[<p>Discord раскрыл, как масштабировал поиск на триллионы сообщений: отказ от монолита, новые кластеры, PubSub и 5-кратное снижение задержек</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/discord-raskryl-sekret-indeksacii-trillionov-soobshhenij-na-platforme">Discord раскрыл секрет индексации триллионов сообщений на платформе</a>»</p>]]></description>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Discord]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Kubernetes]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 25 Apr 2025 16:28:51 GMT</pubDate>
      <content:encoded><![CDATA[<p>Discord <a href="https://discord.com/blog/how-discord-indexes-trillions-of-messages">поделился</a> историей масштабного обновления своей системы поиска, которая теперь справляется с триллионами сообщений.</p><p>Больше новостей — в нашем тг-канале «<a href="https://t.me/your_tech">Представляешь»</a></p><p>Компания рассказала, как отказалась от монолитных Elasticsearch-кластеров, переписала архитектуру индексации и смогла не только удвоить пропускную способность, но и снизить задержки запросов в пять раз.</p><h2>Как все ломалось</h2><p>Изначально поиск в Discord строился на двух Elasticsearch-кластерах, где сообщения шардировались по серверам (гильдиям) и личным сообщениям. Работало неплохо, пока объем данных не стал рушить все вокруг.</p><p>Redis-очередь теряла сообщения при сбоях, кластер из 200+ узлов сыпался от малейшего отказа, а обновление ПО превращалось в квест с полной остановкой сервиса.</p><p>Особые проблемы были с большими серверами: Lucene не умеет обрабатывать более 2 млрд документов на один индекс и единственным «решением» было… удалять спам-гильдии.</p><h2>Новый подход: кластеры в «ячейках»</h2><p>Discord ушел от гигантских кластеров в сторону так называемых «ячеек» — наборов небольших Elasticsearch-кластеров, управляемых через Kubernetes.</p><p>Каждая ячейка обслуживает определенную категорию данных: отдельные кластеры теперь хранят только сообщения из ЛС, другие — сообщения по серверам.</p><p>Это позволило безопасно делать rolling-restart, обновлять Elasticsearch, масштабировать нагрузку и сохранять отказоустойчивость даже при падении отдельных узлов.</p><p>Для больших гильдий (внутри Discord их зовут BFG — Big Freaking Guilds) выделяются отдельные индексы с несколькими шардами для повышения производительности.</p><h2>Очередь PubSub и умная маршрутизация</h2><p>Redis-очередь заменила PubSub — теперь ни одно сообщение не теряется. Для индексации сообщений Discord внедрил свой роутер, который собирает батчи сообщений, сгруппированных по конечному кластеру и индексу. Так массовые операции больше не рассыпаются из-за падения одного из узлов.</p><h2>Результат: всё быстрее, стабильнее и масштабируемо</h2><p>После внедрения новой архитектуры:</p><ul><li>индексируются триллионы сообщений в 2 раза быстрее прежнего;</li><li>задержка поиска снизилась с 500 до &lt;100 мс (p50) и с 1 с до &lt;500 мс (p99);</li><li>работают 40 кластеров с тысячами индексов;</li><li>все обновления проходят без остановки сервиса.</li></ul><p>И главное — теперь можно искать сообщения в личке сразу по всем чатам, а не только по отдельным диалогам.</p>]]></content:encoded>
    </item>
    <item>
      <title>Context Collapse: как микросервисы могут сойти с ума</title>
      <link>https://tproger.ru/articles/context-collapse--kak-mikroservisy-mogut-sojti-s-uma</link>
      <comments>https://tproger.ru/articles/context-collapse--kak-mikroservisy-mogut-sojti-s-uma?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Ельцова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/context-collapse--kak-mikroservisy-mogut-sojti-s-uma</guid>
      <description><![CDATA[<p>Даже самая идеальная микросервисная архитектура может упасть. В статье обсудим зарубежный материал, где автор рассказывает о проблеме Context Collapse.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/context-collapse--kak-mikroservisy-mogut-sojti-s-uma">Context Collapse: как микросервисы могут сойти с ума</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[JSON]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[Отладка]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Сервисы]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[OpenTelemetry]]></category>
      <category><![CDATA[Grafana]]></category>
      <category><![CDATA[CI/CD]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 25 Mar 2025 12:40:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>В феврале на Medium вышла статья <a href="https://levelup.gitconnected.com/context-collapse-the-silent-microservices-killer-76a60058561e">Context Collapse: The Silent Microservices Killer</a>. Перевели ее для вас, так как тема довольно редкая и интересная. Ниже предлагаем обсудить проблему контекста и как он может упасть.</p><h2>Что вообще такое context collapse</h2><p>Есть вероятность, что вы слышите этот термин в первый раз. Немного лирики: в мире микросервисов есть два понятия: технический контекст и доменный контекст (Bounded Context).</p><h2>Технический контекст</h2><p>Технический контекст — это информация, которая передается между микросервисами для выполнения запросов или операций. В нем лежит большое количество данных: идентификаторы запросов (например, TraceID в трассировке), пользовательские сессии, метаданные или параметры аутентификации.</p><p>Без технического контекста микросервисы не могут:</p><ul><li>отслеживать, откуда пришел запрос и куда он направляется,</li><li>сохранять согласованность данных между сервисами,</li><li>выявлять сбои (например, через логи или трассировку).</li></ul><h2>Доменный контекст (Bounded Context)</h2><p>Доменный контекст происходит из методологии Domain-Driven Design (DDD) — это про четкое разделение бизнес-логики между микросервисами. У каждого сервиса должна быть своя «ограниченная область» (Bounded Context), где термины, данные и правила имеют уникальное значение.</p><p>Например, в интернет-магазине слово «заказ» может означать разные вещи для сервиса оплаты (финансовая транзакция) и сервиса доставки (отправка покупателю). Если границы контекста не определены четко, возникает путаница: сервисы начинают дублировать логику или интерпретировать данные по-разному.</p><p>Без доменного контекста:</p><ul><li>код будет дублироваться и появится избыточность,</li><li>усложнится взаимодействие между сервисами,</li><li>систему будет сложнее поддерживать.</li></ul><h2>Вернемся к коллапсу контекста</h2><p>Автор статьи просит нас представить следующую картину: вы сделали новую архитектуру, она масштабируется, разделяется, деплои проходят гладко, CI/CD пайплайны цветут и пахнут — другими словами, не архитектура, а мечта. Все работает как часы, поэтому вы сидите, попивая кофе и раскладывая пасьянс.</p><p>Вдруг приходит пользователь и сообщает, что платеж был проведен дважды. На панели мониторинга появляются задержки, и логи здесь вообще бесполезны. Вы начинаете разбираться и спустя несколько часов споров с терминалом, находите причину: один из сервисов забыл, кто вообще такой этот пользователь, прямо во время проведения транзакции.</p><p>Тра-та-та, это и есть контекстный коллапс. Как называет его автор — тихий убийца микросервисов в 2025 году. Поймать этот баг с помощью breakpoints нельзя, при этом он будет уничтожать вашу производительность и код в целом.</p><h2>Что на самом деле происходит</h2><p>Представьте, что микросервисы — это эстафетный забег: каждый сервис передает «эстафетную палочку» — идентификаторы пользователей, сессионные данные, намерения — следующему участнику. Контекстный коллапс случается, когда эта палочка выпадает на бегу.</p><p>Сервис теряет важное состояние, например:</p><ul><li>«Это корзина Пети»</li><li>"Этот платеж уже прошел"</li></ul><p>И начинается хаос: дублирующиеся API-запросы, потерянные транзакции или, что еще хуже, повреждение данных.</p><p>В 2025 году появляется все больше слишком фрагментированных архитектур и гипермасштабируемых систем. Ваши запросы могут проходить через 10, 20 и даже 30 сервисов, но если на каком-то этапе отвалился контекст, то это конец.</p><p>В статье автор не делает акцента на том, какой именно это контекст — технический или доменный. На самом деле может произойти все что угодно: это может быть как потеря технического контекста, так и нарушение доменных границ (когда сервисы лезут не в свое дело). Оба случая приводят к хаосу: данные становятся несогласованными, ошибки множатся, а разработчики теряют контроль над системой.</p><h2>Как понять, что контекст вот-вот может «коллапснуться»</h2><p>Вы наверняка были в такой ситуации: вы на 100% уверены, что система должна работать, но она не работает, хотя ошибок в коде никаких, казалось бы, нет. Вот основные признаки коллапса, с которыми вы можете столкнуться:</p><ul><li>Всплеск пинга: сервисы запрашивают данные, которые должны бы уже знать, создавая огромное количество лишних вызовов и перегружая API.</li><li>Дублирующиеся действия: повторные списания, задвоенные отправки писем, неожиданные повторные заказы. Пользователи возмущены, рейтинг падает.</li><li>Мистические баги: логи говорят «всё в порядке», но результат явно неверный. Код не видит проблему, а отладка превращается в кошмар.</li></ul><p>Давайте рассмотрим на примере. Клиент решает перевести деньги с одного счёта на другой. Сервис транзакций отправляет запрос в модуль проверки лимитов, затем передаёт его в систему обработки платежей. Но из-за потери контекста на одном из этапов модуль проверки лимитов теряет информацию о сессии клиента и воспринимает его как нового пользователя. В результате:</p><ul><li>Лимиты не распознаются, и транзакция блокируется, даже если у пользователя достаточно денег.</li><li>Либо, наоборот, проверка пропускается, и клиенту позволяют перевести больше лимита.</li><li>Сервис обработки платежей не получает подтверждение проверки и отправляет повторный запрос, а это может привести к двойному списанию средств.</li></ul><p>Клиент идет громить службу поддержки, она — разработчиков, а они размахивают руками, потому что в логах все отлично.</p><p>Опрос CNCF за 2024 год показал, что 68% команд, которые используют микросервисы, сталкиваются с необъяснимыми проблемами производительности. И всему виной в том числе контекстный коллапс.</p><h2>5 решений, как бороться с контекстным коллапсом</h2><p>Да, контекстный коллапс — крайне неприятный баг. Главное — чтобы все сервисы работали слаженно и «знали» друг друга.</p><h3>1. Правильно передавайте контекст</h3><p>Вам нужно передавать критические важные данные — идентификаторы пользователей, состояние транзакций, метаданные запроса — в каждом шаге цепочки. Стоит использовать OpenTelemetry, чтобы распространять контекст по сервисам.</p><p>Вот пример реализации middleware автором на Go:</p><h3>2. Кэшируйте с умом</h3><p>Нет смысла заставлять сервисы угадывать, если они просто могут запомнить. Быстрый кэш, например, с Redis, может хранить временный контекст — например, токены сеанса или состояния запросов, — поэтому сервисы не будут постоянно спрашивать «кто это?» Вот фрагмент кода на Питоне, где используется Redis для хранения и извлечения контекста:</p><p>Это позволяет поддерживать контекст во всех вызовах, не перегружая вашу базу данных. А еще TTL (time-to-live) Redis сам за собой убирает — устаревшие данные не скрываются.</p><h3>3. Используйте Event-Driven Architecture</h3><p>Микросервисы без сохранения состояния выглядят отлично, пока не контекст не коллапснется. Событийная архитектура идет от обратного: вместо того чтобы надеяться, что службы все запомнят, регистрируйте каждый шаг как событие. Если служба все-таки забудет, воспроизведите поток. Вот пример на Node.js с Kafka:</p><p>Здесь также можно использовать RabbitMQ. В общем, больше никаких отговорок со стороны сервиса из разряда «я забыл».</p><h3>4. Проверяйте логи</h3><p>Когда контекст теряется, логи — ваше место преступления. Настройте Grafana Loki или Datadog для поиска «потерянных контекстов». Вот пример с Grafana:</p><p>Тэгните логи с помощью request_id или user_id, а затем вызовите Loki:</p><h3>5. Тестируйте на коллапсы</h3><p>Профилактика лучше, чем лечение. Добавьте хаос-тестирование с помощью, например, Chaos Mesh, чтобы моделировать потери контекста.</p><p>Хаос-тестирование — это метод преднамеренного введения сбоев в систему, чтобы проверить, насколько она устойчива и надежна. Обычное тестирование и мониторинг могут выявить проблемы, но хаос-тестирование (Chaos Engineering) помогает увидеть, как система поведет себя в случае неожиданных отказов.</p><p>Прервите mid-requests к сервисам — сможет ли система восстановится в таком случае? Если нет, значит, ваша передача контекста недостаточно надежна.</p><p>Эти решения не универсальны. Начните с правильного распространения контекста для быстрых улучшений, затем добавьте кэширование или событийную архитектуру для масштабируемости и постоянно аудируйте систему.</p>]]></content:encoded>
    </item>
    <item>
      <title>Зачем разработчику знать SQL, если есть NoSQL? Разбираемся на примерах</title>
      <link>https://tproger.ru/articles/zachem-razrabotchiku-znat-sql--esli-est-nosql--razbiraemsya-na-primerah</link>
      <comments>https://tproger.ru/articles/zachem-razrabotchiku-znat-sql--esli-est-nosql--razbiraemsya-na-primerah?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Владислав Устинов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/zachem-razrabotchiku-znat-sql--esli-est-nosql--razbiraemsya-na-primerah</guid>
      <description><![CDATA[<p> Зачем разработчику знать SQL, если есть NoSQL. Показываем основные отличия SQL и NoSQL. Рассматриваем пошаговую инструкцию и важные особенности ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/zachem-razrabotchiku-znat-sql--esli-est-nosql--razbiraemsya-na-primerah">Зачем разработчику знать SQL, если есть NoSQL? Разбираемся на примерах</a>»</p>]]></description>
      <category><![CDATA[Интернет]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[NoSQL]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Аналитика]]></category>
      <category><![CDATA[аналитик]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 19 Mar 2025 10:00:02 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Ключевые отличия SQL и NoSQL</h2><h3>Структура данных: реляционные таблицы vs. Документо-ориентированные, графовые, ключ-значение базы</h3><p>SQL — язык запросов, с помощью которого мы можем обращаться к реляционным базам данных и манипулировать ими. Они имеют строгую структуру и отношения, логика их схемы напоминает таблицу или несколько связанных таблиц.</p><p>Рассмотрим пример таблицы ниже:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-03-12/a2ede9fc-5a44-48c8-8cb4-d49eae7761b6.jpg" alt="Зачем разработчику знать SQL" /><figcaption>Пример таблицы Excel</figcaption></figure><p>Это таблица excel, в которую занесены данные о различных персонажах. В ней мы можем фильтровать данные, искать их, сортировать содержимое, писать значения с разными типами, обращаться к данным во внешних таблицах. При помощи запросов SQL возможно всё то же самое в реляционной базе.</p><p>Давайте создадим нашу таблицу при помощи SQL:</p><p>Командой CREATE TABLE создаём таблицу, которую называем «Персонажи». В скобках прописываем название столбцов, напротив указываем тип данных, который здесь будет храниться. Например, VARCHAR(50) — это текст до 50 символов, DATE — дата, INT — число. PRIMARY KEY — первичный ключ строки. Он нужен, чтобы у каждой строки таблицы был свой уникальный номер.</p><p>Далее заполним нашу таблицу при помощи команды INSERT INTO Персонажи VALUES:</p><p>Заполняем значениями в кавычках, через запятую. Запятые отделяют столбцы друг от друга.</p><p>А теперь добавим ещё одного персонажа и отфильтруем значения по столбцу «Фильм_Сериал»:</p><p>Команда INSERT INTO добавляет нового персонажа в уже существующую базу.</p><p>В скобках после INSERT INTO мы перечисляем столбцы, которые хотим заполнить. VALUES — значения для этих столбцов.</p><p>Эта команда выбирает все столбцы в таблице «Персонажи», проверяя, что новое значение добавилось.</p><p>Далее проводим фильтрацию по сериалу: «Игра престолов»:</p><ul><li>SELECT — выбирает данные из столбцов.</li><li>После SELECTпишем, какие именно столбцы хотим видеть (не всё, а только эти шесть).</li><li>FROM Персонажи — указываем таблицу, из которой берём данные.</li><li>WHERE Фильм_Сериал — выбираем столбец, по которому будем искать данные.</li></ul><p>В результате получим вот такие данные:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-03-12/5ed2fec8-f939-4ba5-8297-ef5226c7c0c6.jpg" alt="Зачем разработчику знать SQL" /><figcaption>Визуализация вывода</figcaption></figure><p>NoSQL в сравнении с SQL не просто другой язык, а целая философия организации базы данных. Никаких строгих таблиц — всё зависит от того, с чем работаем. В NoSQL есть много видов данных, под каждый из них существуют свои системы управления базами данных (СУБД). Вот основные из них:</p><p><b>Документо-ориентированные базы (MongoDB):</b> данные лежат в виде документов, похожих на JSON, но это не совсем он, а BSON. Различия кроются в том, что это его бинарная версия.</p><p>Пример:</p><p>Слева в кавычках мы пишем название наших полей, например, «Персонаж». Далее через двоеточие указываем значение (тоже в кавычках) и заканчиваем запись для поля запятой. Всё это внутри фигурных скобок.</p><p><b>Ключ-значение (Redis):</b> это тип NoSQL баз данных, где информация хранится в виде пар «ключ-значение», как в словаре: ключ — уникальный идентификатор, значение — любые данные, связанные с ним. Например, запись Джона Сноу будет выглядеть так:</p><p>Внутри фигурных скобок прописываем ключ в кавычках. Далее через двоеточие пишем значение. Отделяем поля между собой при помощи запятой.</p><p><b>Графовые базы (Neo4j):</b> здесь данные — это узлы.</p><p>CREATE (Джон:Персонаж {имя: "Джон Сноу", дом: "Старк"});</p><p>Создаётся узел с меткой Персонаж, который представляет Джона Сноу. Узел имеет два свойства: имя со значением «Джон Сноу» и дом со значением «Старк». Переменная Джон — временное имя для ссылки на узел.</p><p>CREATE (Тирион:Персонаж {имя: "Тирион Ланнистер", дом: "Ланнистер"});</p><p>Далее создаётся ещё один узел с меткой Персонаж, представляющий Тириона Ланнистера. У него тоже два свойства: имя — «Тирион Ланнистер» и дом — «Ланнистер». Переменная Тирион позволяет ссылаться на этот узел.</p><p>CREATE (Джон)-[:ЛАЙК]-&gt;(Тирион);</p><p>Затем появляется направленная связь (ребро) между узлами Джон и Тирион. Она имеет тип ЛАЙК и указывает, что Джон «лайкает» Тириона. Скобки () обозначают узлы, а [:ЛАЙК] со стрелкой -&gt; показывает направление отношения.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-03-12/f97cfff4-8608-4b0f-ba06-7ce5198da147.jpg" alt="Зачем разработчику знать SQL" /><figcaption>Пример визуализации графа из кода</figcaption></figure><h3>Гибкость схемы SQL против NoSQL: строгая схема vs динамическая структура</h3><p>Представьте, что у нас есть база данных с персонажами из фильмов и сериалов — огромная таблица на миллион строк. Теперь мы решили добавить в неё нестандартную запись: включить в список реальную историческую личность, а заодно создать новый столбец «факты», чтобы указать, чем личность запомнилась. Сделать это можно при помощи команды CREATE, но есть проблема.</p><p>Дело в том, что если мы создадим новый столбец в большой базе данных и запишем туда значение только для одного персонажа, то остальные строки в столбце для других примут значение null. И это не очень удобно, так как большое количество null усложняет запросы и может запутать разработчика при обработке данных.</p><p>В этом фундаментальное различие SQL и NoSQL. Первый плохо подходит для работы с неструктурированными данными.</p><p>В NoSQL мы можем при помощи MongoDB прописать значение только для одного персонажа, не трогая других:</p><p>Сравните данные Илона Маска и Сайтамы, у них есть различия в схеме.</p><p>Для такой записи в SQL нам пришлось бы добавлять поле «Чем известен» для всех персонажей в базе. Другие бы тогда получили значение null:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-03-12/e9f5f43b-ae55-41bf-a58c-bf1807d5c9bc.jpg" alt="Зачем разработчику знать SQL" /></figure><p>Из-за строгой схемы надо заранее продумывать логику структуры. Если база данных уже большая и её логику надо поменять, это чревато неудобствами.</p><p>У NoSQL, в отличие от SQL, нет строгой схемы, благодаря этому он отлично подходит для работы с плохо структурированными данными.</p><h2>Транзакции в SQL и NoSQL: ACID (SQL) vs. BASE (NoSQL)</h2><p>Транзакция — набор тех операций, которые либо выполняются полностью, либо не выполняются совсем. Представим перевод денег: снимаем 500 рублей с нашего счёта и отправляем их на другой. Если что-то сломается на полпути, транзакция либо отменится, либо завершит оба шага. В базах данных транзакции нужны, чтобы данные оставались надёжными. Рассмотрим основные различия транзакций SQL и NoSQL.</p><h3>ACID (SQL)</h3><p>SQL-базы данных работают по принципу ACID-транзакций, что гарантирует стабильность и предсказуемость работы с данными. Этот набор правил включает атомарность, согласованность, изоляцию и долговечность.</p><p><b>Атомарность (Atomicity)</b> — транзакция выполняется как единое целое. Если хоть одна операция внутри неё не удалась, всё отменяется. Например, если при переводе денег сервер внезапно отключился, система откатит изменения, и средства не исчезнут.</p><p><b>Согласованность (Consistency) </b>— данные всегда соответствуют правилам базы. Если на счёте 80$, а мы пытаемся перевести 100$, система просто не даст выполнить такую операцию.</p><p><b>Изоляция (Isolation) </b>— параллельные транзакции не мешают друг другу. Пока одна выполняется, другая видит только конечный результат. Например, если один процесс переводит 100$ со счёта A на счёт B, а другой в этот момент проверяет баланс, он увидит либо старое состояние, либо уже обновлённое, но никогда промежуточное.</p><p><b>Долговечность (Durability) </b>— завершённая транзакция остаётся в базе навсегда, даже в случае сбоя. Если мы пополнили счёт на 100$, эта информация будет сохранена, независимо от того, что произойдёт с сервером после.</p><h3>BASE (NoSQL)</h3><p>BASE-модель, которую используют NoSQL-базы данных, строится на трех принципах: базовая доступность, мягкое состояние и конечная согласованность. В отличие от строгих ACID-правил, здесь делается упор на скорость и масштабируемость, даже если это временно снижает точность данных.</p><p><b>Базовая доступность (Basically Available)</b> — система всегда отвечает на запросы, даже если часть данных ещё не синхронизирована. Например, ставя лайк, мы сразу видим его, даже если информация ещё не дошла до сервера.</p><p><b>Мягкое состояние (Soft State)</b> — данные могут временно быть несогласованными. Ради высокой доступности система допускает, что информация изменяется без нашего участия. Например, когда у нового видео на YouTube лайков больше, чем просмотров — это результат того, что одни данные обновились быстрее других.</p><p><b>Конечная согласованность (Eventual Consistency) </b>— если систему оставить в покое, она сама «додумает» и согласует данные между всеми узлами. Допустим, у поста 50 лайков, но у разных пользователей отображаются разные числа: кто-то видит 49, кто-то 53. Через некоторое время система синхронизируется, и у всех будет одинаковое значение.</p><p>BASE-жизнь — это про скорость и гибкость. Главное, чтобы данные в итоге сошлись, а не были идеальными в каждый момент времени.</p><h2>Масштабируемость: вертикальное (SQL) vs горизонтальное (NoSQL) масштабирование</h2><h3>Вертикальное масштабирование (SQL)</h3><p>Реляционные базы данных изначально проектировались для работы на одном сервере (или кластере серверов) с использованием строгой структуры данных (таблицы, строки, столбцы) и поддержки ACID-транзакций.</p><p>Если наше «железо» не справляется с обработкой реляционной базы, то проще его прокачать, например, установить больше памяти, либо купить новый сервер помощнее и перенести на него данные. При таком масштабировании мы как бы гонимся вверх, стараемся получить больше памяти и производительное «железо».</p><p>Конечно, можем купить второй сервер или кластер серверов и распределить на них часть нагрузки, но это будет сложно из-за архитектуры реляционных баз.</p><h3>Горизонтальное масштабирование (NoSQL)</h3><p>При горизонтальном масштабировании мы докупаем дополнительные сервера и распределяем между ними нагрузку. NoSQL базы данных изначально проектировались для работы на множестве серверов. Здесь не нужна строгая схема хранения данных, и они лучше оптимизированы для работы с большим объёмом информации.</p><p>Благодаря этому мы можем распределять нагрузку NoSQL среди множества серверов. Это значит, что если нам не хватает производительности, то можно просто докупить новые устройства.</p><p>Вертикальное масштабирование здесь тоже возможно, но оно часто дороже, хуже подходит для больших баз данных и, в целом, менее удобно из-за архитектуры NoSQL.</p><blockquote>Первое и главное преимущество SQL — строгая структура данных и операций. У базы есть контракт, который ты обязан соблюдать при добавлении, изменении или запросе данных. Правило простое: «Либо всё, либо откат». Второе преимущество — мощный и популярный язык запросов. Сложные аналитические задачи ему по плечу. Уверен, реляционные БД будущего будут опираться на опыт SQL.<br /><br />Строгая структура — одновременно и ограничение. Добавление полей или связей может стать проблемой при реализации на клиенте. Второй момент — горизонтальное масштабирование. Реляционные базы данных очень сложно масштабировать горизонтально. Ты не можешь просто подключить ещё один кластер и продолжать спокойно существовать. Есть ещё определённые проблемы с производительностью, если между объектами существует много связей.<br /><br />Плюсы NoSQL в гибкости, лёгкости масштабирования, высокой производительности при больших нагрузках и разнообразии предметно-ориентированных решений. Например, Redis — для кеширования, Firebase — для реалтайм-обновлений и так далее. Структуру данных можно менять на лету, идеально для проектов с частыми изменениями структуры данных.<br /><br />Минусы в том, что у многих NoSQL свои системы команд и запросов — их приходится учить. Также есть сложности с консистентностью данных.</blockquote><h2>Где использовать SQL и NoSQL</h2><h3>Сценарии, где SQL незаменим</h3><p>SQL хорош там, где важны точность, структура и сложные взаимосвязи данных.</p><h4>Финансовые и банковские системы</h4><p>Здесь цена любой ошибки — чьи-то деньги, поэтому важна точность. Транзакции по стандарту ACID гарантируют, что деньги не потеряются при сбоях. Связь между счетами, клиентами и операциями проще строить через реляционные базы данных.</p><p>Примеры систем: PostgreSQL, Oracle Database, Microsoft SQL Server.</p><h4>Аналитика и сложные запросы</h4><p>В аналитике нужно регулярно вытаскивать данные из таблиц и строить зависимости. В SQL есть операторы JOIN, GROUP BY и оконные функции, которые решают задачи, наподобие расчёта среднего чека за квартал, в пару строк.</p><p>Примеры систем: MySQL, Snowflake, Google BigQuery.</p><h4>Работа с отчётами и BI-системами</h4><p>Данные для бизнеса — это таблицы, сводки, графики. SQL легко интегрируется с инструментами вроде Power BI или Tableau. Строгая схема помогает избежать путаницы в метриках.</p><p>Примеры систем: PostgreSQL, SQL Server, Redshift.</p><h4>Логирование и аудит данных</h4><p>В этой сфере важно понимать, кто, что и когда изменил. Реляционные базы фиксируют историю изменений с точными связями. Триггеры и индексы ускоряют поиск по логам.</p><p>Примеры систем: SQLite, PostgreSQL, MariaDB.</p><h4>Управление складом и инвентаризацией</h4><p>Данные о товарах, поставках и остатках связаны между собой. Реляционная модель идеально описывает такие структуры. Запросы вроде «что заканчивается на складе» пишутся быстро и понятно.</p><p>Примеры систем: MySQL, Oracle Database.</p><h3>Где NoSQL работает лучше?</h3><p>NoSQL — это про скорость, гибкость и масштабирование. Там, где SQL требует строгих рамок, NoSQL даёт свободу и справляется с хаосом больших данных. Вот сценарии, где он выигрывает:</p><h4>Высоконагруженные системы и real-time сервисы</h4><p>Допустим, у нас стриминговый сервис, здесь миллионы запросов в секунду, а задержки недопустимы. Горизонтальное масштабирование размазывает нагрузку по серверам. Данные отправляются быстро, без сложных JOIN’ов.</p><p>Примеры систем: Cassandra, MongoDB, DynamoDB.</p><h4>Гибкие структуры данных и работа с JSON</h4><p>В реальной жизни данные далеко не всегда приходят в виде строгой схемы. У кого-то может быть указан email, у кого-то его нет, но есть телефон. Поэтому для работы с такой информацией лучше подходят документо-ориентированные базы, которые хранят JSON или BSON без строгой структуры. Если мы добавим новое поле, такая база не сломается.</p><p>Примеры систем: MongoDB, CouchDB, Firebase.</p><h4>Графовые базы для рекомендаций и соцсетей</h4><p>Иногда связи между объектами важнее самих данных. Графовые базы строят сети вроде «друзья друзей» или «похожие товары» за доли секунды. SQL для этой задачи медленнее.</p><p>Примеры систем: Neo4j, ArangoDB, OrientDB.</p><h2>Почему SQL и NoSQL нужно знать вместе?</h2><p>SQL даёт точность и структуру, данные в NoSQL — это про скорость и гибкость. В реальных проектах их часто используют вместе, чтобы закрыть слабые места друг друга.</p><h3>Примеры гибридных архитектур: SQL и NoSQL в одном проекте</h3><h4>Работа с каталогами товаров, запросами и системой рекомендаций в интернет-магазинах</h4><p>Представим интернет-магазин: каталог товаров и заказы лежат в SQL — там важны связи и точность. А рекомендации «похожие товары» или история просмотров — в NoSQL, чтобы быстро отдавать данные и не мучиться со схемой.</p><p>Например, PostgreSQL хранит данные о клиентах и транзакциях, а MongoDB — отзывы и пользовательские профили. Всё в одном проекте, каждый инструмент на своём месте.</p><h4>Кеширование запросов с помощью Redis и SQL</h4><p>SQL силён в сложных запросах, но в больших базах может тормозить. А если у нас есть операция, которая предполагает повторный подсчёт? Это будет долго. Здесь выручает Redis, NoSQL-база типа «ключ-значение».</p><p>Представим аналитику продаж: SQL вычисляет «топ-10 товаров за месяц» Готовый список сохраняем в Redis. Теперь, когда нам нужен этот топ, данные тянутся из Redis за миллисекунды, нам не надо заново проводить вычисления в SQL.</p><h4>Использование NoSQL для логов, SQL — для отчётности</h4><p>Логи — это поток данных: миллионы записей, структура не всегда предсказуема. NoSQL вроде Cassandra или MongoDB «переваривает» их без проблем благодаря скорости записи и горизонтальному масштабированию. А потом из этого хаоса SQL, например, MySQL, вытягивает нужное для отчётов: «сколько ошибок за день» или «кто чаще ломает систему». NoSQL собирает сырые данные, SQL их структурирует.</p><h2>Стоит ли учить SQL? Мнение экспертов</h2><p>Мы решили спросить у экспертов, что бы они посоветовали молодым разработчикам изучать в первую очередь, SQL или NoSQL. Делимся мнениями:</p><blockquote>Мой совет — начинайте с SQL. Вот почему:<br /><br />1) База знаний. SQL учит вас основам работы с данными — как их хранить и извлекать. Это фундамент, который пригодится в любом проекте, даже если вы потом перейдёте на NoSQL.<br /><br />2) Широкое применение. SQL встречается повсюду — от стартапов до банков. Знание SQL сразу даёт вам больше шансов найти работу или понять, что происходит в существующем проекте.<br /><br />Но это не значит, что NoSQL можно просто игнорировать. После SQL я бы рекомендовал изучить хотя бы одну NoSQL базу — например, Redis или MongoDB. Современные проекты часто требуют гибкости и скорости, которые дают NoSQL системы. Зная обе технологии, вы сможете выбирать инструмент под задачу, а не подстраиваться под то, что знаете.</blockquote><blockquote>Первым делом нужно освоить SQL. Это фундаментальный навык, который необходим в любой сфере, связанной с данными. Знание реляционных баз помогает понять, как правильно структурировать данные, оптимизировать запросы и обеспечивать их целостность. Независимо от того, с какой технологией будет иметь дело разработчик в будущем, понимание SQL даст ему прочную основу.<br /><br />После освоения SQL я бы рекомендовал изучить и NoSQL. Важно понимать, какие задачи он решает, какие бывают типы нереляционных баз и в каких случаях их применение оправдано. Даже если специалист в своей работе чаще использует SQL, знание NoSQL поможет ему грамотно проектировать архитектуру и принимать взвешенные технологические решения.</blockquote><blockquote>Не существует как такового единого NoSQL как системы управления базы данных, зачастую это очень сильно различающийся набор решений, поэтому стоит начать с SQL, для разных СУБД он довольно мало отличается синтаксически. SQL — это фундамент. NoSQL даст более полное понимание того, как можно работать с базами данных. Можно отметить, что в SQL добавляются фичи NoSql и наоборот.</blockquote><p>SQL и NoSQL желательно знать вместе. Как видно исходя из комментариев экспертов и примеров, SQL — первый ключ к базам данных, NoSQL — второй, для более сложных сценариев.</p>]]></content:encoded>
    </item>
    <item>
      <title>Рефакторинг запросов: как ускорить работу API без переписывания всего кода</title>
      <link>https://tproger.ru/articles/refaktoring-zaprosov--kak-uskorit-rabotu-api-bez-perepisyvaniya-vsego-koda</link>
      <comments>https://tproger.ru/articles/refaktoring-zaprosov--kak-uskorit-rabotu-api-bez-perepisyvaniya-vsego-koda?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Вадим Егорцев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/refaktoring-zaprosov--kak-uskorit-rabotu-api-bez-perepisyvaniya-vsego-koda</guid>
      <description><![CDATA[<p>Рефакторинг запросов. Показываем, как ускорить работу API без переписывания всего кода. Рассматриваем пошаговую инструкцию и основные нюансы ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/refaktoring-zaprosov--kak-uskorit-rabotu-api-bez-perepisyvaniya-vsego-koda">Рефакторинг запросов: как ускорить работу API без переписывания всего кода</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[JSON]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Рефакторинг]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 24 Feb 2025 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>API тормозит, а переписывать код с нуля — не вариант? Рефакторинг запросов поможет ускорить работу без радикальных изменений. Разбираем, как оптимизировать API, сокращая задержки и снижая нагрузку на сервер, не разваливая всю систему.</p><h2>Анализируем производительность API</h2><p>Прежде чем ускорять API, нужно понять, что именно замедляет его работу. Для этого оцениваем ключевые метрики:</p><ul><li>Время отклика — сколько времени проходит от запроса до получения ответа.</li><li>Нагрузка на сервер — сколько ресурсов потребляет API при обработке запросов.</li><li>Частота ошибок — как часто сервер возвращает некорректные ответы.</li></ul><p>Отслеживать эти показатели помогают инструменты вроде Postman, New Relic и APM-систем. Они визуализируют данные, автоматизируют тестирование и позволяют находить проблемы в режиме реального времени.</p><h3>Postman</h3><p>Используется для ручного тестирования запросов. Postman показывает время отклика и ошибки. Также через него удобно тестировать сценарии работы API.</p><p>Инструмент удобен тем, что поддерживает коллекции запросов. Это упрощает тестирование сложных цепочек операций во время рефакторинга. Можно интегрировать Newman для автоматизации тестов и анализа метрик на регулярной основе.</p><p><a href="https://tproger.ru/articles/gajd-po-rabote-s-postman">Большой гайд по работе с Postman API Platform</a></p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/bec33c5a-fa3c-4888-af85-885ece3d6f85.jpg" alt="" /><figcaption>Интерфейс Postman</figcaption></figure><h3>New Relic</h3><p>Платформа для мониторинга производительности приложений. Можно отслеживать время обработки API-запросов, статистику по операциям, загрузку системы.</p><p>New Relic показывает распределение времени выполнения между сервером, БД и внешними сервисами, что помогает в рефакторинге разных частей системы.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/732eea24-d64a-4e3b-9585-f2e4f23e2f75.png" alt="" /><figcaption>Интерфейс New Relic</figcaption></figure><h3>APM-системы</h3><p>Инструменты Datadog, AppDynamics или Dynatrace сохраняют данные о том, как работает API. Через них можно смотреть трассировку запросов, выявлять проблемы с медленными вызовами или зависимостями от внешних систем.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/55c2a216-1a7c-46c9-b19a-3324bc9a9493.jpg" alt="" /><figcaption>Трасса в Datadog</figcaption></figure><h3>Ищем узкие места в запросах</h3><p>Для выявления проблемных фрагментов кода необходимо посмотреть, как API реагирует на разные нагрузки, и как распределяется время обработки запроса.</p><p>Поиск состоит из 7 этапов:</p><ul><li><b>Анализ общей картины</b>. С помощью APM-систем и логирования нужно выделить самые медленные запросы.</li><li><b>Локализация длинных операций</b>. В одном запросе может быть несколько узких мест (тяжелый SQL-запрос, внешний API-вызов).</li><li><b>Поиск запросов с высокой частотой вызовов</b>. Например, запросы к слою авторизации или ручки API, к которым обращаются массово при каждом действии пользователя.</li><li><b>Проверка ошибок</b>. Ошибки приводят к дополнительной нагрузке: например, если клиент совершает повторные запросы после тайм-аута.</li><li><b>Анализ распределения нагрузки</b>. Если одни эндпоинты перегружены трафиком, а другие используются редко, в рамках рефакторинга нужно сбалансировать нагрузку. Например, добавить серверы только под обработку горячих эндпоинтов.</li><li><b>Тестирование в условиях пиковой нагрузки</b>. Стресс-тест средствами JMeter поможет выявить проблемы, скрытые в условиях обычного трафика.</li><li><b>Анализ зависимостей API</b>. Замедленная работа внешнего сервиса увеличивает время отклика для каждого запроса. Через Jaeger или Zipkin можно посмотреть цепочку зависимостей.</li><li><b>Локализация длинных операций</b>. В одном запросе может быть несколько узких мест (тяжелый SQL-запрос, внешний API-вызов).</li></ul><h2>Оптимизация запросов к базе данных</h2><p>Уменьшить время отклика API без переписывания кода можно за счёт оптимизации работы с БД. Один из ключевых инструментов — <a href="https://tproger.ru/articles/indeksy-v-postgresql">индексы</a>. Они ускоряют поиск данных, снижая нагрузку на сервер.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/2e25a089-667a-4002-9967-66601f996cfa.jpg" alt="" /><figcaption>B-Tree индекс PostgreSQL</figcaption></figure><p>Что индексировать в первую очередь:</p><ul><li>Поля, используемые в WHERE, JOIN, ORDER BY, GROUP BY. Например, если запросы часто фильтруют по email, имеет смысл добавить индекс.</li></ul><ul><li>Запросы с несколькими условиями фильтрации — их лучше оптимизировать составными индексами. Важно, чтобы порядок полей соответствовал порядку в SQL-запросах.</li></ul><ul><li>Только те данные, где индексация действительно ускорит поиск. Например, индекс для gender (male/female) не даст прироста скорости.</li></ul><p>Лишние индексы замедляют операции записи, их нужно удалять. В PostgreSQL они могут пересекаться, но при дублировании  замедляют запросы. Вместо нескольких отдельных индексов эффективнее использовать составные.</p><h3>Агрегация и выбор только необходимых полей</h3><p>Обработка бесполезных данных увеличивает объем передаваемой информации, замедляет чтение и создает нагрузку на сеть.</p><p>Например, команда SELECT * выбирает все колонки таблицы, включая неиспользуемые. Вместо SELECT * FROM users следует указывать целевые поля: SELECT id, name, email FROM users.</p><p>Если к запросу добавлены ненужные JOIN-ы, группировки или сортировки, в рамках рефакторинга нужно постараться избавиться от них. Избыточные операции на стороне БД рекомендуется переносить в бизнес-логику приложения.</p><p>Снизить объем данных можно за счет COUNT, SUM, AVG, MAX, MIN — эти функции возвращают обобщенные записи вместо отдельных значений. Частичную обработку данных можно выполнять на стороне БД. Например, в PostgreSQL есть встроенные функции по типу JSON_AGG.</p><h3>Пагинация и лимиты в запросах</h3><p>API, работающие со списками пользователей и транзакциями, обязательно должны использовать пагинацию. Лимит на объем возвращаемых записей предотвращает перегрузку серверов и клиента.</p><p>Самое простое решение — во время рефакторинга ограничить количество строк с помощью LIMIT или FETCH FIRST. Например, вместо загрузки всех пользователей SELECT id, name FROM users LIMIT 30 вернет только первые 30 строк.</p><p>Еще можно использовать постраничную выборку (комбинацию LIMIT и OFFSET):</p><p>Если производительность упала, значит офсет слишком большой. В таких случаях лучше перейти на модель курсоров.</p><p>Пагинация с курсорами заменяет OFFSET значением последнего взятого элемента. Например, вместо LIMIT 20 OFFSET 100 можно использовать запрос:</p><p>Чтобы пагинация работала корректно, всегда нужно использовать явный ORDER BY.</p><p><a href="https://tproger.ru/articles/realizuem-paginaciju-v-go-ispolzuja-postgresql">Пагинация на Go в PostgreSQL</a></p><h2>Кэширование</h2><p>Количество запросов к серверу можно сократить, если сохранять часто запрашиваемые данные. Это называется <b>кэшированием</b>. Рефакторинг кэширования уменьшает нагрузку на сервер, снижает время отклика API.</p><p>Рассмотрим инструменты для кэширования.</p><h3>Redis</h3><p>Подходит для серверного и распределенного кэширования. Поддерживает TTL, работу со списками, хэшами и включение репликации для отказоустойчивости. Для интеграции доступны библиотеки и клиенты — ioredis для Node.js или StackExchange.Redis для .NET.</p><p><a href="https://tproger.ru/articles/kak-ispolzovat-redis-dlya-kewirovaniya-i-ocheredej-v-veb-prilozheniyah">Как использовать Redis для кэширования и очередей в веб-приложениях</a></p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/a5b5085d-4c58-45f0-b8f7-b27fcd78ad85.jpg" alt="" /><figcaption>Схема работы Redis, данные хранятся в оперативной памяти сервера</figcaption></figure><p>Рефакторинг кэширования повысит производительность сервиса:</p><ul><li>если API возвращает неизменяемые или редко изменяющиеся данные,</li><li>если часть запросов к БД выполняется кратно дольше остальных.</li></ul><p>Например, в Redis можно сохранять список активных пользователей:</p><h3>Memcached</h3><p>Применяется для уменьшения нагрузки на базу данных и работы с временными данными. Memcached менее функционален, чем Redis, но более эффективен для сценариев, где не требуется сложная логика или постоянное хранилище.</p><p><a href="https://tproger.ru/articles/kak-ispolzovat-servery-redis-i-memcached-dlya-kewirovaniya">Как использовать серверы Redis и Memcached для кэширования</a></p><h3>Виды кэша</h3><p>Выбор типа кэширования зависит от архитектуры API и характера данных.</p><ul><li>Клиентский кэш — данные хранятся на стороне клиента. Например, браузеры загружают страницу дольше в первый раз, но затем мгновенно извлекают её из кэша.</li><li>Серверный кэш — данные сохраняются на сервере или в промежуточном хранилище (Redis, Memcached). Это снижает нагрузку на базу данных: повторные запросы возвращаются из кэша, а не пересчитываются заново.</li><li>Распределённый кэш — данные кэшируются в нескольких узлах для масштабирования и высокой доступности. Например, Redis в режиме кластера помогает организовать кэширование в микросервисной архитектуре.</li></ul><h2>Уменьшение объема передаваемых данных</h2><p>Скорость API зависит от объема данных, передаваемых между клиентом и сервером.</p><h3>Оптимизация формата ответа: JSON vs Protobuf</h3><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/d023e3be-de47-4800-9eed-c034b97920c4.jpg" alt="" /><figcaption>Сравнение JSON и Protobuf</figcaption></figure><p>Формат <b>JSON </b>популярен из-за читаемости и универсальной поддержки большинством языков программирования. Он менее эффективен по сравнению с бинарными форматами. JSON занимает больше места из-за текстового представления структур и значений.</p><p><b>Protobuf </b>(Protocol Buffers) —  бинарный формат, разработанный Google. Он компактен, быстрее сериализуется и занимает меньше места по сравнению с JSON. В формате Protobuf каждый элемент закодирован с минимальным количеством байтов.</p><p>Переход на Protobuf может потребовать изменений в клиентах API, поэтому такая оптимизация выполняется на этапе, когда остальные методы снижения объема данных себя исчерпали.</p><h3>Удаление избыточных данных и компрессия</h3><p>Часто API отправляет больше данных, чем реально нужно клиенту. Это лишний сетевой трафик и задержки:</p><ul><li>Удаление ненужных полей — ограничение выборки данных на стороне сервера: используются выборочные запросы к БД, настройка сериализаторов или фильтрация на уровне представлений.</li><li>Сжатие Gzip — уменьшение размера текстовых данных (JSON, XML) на 70–90%, не требуя изменений в API. Включается на уровне Nginx, Apache или через middleware. Клиенты автоматически распаковывают такие ответы, сохраняя прозрачность процесса.</li></ul><h3>Версионирование API</h3><p>Клиенты обычно нуждаются в данных разного объема. Вместо универсального ответа для всех пользователей рекомендуется разработать несколько версий API.</p><p>Новые версии должны отправлять клиентам упрощенные данные или предлагать другую модель представления без модификации существующих запросов.</p><p>Упрощение достигается через фильтрацию полей на стороне сервера с использованием сериализаторов или форматов ответа. Например, через GraphQL можно сделать так, чтобы клиенты напрямую запрашивали только необходимые поля.</p><p>Чтобы не произошло одновременного отказа у клиентов на старых версиях API, можно временно поддерживать несколько версий. Со временем старую версию нужно объявить устаревшей и отключить.</p><h2>Параллелизация и объединение запросов</h2><p>Оптимизировать API можно за счет рефакторинга batch-запросов. Они объединяют несколько запросов в один — количество обращений между клиентом и сервером сокращается.</p><p>Клиенты отправляют массив запросов, сервер обрабатывает их и возвращает всего один ответ.</p><p>Batch-запросы дают прирост к производительности, когда клиент ожидает получение или обновление нескольких независимых ресурсов.</p><p>Например, мобильное приложение может запрашивать информацию о пользователе и связанных с ним объектах (сообщения, уведомления) за один вызов.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/c893e38f-6031-4a2a-a861-b911798ce759.jpg" alt="" /><figcaption>Схематичное изображение batch-запросов</figcaption></figure><p>Если API поддерживает вложенные запросы, клиент может запрашивать связанные данные одним вызовом. Пример на GraphQL:</p><h3>Асинхронные операции</h3><p>API после рефакторинга будет работать быстрее, если выполнять запросы независимо друг от друга. Сервер может распределять выполнение независимых операций по асинхронным потокам.</p><p>Например, при обработке одного запроса API может обратиться к нескольким подсистемам, отправить синхронные запросы в базу данных и внешние API, параллельно собирая результаты.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/d1a8d43e-6ba2-4c84-866f-caae7301910a.jpg" alt="" /><figcaption>Принцип работы асинхронного API</figcaption></figure><p>Для асинхронных операций часто используются очереди сообщений RabbitMQ или Apache Kafka.</p><h2>Оптимизация на уровне серверной логики</h2><p>Обработку запросов можно откладывать до момента, когда данные действительно понадобятся. Это полезно при работе с запросами, где связанная информация не требуется или используется не полностью. Такое поведение называется <b>lazy loading</b> (ленивая загрузка).</p><p>Загружать связанные данные можно заранее в одном запросе, чтобы сократить количество запросов к БД. Такой принцип работы API называется <b>eager loading</b> (стремительная загрузка). Используется в случаях, когда известно, что все связанные данные понадобятся в ходе операции.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2025-02-14/56dd35a2-1b7c-437e-b796-cc3ef56b2ea0.jpg" alt="" /></figure><p><b>Lazy loading</b> подходит, если только небольшая связка данных используется в каждом запросе.</p><p><b>Eager loading</b> лучше применять, когда количество запросов к БД критично или весь объем данных необходим для выполнения операции.</p><h3>Очереди для обработки задач в фоне</h3><p>Вместо выполнения тяжелых задач в реальном времени, сервер может добавлять их в очередь, чтобы они обрабатывались фоновыми воркерами.</p><p>Очереди используются для задач, не влияющих на основной ответ клиенту:</p><ul><li>отправка писем,</li><li>формирование отчетов,</li><li>обновление статистики,</li><li>построение индексов,</li><li>обработка данных.</li></ul><p>При поступлении запроса API выполняет только основные операции (валидацию данных или запись в базу), после чего создает задачу в очереди. Процессы выполняются в отдельной среде, не влияя на основной поток обработки запросов.</p><h3>Разделение сложных операций на этапы</h3><p>Комплексные операции, например, генерации отчетов, можно разделить на сбор данных из базы, их обработку и финальное преобразование. Каждый шаг выполняется независимо, а результаты промежуточных операций сохраняются для последующего использования.</p><p>Вместо последовательного выполнения всех шагов, задачу можно поручить системе планировщиков или pipeline в <a href="https://tproger.ru/articles/kak-nastroit-i-ispolzovat-jenkins-dlya-avtomatizacii-processov">Jenkins</a>. Каждый этап становится в очередь и выполняется отдельным процессом.</p><h2>Мониторинг и тестирование</h2><p>После рефакторинга хочется быть уверенным в стабильности API, выявить возможные проблемы и оценить эффективность оптимизаций. Процесс включает:</p><ul><li>нагрузочное тестирование,</li><li>сравнение метрик до и после рефакторинга,</li><li>автоматизацию наблюдения за состоянием API.</li></ul><p>Нагрузочное тестирование определяет, как API справляется с возрастающей нагрузкой. Оно выявляет точки перегрузки. Инструменты для стресс-теста API: Apache JMeter, Gatling.</p><p>На основе результатов нагрузочного тестирования можно проанализировать, насколько рефакторинг улучшил производительность API. В сборе метрик помогут APM-инструменты.</p><p><b>Если показатели значительно улучшились, поздравляем, рефакторинг удался. </b></p><p>Автоматизация мониторинга повышает надежность API и предотвращает проблемы до их масштабного проявления. Инструменты для поддержания производительности в реальном времени: Prometheus + Grafana, Datadog.</p><h2>Что запомнить</h2><ul><li>Производительность API оценивается с помощью метрик: времени отклика, нагрузки на сервер и частоты ошибок.</li><li>Для мониторинга API используются инструменты Postman, New Relic и APM-системы, которые выявляют узкие места системы.</li><li>Оптимизация запросов к базе данных включает индексацию, удаление избыточных операций и выбор только необходимых полей. Для работы с большими объемами данных используется пагинация с применением LIMIT, OFFSET или курсоров.</li><li>Кэширование снижает нагрузку на сервер, сохраняя востребованные данные локально.</li><li>Уменьшение объема передаваемых данных достигается за счет перехода с JSON на Protobuf, компрессии Gzip и фильтрации полей.</li><li>Для сокращения числа запросов к API применяются batch-запросы.</li><li>Асинхронная обработка операций с помощью очередей RabbitMQ или Kafka ускоряет время отклика API для клиентов.</li><li>Очереди для фоновой обработки задач снижают нагрузку на основной поток запросов.</li><li>Нагрузочное тестирование помогает оценить улучшения после рефакторинга.</li><li>Автоматизация мониторинга предотвращает проблемы API до их критического проявления.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Это БАЗА (данных): Как подключиться и выполнить запрос?</title>
      <link>https://tproger.ru/articles/kak-podklyuchitsya-k-baze-dannyh-i-vypolnit-zapros-</link>
      <comments>https://tproger.ru/articles/kak-podklyuchitsya-k-baze-dannyh-i-vypolnit-zapros-?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Елизавета Малышева]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-podklyuchitsya-k-baze-dannyh-i-vypolnit-zapros-</guid>
      <description><![CDATA[<p>Как подключиться к базе данных. Показываем основные запросы к базам данных. Рассматриваем пошаговую инструкцию по использованию ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-podklyuchitsya-k-baze-dannyh-i-vypolnit-zapros-">Это БАЗА (данных): Как подключиться и выполнить запрос?</a>»</p>]]></description>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[JSON]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[NoSQL]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Уязвимость]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 16 Jan 2025 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Хотите заплатить хакерам $15 миллионов за свои данные?</p><p>Это не шутка. Именно<a href="https://www.interfax.ru/world/920813"> такую сумму</a> заплатила американская сеть казино Caesars Entertainment злоумышленникам, которые увели их БД.</p><p>База данных ––  самое ценное, что есть у каждой компании. Именно в ней хранится вся чувствительная и важная для бизнеса информация. В этой статье расскажем, какие есть базы данных и как правильно с ними работать: подключаться и делать запросы.</p><h2>Такие разные базы данных</h2><p>Базы данных — основной инструмент программирования. БД хранят важную информацию о пользователях и позволяют этой информацией управлять.</p><p>Они делятся на два основных типа: реляционные и нереляционные.</p><p><b>Реляционная база</b> — как большой шкаф с ящиками. У каждого своя подпись, и в нём лежат только определенные вещи по порядку. Например, один ящик для носков, второй для шапок, третий –– для носовых платков.</p><p>Все очень аккуратно, ничего не теряется, но если нужно что-то изменить, например, носки переложить в ящик с платками –– будет не так-то просто.</p><p>Чтобы делать запросы к реляционной базе, нужно использовать язык SQL. А обрабатывать эти запросы будет СУБД –– система управления базами данных. Простыми словами –– штука, которая помогает вам управлять самой базой. Что-то из шкафа вытащить, кого-то в него спрятать.</p><p><b>Нереляционная база данных</b> — как огромная коробка, куда можно складывать всё, что угодно и как угодно. Это удобно, но для системы с чёткой структурой не подойдёт.</p><p>Какой тип БД выбрать для проекта –– зависит от целей компании, размера и особенностей бизнеса. Разберём каждый тип.</p><h2>Реляционные базы данных (SQL)</h2><p>Реляционные базы –– таблицы, где данные организованы в строки и столбцы. У каждой таблицы строгая схема, которая определяет типы данных и их взаимосвязь. Реляционные базы используются для приложений, где важны точность и согласованность информации.</p><p>Посмотрим на конкретные примеры.</p><h3>MySQL</h3><p>Очень популярная база с открытым исходным кодом. Она надежная, удобная и быстрая. MySQL часто используют для веб-приложений. Например,  Airbnb, Netflix и Uber.</p><p>Её любят, потому что у неё качественная и подробная документация, а также большое сообщество.</p><h3>PostgreSQL</h3><p>Мощная и универсальная реляционная база данных, которая предлагает расширенные возможности. Среди них —  работа с JSON, хранение массивов и поддержка пользовательских типов данных. Это делает PostgreSQL гибким инструментом для решения самых разнообразных задач.</p><p>СУБД используется в сложных приложениях, где важны высокая надежность и гибкий функционал.</p><h3>SQLite</h3><p>Эта однофайловая СУБД мало весит и не требует сервера для работы. Она идеально подходит для небольших приложений, мобильных устройств и тестирования.</p><p>Если нужно будет что-то куда-то переносить –– процесс пойдёт быстро. За эту простоту SQLite и любят.</p><h2>Нереляционные базы</h2><p>Нереляционные базы данных подходят для работы с большими объемами, которые могут быть неструктурированными или слабо структурированными.</p><p>В отличие от реляционных, у этих баз нет строгой схемы, что делает их гибкими и удобными для многих современных приложений.</p><p>Разберём самые популярные варианты нереляционных баз.</p><h3>MongoDB</h3><p>NoSQL-база данных, которая хранит данные в формате JSON, обеспечивает гибкость и простоту работы. MongoDB часто используется в приложениях, требующих динамической структуры данных, а также в учебных и небольших проектах благодаря своей доступности и лёгкости освоения.</p><h3>Redis</h3><p>Высокопроизводительная база данных, оптимизированная для минимизации задержек при доступе к данным. Идеально подходит для кэширования, управления сессиями, обработки очередей сообщений и других задач, требующих мгновенного отклика.</p><h2>В чём разница между базами данных</h2><p>Выбор базы данных — долгосрочное решение, от которого зависит эффективность работы вашего приложения. Давайте подробнее разберём различия между реляционными и нереляционными базами данных, чтобы помочь вам сделать осознанный и правильный выбор.</p><p>Структура данных</p><figure><img src="https://media.tproger.ru/user-uploads/105990/2025-01-10/0da24965-ccd4-4f79-b395-82f484763ae0.png" alt="" /></figure><p>Масштабируемость</p><figure><img src="https://media.tproger.ru/user-uploads/105990/2025-01-10/30f9658d-daa5-46b7-932f-0e7772881a03.png" alt="" /></figure><p>Скорость и производительность</p><figure><img src="https://media.tproger.ru/user-uploads/105990/2025-01-10/05f69730-26ff-4f9c-ba20-044a59df6d02.png" alt="" /></figure><p>Выбор зависит от потребностей проекта: для сложных транзакционных систем с жесткой структурой данных лучше использовать реляционные базы, а для гибких, масштабируемых приложений с неструктурированными данными — нереляционные.</p><p>Для работы с базами данных важно уметь выполнять ключевые операции: извлечение, добавление, обновление и удаление данных. Разберём, что такое SQL-запросы и как выполнение этих операций отличается в реляционных и нереляционных базах данных.</p><h2>Подключаемся к MySQL</h2><p>Для реляционных баз данных используются стандартные операции:</p><ul><li>SELECT — получить данные;</li><li>INSERT — добавить новые данные;</li><li>UPDATE — обновить существующие данные;</li><li>DELETE — удалить данные.</li></ul><p>Поработаем с ними на примере MySQL. Представим, что у нас есть база с пользователями нашего блога. Чтобы поработать с ней, нужно настроить соединение с программой. Разберём этот процесс шаг за шагом.</p><h3>Шаг 1. Устанавливаем библиотеку</h3><p>Для работы с MySQL в Python нам понадобится библиотека mysql-connector-python.</p><p>Дла этого напишем команду в терминале:</p><p>С помощью неё можно подключиться к базе, отправлять к ней запросы и обрабатывать результаты этих запросов.</p><h3>Шаг 2. Настраиваем подключение</h3><p>Чтобы подключиться к базе данных MySQL, необходимо знать её параметры:</p><ul><li>Хост: адрес сервера, где находится база данных. Это может быть localhost;</li><li>Порт: номер порта для подключения. По умолчанию для MySQL — 3306-й порт;</li><li>Пользователь: имя пользователя, который имеет доступ к базе;</li><li>Пароль: пароль для этого пользователя;</li><li>Имя базы данных: конкретная база, с которой вы хотите работать.</li></ul><p>Как это может выглядеть:</p><ul><li>Хост: localhost</li><li>Порт: 3306</li><li>Пользователь: root</li><li>Пароль: “”</li><li>Имя базы данных: test_db</li></ul><h3>Шаг 3. Подключаемся к базе данных</h3><p>Теперь создадим программу, которая установит соединение с MySQL.</p><h4>Что тут происходит:</h4><ol><li>Импорт нужных библиотек: mysql.connector — это основной модуль для работы с MySQL в Python. Error — класс для обработки ошибок;</li><li>Настройка подключения: Используем метод mysql.connector.connect, в который передаются параметры: host, user, password и database. Это наши настройки для базы данных;</li><li>Проверка подключения:Метод is_connected() из переменной connection проверяет, удалось ли установить соединение;</li><li>Выполнение запросов:Создаём курсор для выполнения SQL-запросов с помощью connection.cursor(). После этого можно делать запросы к базе. Выбираем конкретную базу, получаем все данные, добавляем в неё сущность, а затем удаляем её. Последний запрос — получение одного конкретного юзера;</li><li>Закрытие соединения:Закрываем соединение с помощью метода connection.close() для того, чтобы избежать утечек ресурсов и проблем с безопасностью.</li></ol><p>Вы также можете обернуть код в try-except, чтобы отловить непредвиденные ошибки и не уронить сервер.</p><p>С помощью этого подхода можно быстро настроить подключение к MySQL и начать работу с базой данных в Python-проекте.</p><h2>Подключаемся к MongoDB</h2><p>Для нереляционных баз используем NoSQL-запросы. Функционал такой же: добавление, удаление, изменение и получение данных. А выглядят они по-другому. Посмотрим на примере MongoDB.</p><p>Для работы с MongoDB в Python используется библиотека pymongo. Разберём, как настроить соединение, выбрать базу данных и выполнить простой запрос.</p><h3>Шаг 1. Устанавливаем библиотеку</h3><p>Для начала установим библиотеку pymongo на своём компьютере. Введём команду в терминал:</p><h3>Шаг 2. Настраиваем подключение</h3><p>Чтобы подключиться к MongoDB, нужно знать параметры базы:</p><ul><li>Хост: адрес сервера;</li><li>Порт: порт, на котором работает MongoDB. По умолчанию — 27017;</li><li>Имя базы данных: название базы, которую вы хотите использовать;</li></ul><p>MongoDB автоматически создаст базу данных, если она не существует.</p><p>Пример настроенной базы:</p><ul><li>Хост: localhost</li><li>Порт: 27017</li><li>Имя базы данных: test_db</li></ul><h3>Шаг 3. Подключаемся и выполняем запрос</h3><p>Нам нужно подключиться к базе данных MongoDB, добавить, удалить и получить данные коллекции:</p><p>Разберём подробнее:</p><ol><li>Импорт библиотеки: Используем MongoClient из библиотеки pymongo, чтобы создать подключение к базе;</li><li>Создание подключения: Используем данные хоста и порта, где запущен MongoDB;</li><li>Выбор базы данных и коллекции: Выбираем нужную базу данных. В нашем случае –– test_db. Если базы нет, MongoDB создаст её при добавлении первого документа;</li><li>Добавление документа: Добавляем новый документ с пользователем с помощью insert_one(). А также выводим в консоль информацию о добавленном пользователе;</li><li>Удаление. Удаляем юзера и проверяем, получилось ли. Если нет, в консоли увидим запись, что такого юзера не существует;</li><li>Выполнение запроса: Ищем только что добавленный документ методом find_one() и проверяем, нашли ли. Тут то же самое: если юзера нет, то в консоли мы это увидим;</li><li>Закрытие подключения: Закрываем вызов через client.close() и оповещаем самих себя в консоли.</li></ol><p>Вы могли заметить, что мы везде заканчиваем код закрытием соединения. Это важно по нескольким причинам:</p><ol><li>Подключение к базе данных –– ресурсы сервера и клиента. Если соединение не закрыть, ресурсы останутся занятыми. Значит, их нельзя будет использовать для более нужных вещей;</li><li>Неиспользуемые открытые соединения приводят к утечкам памяти;</li><li>Открытое соединение может стать уязвимостью для атак, несанкционированного доступа или SQL-инъекций.</li></ol><p>В этом фрагменте кода реализовано то же самое, но с улучшениями. Мы обернули его в блок try-except, чтобы защитить сервер от ошибок и предотвратить его аварийное завершение работы.</p><p>В этой статье мы лишь поверхностно коснулись темы баз данных. Если у вас ещё нет лишних денег на выкуп своих данных, рекомендуем инвестировать время в изучение основ и тонкостей работы с БД.</p><p>Качественно спроектированная база данных не только повысит эффективность и прибыльность бизнеса, но и защитит его от серьёзных рисков. Кроме того, вы значительно прокачаете свои навыки программирования.</p><p>Удачи в разработке и безопасности ваших данных! 🚀</p>]]></content:encoded>
    </item>
    <item>
      <title>Pub/Sub — когда нужно масштабировать приложения</title>
      <link>https://tproger.ru/articles/pub-sub---kogda-nuzhno-maswtabirovat-prilozheniya</link>
      <comments>https://tproger.ru/articles/pub-sub---kogda-nuzhno-maswtabirovat-prilozheniya?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сергей Лалетин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/pub-sub---kogda-nuzhno-maswtabirovat-prilozheniya</guid>
      <description><![CDATA[<p>Узнайте, как работает архитектура Pub/Sub для масштабирования приложений, организации обмена данными между микросервисами и создания надёжных систем. Примеры с Apache Kafka, RabbitMQ и Redis.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/pub-sub---kogda-nuzhno-maswtabirovat-prilozheniya">Pub/Sub — когда нужно масштабировать приложения</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 28 Dec 2024 09:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Когда нужно создать приложение, которое будет справляться с большим количеством пользователей и данных, разработчики используют подход Pub/Sub (сокращение от англ. Publisher/Subscriber, то есть Издатель/Подписчик). Проще всего сравнить эту модель с рассылкой новостей:</p><ul><li><b>Издатель </b>— это тот, кто отправляет информацию. Например, новостной сайт.</li><li><b>Подписчик</b> — это тот, кто подписался и хочет получать информацию. Например, читатель новостного сайта, — он будет получать уведомления о новых статьях.</li></ul><p>Pub/Sub помогает создавать надёжные и быстрые приложения, даже если пользователей много. О том, как это работает, читайте ниже.</p><h2>Что внутри Pub/Sub</h2><p>Чтобы было проще понять, как устроена архитектура Pub/Sub, приведём простую аналогию. Представьте, что у вас есть канал в Telegram. Автор канала (Publisher) пишет сообщение, а все участники (Subscribers) его получают. Если один из подписчиков покидает канал или кто-то, наоборот, присоединяется, автору ничего не нужно менять — он, как и раньше, публикует посты, уведомления о которых приходят всем, кто подписан на этот канал.</p><p>Большие приложения состоят из множества независимых частей — микросервисов. Чтобы приложение работало правильно, микросервисы должны быстро обмениваться данными. И как раз для этого разработчики и используют подход Pub/Sub.</p><p>Мы уже рассказали об издателях и подписчиках, но в этой архитектуре также участвуют и брокеры (или посредники) — именно они отвечают за доставку сообщений.</p><p>Забегая вперёд скажем, что именно брокер обеспечивает масштабируемость в системе. Он организует передачу сообщений и не блокирует выполнение других операций, даже если участников становится очень много. Код брокера обычно пишут на Python или Java, используя при этом дополнительные библиотеки.</p><p>Главная идея такая: издатель не знает, кто получит его сообщения, а подписчики не запрашивают эти сообщения напрямую. Всё происходит через посредника. Это работает следующим образом:</p><ol><li>Издатель отправляет сообщение (например, текст или видео) в определённый канал — он называется темой (Topic).</li><li>Получатели подписываются на нужную тему, чтобы принимать эти сообщения.</li><li>Как только сообщение отправлено в тему, все подписчики его получают.</li></ol><p>Этот процесс проиллюстрирован на следующей схеме:</p><figure><img src="https://media.tproger.ru/user-uploads/100503/2024-12-19/d0de5fc7-4786-41fa-a1d5-881e7f3a7cfb.png" alt="" /><figcaption>Помимо всех действующих лиц на схеме вы можете увидеть каналы. Input channel — это место, куда издатель отправляет сообщения, а output channel — место, откуда подписчики получают эти сообщения.</figcaption></figure><p>Обмен сообщениями происходит асинхронно. Это значит, что отправитель (или издатель) не ждёт, когда получатель (или подписчик) обработает сообщение. Поэтому компоненты приложения могут работать независимо друг от друга, но при этом обмениваться данными.</p><p>Паттерн Pub/Sub тем и отличается от стандартных алгоритмов, в которых очередь из сообщений продолжает формироваться, пока пользователи либо службы не сделают запрос и не извлекут их.</p><p>Итак, почему же эта архитектура так удобна?</p><p>Суть в том, что разработчикам не нужно вручную прописывать, кто кому и что отправляет. Например, если издатель опубликовал событие «пользователь сделал заказ», система сама оповестит все нужные сервисы: склад получит команду проверить товар, доставка начнёт планировать маршрут, а клиент получит соответствующее уведомление. И всё это будет происходить параллельно.</p><p>Сообщение обязательно дойдёт до всех подписчиков, если они не настроили фильтры, чтобы его игнорировать. Например, если один сервис подписан только на сообщения «заказы», он не будет получать данные из темы «новые пользователи».</p><p>Теперь для наглядности приведём пример простого кода брокера.</p><h2>Пример кода брокера на Python</h2><p>Код можно написать на разных языках, но для примера мы возьмем Python — просто потому, что его легче понять. Мы используем популярную библиотеку для работы с очередями — asyncio. А чтобы продемонстрировать случайную задержку при обработке сообщений, используем библиотеку random.</p><p>В коде реализовано всё то, о чём мы говорили выше: когда издатель публикует сообщение, оно мгновенно передаётся всем подписчикам, которые подписаны на соответствующую тему. Далее каждый подписчик асинхронно обрабатывает сообщение (с задержкой, чтобы имитировать реальную работу приложения).</p><p>Этот код — очень простой пример реализации брокера. Для более сложных систем, например, с постоянным хранением сообщений, обработкой ошибок и масштабированием, нужна инфраструктура серьёзнее. Для её создания используют внешние брокеры сообщений — те же <b>RabbitMQ</b> или <b>Apache Kafka</b>. Об этом мы рассказываем ниже.</p><h2>Как выбрать инструменты для реализации Pub/Sub</h2><p>Как мы уже сказали, брокера для Pub/Sub можно написать на разных языках с использованием дополнительных библиотек. Выбор зависит от масштаба проекта, специфики, количества пользователей.</p><p>Рассмотрим наиболее популярные инструменты.</p><h3>Apache Kafka</h3><p>Это платформа для обмена данными между приложениями в реальном времени. Она позволяет передавать большие объёмы информации быстро и надёжно — можно не переживать, что данные будут потеряны.</p><p>Например, если приложение A хочет отправить данные приложению B, оно не делает это напрямую, а отправляет сообщение в Kafka. Kafka сохраняет эти сообщения и передает их всем заинтересованным приложениям, которые подписались на получение этой информации.</p><p>Kafka особенно полезен там, где нужно работать с большими объёмами данных в реальном времени — например, в системах стриминга видео или аналитики. Она может обрабатывать миллионы событий в секунду и гарантирует, что данные не потеряются.</p><p>Вот где используют Apache Kafka:</p><ul><li><b>LinkedIn</b> использует Kafka для передачи сообщений между микросервисами.</li><li>Netflix применяет Kafka для контроля количества событий, обрабатываемых одновременно, и передачи данных из нескольких потоков.</li><li>The New York Times использует Kafka для публикации новостей в режиме реального времени.</li></ul><p>Приведём пример простой публикации и получения сообщений (учитывайте, что у вас должна быть установлена соответствующая библиотека и запущен Apache Kafka на локальном сервере или в облаке):</p><p>Producer отправляет сообщения в Kafka в определённую тему (в нашем случае — test_topic). Consumer подписывается на эту тему и получает сообщения.</p><p>В реальных системах таких издателей и подписчиков может быть много, и Kafka помогает координировать их работу.</p><h3>RabbitMQ</h3><p>Это брокер сообщений или посредник, который помогает разным приложениям обмениваться данными. Его разработали в 2007 году на Erlang — языке, который отлично подходит для создания устойчивых к сбоям систем.</p><p>RabbitMQ поддерживает несколько протоколов обмена данными, поэтому его можно использовать в разных проектах. Например, он может связывать микросервисы, обрабатывать фоновую информацию и управлять большими объёмами сообщений.</p><p>RabbitMQ работает как почтовая служба:</p><ol><li>Одно приложение отправляет сообщение (письмо).</li><li>RabbitMQ принимает это сообщение и сохраняет его в очереди.</li><li>Другое приложение (подписчик) получает сообщение из этой очереди.</li></ol><p>Особенность RabbitMQ — push-модель. Брокер сам отправляет сообщения получателю сразу, как только они появляются. Получателю не нужно запрашивать данные постоянно — он просто ждёт, пока RabbitMQ пришлёт новые сообщения.</p><p>Эта особенность полезна, когда нужно быстро информировать участников системы о важных событиях. Например, отправлять уведомления о статусе заказа, оповещать системы мониторинга о проблемах или обновлять данные в реальном времени.</p><p>Покажем пример (RabbitMQ можно подключить к Python с помощью библиотеки pika):</p><p>Вот как это работает:</p><p>Сначала Producer отправляет сообщение в очередь test_queue. Затем Consumer подписывается на эту очередь и получает сообщения. RabbitMQ принимает сообщение и доставляет его получателю сразу после появления в очереди (та самая push-модель).</p><h3>Redis</h3><p>Система управления данными, которая поддерживает не только стандартную очередь сообщений, но и паттерн Pub/Sub. Этот инструмент используют для организации обмена сообщениями, при этом он хранит промежуточный контент (например, набранный, но не отправленный текст), управляет базами данных небольших приложений и одностраничных сайтов.</p><p>Redis — идеальный выбор для проектов, где важна скорость доставки информации, в том числе в биржевых и финансовых сервисах. Его применяют также для реализации механизма подписок. Вот пример кода:</p><p>В примере издатель отправляет 5 сообщений с небольшой паузой, а подписчик сразу их получает.</p><h2>Примеры реализации Pub/Sub</h2><p>Паттерн используют в сферах, где нужно организовать быстрый обмен информацией между распределёнными компонентами системы. Автоматизация процессов — ключевое направление реализации Pub/Sub.</p><p>Рассмотрим наиболее актуальные области применения шаблона.</p><h3>Мониторинг системы и мгновенная отправка уведомлений</h3><p>Те самые темы, о которых мы рассказали раньше, создаются для различных категорий данных — например, загрузка процессора, состояние серверов, журналы ошибок. Каждая служба может публиковать свои метрики в топиках, а подписчики (системы визуализации, алерты и дашборды) получают эти данные для обработки.</p><p>Вот пример сценария:</p><ol><li>В крупномасштабной системе мониторинга серверов сообщения о сбоях отправляются в специальный канал.</li><li>Система визуализации Grafana или Prometheus подписана на этот канал и сразу обновляет дашборды.</li><li>В случае критического сбоя на основной системе резервный сервер автоматически включается через подписку на ту же тему.</li></ol><p>Pub/Sub позволяет внедрить автоматические реакции на определенные события. Например, если загруженность сервера превышает 90%, система может отправить сообщение об аварийном переключении нагрузки или даже автоматически включить дополнительные вычислительные узлы.</p><h3>IoT (Интернет вещей)</h3><p>Смарт-устройств становится все больше, и для каждого из них необходимо организовать надёжный метод сбора и передачи информации. Здесь также на помощь приходит Pub/Sub.</p><p>Гаджеты могут выступать издателями: они отправляют показания сенсоров, данные о температуре, движении, состоянии и других параметрах на центральный сервер или в облако.</p><p>Для лучшего понимания приведём пример:</p><ol><li>Датчики движения отправляют сообщения в тему «Безопасность».</li><li>Подписчик — система управления домом — принимает эти сообщения и отправляет уведомления на мобильное приложение пользователя.</li><li>Тем временем умная лампа подписана на другой канал и автоматически включается по сигналу о движении.</li></ol><p>Pub/Sub обеспечивает масштабируемость IoT-систем — новые устройства можно легко подключать к существующим темам, не нарушая работу всей архитектуры.</p><h3>Резервное копирование</h3><p>Многим компаниям важно не только хранить информацию, но и организовать надёжное резервное копирование, чтобы уменьшить риски потери данных. Pub/Sub помогает и здесь: можно автоматически собирать и передавать резервные копии данных в облачные хранилища или на резервные серверы. Вот как это может работать:</p><ol><li>Каждую ночь системы баз данных отправляют уведомление в тему «Резервное копирование».</li><li>Подписчик (облачный сервис хранения) получает это сообщение и инициирует процесс копирования данных.</li><li>Если основной сервер недоступен, сообщение передаётся на резервный сервер, который берёт на себя задачу сохранения.</li></ol><p>Также Pub/Sub позволяет настроить многоуровневое резервирование: копии данных могут одновременно отправляться на несколько хранилищ, что и позволяет минимизировать риски потери информации.</p><h2>Преимущества и недостатки использования Pub/Sub</h2><p>Давайте кратко пройдёмся по основным плюсам этого паттерна:</p><ul><li>Систему легко адаптировать под любое количество пользователей. Новых издателей и подписчиков можно добавлять без потери производительности, а архитектуру тем и посредников менять без затрагивания базовых компонентов.</li><li>Издатели и подписчики работают независимо друг от друга. Это позволяет создавать безопасные, модульные системы, где компоненты не зависят от прямых связей и могут развиваться отдельно.</li><li>Pub/Sub можно использовать с разными языками программирования (например, Python или Java) и интегрировать с различными брокерами сообщений.</li><li>Сообщения доставляются мгновенно, что делает Pub/Sub идеальным решением для сервисов реального времени.</li><li>Сообщения дублируются в хранилищах для гарантированной доставки. Дополнительно обеспечивается проверка подлинности издателей и шифрование данных для защиты информации.</li></ul><p>У паттерна есть и недостатки. Он слишком сложен для использования в небольших приложениях, требует грамотной настройки и сопровождения. Если продукт не нуждается в масштабировании, то применение Pub/Sub — неоправданная трата ресурсов. Не всем системам требуется такой уровень сложности и надёжности.</p><p>Для потоковой передачи медиафайлов Pub/Sub — не лучший выбор, поскольку работает в асинхронном режиме. Для конференций в формате видео, голосовых коммуникаций по протоколу IP технология не подойдёт.</p><h2>Итоги</h2><p>Pub/Sub — это эффективный и сравнительно простой способ организовать обмен данными между компонентами системы. Он лежит в основе работы распределённых приложений с микросервисной архитектурой и обеспечивает передачу информации в реальном времени.</p><p>Технология работает асинхронно, что позволяет системе легко масштабироваться и разделять её на независимые модули. Благодаря брокерам сообщений, которые обрабатывают и распределяют данные, приложения не перегружаются, а обмен информацией становится быстрым и безопасным.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как использовать серверы Redis и Memcached для кэширования</title>
      <link>https://tproger.ru/articles/kak-ispolzovat-servery-redis-i-memcached-dlya-kewirovaniya</link>
      <comments>https://tproger.ru/articles/kak-ispolzovat-servery-redis-i-memcached-dlya-kewirovaniya?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анастасия Полищук]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-ispolzovat-servery-redis-i-memcached-dlya-kewirovaniya</guid>
      <description><![CDATA[<p>Что такое кэширование. Показываем основные способы использовать серверы Redis и Memcached для кэширования. Рассматриваем пошаговую инструкцию ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-ispolzovat-servery-redis-i-memcached-dlya-kewirovaniya">Как использовать серверы Redis и Memcached для кэширования</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 30 Oct 2024 10:00:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Чем, в первую очередь, руководствуются пользователи, открывая ваш сайт или запуская мобильное приложение? Правильно, скоростью работы! Если страница не загрузится за пару секунд, большинство посетителей просто закроет ее и уйдет к конкурентам.</p><p>Чтобы основные данные загружались максимально быстро, их рекомендуется кэшировать в оперативную память сервера или использовать кэширование на пользовательское устройство. Сегодня рассмотрим вариант создания кэша на сервере с помощью Redis и Memcached.</p><h2>Что такое Redis и Memcached</h2><p>Разберемся, что представляют из себя обе системы и перечислим их основные различия.</p><h3>Краткий обзор Redis: структура данных, поддержка персистентности, возможности</h3><p>Redis — это популярная key-value система хранения данных в оперативной памяти. Она позволяет работать с большим количеством типов данных, среди них списки и множества, битовые массивы, гео-координаты и другие.</p><p>Redis нельзя назвать полноценной СУБД, однако его возможностей достаточно для того, чтобы загружать данные из основной базы данных в кэш, получать и передавать информацию, взаимодействовать с пользователем и оперативно реагировать на все запросы. Для Redis доступно несколько вариантов отказоустойчивых конфигураций и подходов, в том числе кластеризация и шардирование.</p><p>Для обеспечения персистентности данных Redis выгружает слепки на жесткий диск сервера, однако этот процесс может требовать больших ресурсов системы и занимать редис-сервер на продолжительное время из-за атомарности выполнения операций.</p><h3>Краткий обзор Memcached: легковесное кэширование в памяти, простота и высокая скорость</h3><p>Memcached — это сервис кэширования данных в оперативную память, созданный в далеком 2003 году. Большой плюс этого сервиса в том, что скорость его работы не зависит от количества хранимых в кэше данных, а интерфейс максимально прост. Однако есть и существенное ограничение: Memcached позволяет хранить лишь данные типа set.</p><h3>Основные различия между Redis и Memcached</h3><p>Сравнивая две системы, в первую очередь еще раз отметим отличия в хранимых данных: серверы Redis поддерживают несколько типов данных, тогда как Memcached способен хранить лишь множества (set). Redis умеет сохранять слепки данных на жесткий диск, для него доступна LRU-политика удаления данных и полное отключение функции освобождения места. А также, в отличие от Memcached, этот сервис поддерживает master-slave репликацию и подходит для создания очередей сообщений.</p><h2>Настройка Redis для кэширования</h2><p>Далее разберемся с тем, как установить Redis, задать ему базовые настройки и использовать key-value систему, запуская кэширование базы данных.</p><h3>Установка Redis и базовая конфигурация</h3><p>Так как Redis создан для Unix-подобных систем, его установка на Windows Server возможна только через wsl. Когда в вашем распоряжении есть сервер, например, на Ubuntu, установить Redis можно через apt или apt-get.</p><p>Для проверки успешной установки можно запустить пинг сервера Redis через redis-cli:</p><p>Фактически, система доступна для использования уже на этом этапе, однако рекомендуется изменить некоторые настройки для повышения ее безопасности. Изменения вносятся в файл конфигурации:</p><p><br />Можно изменить порт — по умолчанию Redis функционирует на 6379 — дать доступ по сети и задать пароль администратора. Для этого меняем bind 127.0.0.1 -:: 1 на bind 0.0.0.0, раскомментируем строку requirepass и в ней прописываем сам пароль. Например: requirepass averyVERYlongPASSword72349.</p><p>После внесения изменения перезапускаем Redis-server:</p><h3>Как использовать Redis для кэширования типа ключ-значение</h3><p>Благодаря тому, что Redis — достаточно универсальное in-memory хранилище, его можно использовать при кэшировании данных в формате ключ-значение в самых разных проектах. Он подойдет и для хранения API-запросов, и для пользовательских сессий, и для оптимизации работы с основными базами данных, например, Postgresql или MySQL. Каждому элементу в Redis присваивается собственный уникальный ключ, по нему в любой момент можно получить оперативный доступ, изменить или удалить запись. Максимальный объем данных в одной записи-значении — 512Mb.</p><p>В Redis доступны разные политики вытеснения, вы можете самостоятельно выбрать принцип, по которому сервер будет удалять ненужные данные при переполнении выделенной ему памяти. Паттерн кэширования также остается «на совести» разработчика.</p><p>Если для сайта или приложения нужен кэш большого объема, применяется принцип холодного кэширования, когда данные загружаются на старте системы, а значит, находятся в Redis еще до прямого запроса от пользователя.</p><h3>Примеры использования Redis с языками программирования (например, Python или Node.js)</h3><p>Разберемся, как использовать Redis из Python, добавлять и удалять данные на примере работы с помощью клиента redis-py. Первым делом его нужно установить:</p><p>Далее запишем в Redis данные и получим одну из записей по ключу.</p><p>Подключаемся к Redis.</p><p>Записываем данные.</p><p>Получаем запись по ключу "Two" и выводим с декодированием.</p><p>Видим в консоли:</p><p><br />Для удаления используем команду delete.</p><p>Подробности по использованию redis-py можно найти в официальной <a href="https://redis-py.readthedocs.io/en/stable/">документации</a>: этот клиент активно поддерживается и пользуется популярностью у разработчиков.</p><h2>Настройка Memcached для кэширования</h2><p>Теперь перейдем к Memcached: разберемся, как его установить и настроить, и поймем, какие ограничения имеются у этого сервиса.</p><h3>Установка и настройка Memcached</h3><p>Установка Memcached мало отличается от любого другого сервиса, для нее используется пакетный менеджер. В Ubuntu и Debian — apt, в CentOS или Fedora — yum. Для примера, установка на Ubuntu будет выглядеть так:</p><p>По-умолчанию используется порт 11211, но его можно изменить в файле конфигурации /etc/memcached.conf. Основные параметры заданы буквенными ключами, для некоторых задать дополнительные настройки:</p><ul><li>logfile/var/log/memcached.log</li></ul><ul><li><b>-v</b> и <b>-vv</b> – подробный и очень подробный режимы вывода информации</li></ul><ul><li><b>-m</b> – доступный максимум оперативной памяти, по-умолчанию 64Мб</li></ul><ul><li><b>-u</b> – системный пользователь, от имени которого запущен сервис</li></ul><ul><li><b>-l </b>127.0.0.1 – IP-адрес, на котором Memcached будет ожидать соединения. По-умолчанию сервис недоступен по сети</li></ul><ul><li><b>-p</b> – порт</li></ul><ul><li><b>-с</b> – допустимое количество одновременных подключений</li></ul><ul><li><b>-P</b> /var/run/memcached/memcached.pid</li></ul><p>После внесения изменения в файл настроек Memcached необходимо перезапустить:</p><h3>Принцип работы с ключ-значение в Memcached</h3><p>Структура Memcached похожа на структуру хранения данных в Redis: каждому значению value присваивается уникальный ключ key. В value может храниться строка или бинарные данные.</p><p>Для внесения записи в базу достаточно воспользоваться командой &lt;имя ключа&gt; &lt;флаги (можно оставить 0)&gt; &lt;время хранения в секундах (0 – вечно)&gt; &lt;объем памяти в байтах, зарезервированный для хранения значения&gt;:</p><p>После введения этой команды в строку терминала можно ввести значение для хранения.</p><p>Для получения данных используется команда get &lt;ключ&gt;, для удаления — delete &lt;key&gt;.</p><p>Однако все эти нативные команды используются достаточно редко, так как каждый популярный язык программирования содержит собственные методы и клиенты для работы с Memcached.</p><h3>Примеры использования Memcached с популярными языками программирования.</h3><p>Посмотрим, как обращаться к Memcached с помощью Python и какие основные команды можно выполнить.</p><p>Для начала установим библиотеку, например, pymemcache:</p><p>Подключимся к запущенному серверу Memcached, создадим запись, внесем и получим данные.</p><p>Ниже показан код, который позволит проверить наличие записи в Memcached, и при ее отсутствии запустит функцию updating_key. Предположим, что она задана заранее и получает нужное значение из основной БД.</p><p>Также можно удалить запись по ключу при помощи delete:</p><p>Операции set, get и delete — основные способы взаимодействия с memcached из Python, они позволят создавать кэш и пользоваться им по мере необходимости.</p><h2>Сравнение Redis и Memcached</h2><p>Для типовых задач кэширования на большинстве проектов подходят оба решения. Однако каждое из них имеет свои особенности, бонусы и недостатки.</p><h3>Производительность: сравнение скорости работы Redis и Memcached</h3><p>Считается, что Memcached — самый быстрый сервис, который можно использовать при кэшировании. Однако на практике его производительность и скорость работы на запись вполне сравнимы с Redis. На запись миллиона ключей Memcached тратит около трех миллисекунд, Redis — около 15-ти. А операция считывания данных и вовсе выводит вперед Redis, в котором время практически не растет с ростом числа считываемых ключей, тогда как у Memcached возрастает, пусть и незначительно.</p><h3>Гибкость данных: поддерживаемые типы данных в Redis и ограничение Memcached</h3><p>По параметру «количество поддерживаемых типов данных» Redis обходит Memcached на голову, поскольку последний умеет хранить лишь строковые и бинарные значения. А с помощью Redis можно кэшировать помимо этих двух типов еще и списки, множества, упорядоченные множества, битовые поля и геопространственные данные.</p><p>Не будем забывать и об ограничениях Memcached, в котором длина ключа ограничена 250 байтами, а размер значения не может превышать 1 Мб по-умолчанию и 128 Мб — при внесении изменений в настройки.</p><h3>Персистентность данных: плюсы и минусы каждого решения</h3><p>В описании Redis мы уже упоминали о том, что он умеет записывать данные на жесткий диск. С одной стороны, это обеспечивает их сохранность, с другой занимает ресурсы сервера. Редис — однопоточная система с атомарным выполнением операций, а значит, выгрузка большого слепка данных на диск блокирует все остальные процессы. Для борьбы с этим существует несколько способов, в том числе запуск Redis cluster и разбиение задачи выгрузки данных в Redis на несколько небольших пакетов.</p><p>В отличие от Redis, Memcached вовсе не является персистентным хранилищем и в нем отсутствуют функции для обеспечения сохранности данных. То есть после ребута сервер начинает работать с пустым кэшем и после сбоя Memcached все данные в нем будут потеряны.</p><h3>Когда использовать Redis и когда Memcached для конкретных задач.</h3><p>Говоря о конкретных случаях использования того или иного решения, можно точно сказать, что выбор остается за разработчиком. Но если в небольшом приложении нужно кэшировать простые строковые данные, а сброс кэша в случае перезагрузки сервера не является чем-то критичным, то отлично подойдет Memcached. Для более сложных ситуаций с хранением разных типов данных и необходимостью повышать отказоустойчивость, стоит отдать предпочтение Redis.</p><p>Например, для кэширования пользовательских сессий или результатов запросов к API больше подойдет Redis, а для выгрузки части информации из основной базы данных для ускорения доступа к ней достаточно будет и Memcached.</p><h2>Примеры использования кэширования в реальных проектах</h2><p>Лучше всего понять, в каких задачах разработчик сталкивается с необходимостью кэширования, помогут примеры из реальных проектов.</p><h3>Кэширование результатов API запросов</h3><p>Мало кто создает API просто так «для галочки». Разработчик обычно предполагает, что запросы в интерфейс будут достаточно многочисленными, а некоторые из них вполне могут повторяться. Или некоторые отчеты будут требовать довольно продолжительных вычислений.</p><p>В таких ситуациях для снижения нагрузки на основную БД и ускорения получения ответов от приложения пользуются кэшированием данных в RAM. Это позволит давать быстрый доступ к одним и тем же данным без постоянного подключения к основной базе. А холодный кэш, сформированный из результатов самых тяжелых отчетов, позволит пользователю не ждать их формирования, а получить данные сразу после отправки запроса.</p><h3>Кэширование сессий пользователей</h3><p>Пользовательские сессии — еще один повсеместно распространенный повод прибегнуть к кэшированию. Получение пользовательского токена от сервера авторизации — достаточно продолжительный процесс, и для того чтобы не повторять его при каждом запросе, токен часто хранят в кэше на пользовательском устройстве.</p><p>На стороне сервера в таком случае таблица с привилегиями для каждого токена может храниться в кэше Redis или Memcached, так проверка прав доступа будет происходить максимально быстро.</p><h3>Кэширование баз данных и оптимизация запросов</h3><p>Сервисами для кэширования пользуются и с целью хранения данных из основной БД. Кто-то загружает в кэш всю базу, кто-то — лишь ее наиболее часто используемую часть, но цели в обоих случаях совпадают. Во-первых, кэширование баз данных ускоряет процесс получения информации по запросу: RAM отдает данные гораздо быстрее. Во-вторых, позволяет снизить нагрузку на систему: при хранении части данных в оперативной памяти многие запросы проходят вовсе без обращения к жесткому диску.</p><h2>Советы по оптимизации кэширования</h2><p>Несмотря на всю прелесть использования Redis и Memcached, будем помнить о том, что не все кэширование одинаково полезно. Существует несколько деталей, на которые стоит обратить внимание при проектировании и создании кэша.</p><h3>Выбор правильной стратегии кэширования (Lazy caching, Write-through, Write-back)</h3><p>Одна из основных проблем кэширования — возможность того, что данные устареют и не обновятся вовремя. Для борьбы с этим недугом придумано несколько стратегий:</p><ul><li><b>Lazy caching</b> или ленивый кэш. Такой кэш просто хранится без всяких проверок до тех пор, пока не устареет, и подходит для редко обновляемых данных. Политика устаревания настраивается для каждого случая индивидуально.</li><li><b>Write-through cache</b> или сквозной кэш. Изменение данный проходит «насквозь», задевая сразу и кэш, и основное хранилище.</li><li><b>Write-back</b>. В этом случае все изменения вносятся оперативно только в кэш, а в основное хранилище выгружаются по команде или с определенным интервалом.</li><li><b>Синхронизированный кэш</b>. При получении запроса на данные, клиент проверяют метку их последнего изменения: если она не совпадает с меткой в основном хранилище, данные обновляются.</li></ul><p>При использовании любого паттерна кэширования важно помнить о когерентности данных, то есть о том, что все клиенты и страницы должны получать одинаково актуальные данные. Для этого часто используется принудительный сброс кэша при изменении данных.</p><h3>Настройка политики истечения срока действия кэша (TTL)</h3><p>Срок действия данных в кэше или срок их «жизни» — это время, в течение которого ключ считается актуальным. Стратегия TTL, или time-to-life — утилитарный подход и в чистом виде может привести к некорректности данных в кэше. Ведь очень редко удается заранее просчитать, сколько именно секунд или дней тот или иной ключ останется неизменным и актуальным.</p><p>Поэтому большинство систем, в том числе Redis и Memcached при автоматическом освобождении места ориентируются на частоту использования конкретных ключей и время, прошедшее с момента последнего к ним обращения. Хотя кэширование в Redis поддерживает политику volatile-ttl, при которой первыми удаляются ключи с истекшим или коротким оставшимся TTL.</p><h3>Управление памятью и избегание переполнения кэша</h3><p>Оперативная память сервера — величина небесконечная, а для хранения кэша выделяется и вовсе лишь ее часть. Значит, каждому разработчику, использующему кэширование, придется столкнуться с проблемой переполнения. Redis и Memcached выполняют удаление устаревших ключей в автоматическом режиме. Например, Redis при достижении лимита памяти перестанет загружать в кэш новые данные и начинает удаление согласно заданным настройкам, но на чтение продолжит работать в прежнем режиме.</p><p>Redis поддерживает несколько подходов к очищению кэша: volatile-lru, volatile-ttl, volatile-random, allkeys-lru и allkeys-random, а в Memcached максимальный срок жизни каждого ключа задается в параметрах при его создании. Обе эти системы чаще всего настраиваются для удаления ключей, которые не использовались дольше всего.</p>]]></content:encoded>
    </item>
    <item>
      <title>MongoDB: чем эта база отличается от других</title>
      <link>https://tproger.ru/articles/mongodb--chem-eta-baza-otlichaetsya-ot-drugih-</link>
      <comments>https://tproger.ru/articles/mongodb--chem-eta-baza-otlichaetsya-ot-drugih-?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дарья Закаулова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/mongodb--chem-eta-baza-otlichaetsya-ot-drugih-</guid>
      <description><![CDATA[<p>Что такое MongoDB. Показываем основные отличия от других баз данных. Рассматриваем преимущества и недостатки MongoDB ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/mongodb--chem-eta-baza-otlichaetsya-ot-drugih-">MongoDB: чем эта база отличается от других</a>»</p>]]></description>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[JSON]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 22 Oct 2024 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<h2>MongoDB — это…</h2><p><a href="https://www.mongodb.com">Официальный сайт</a> дает следующее определение:</p><blockquote>MongoDB — документоориентированная база данных, разработанная для простоты и  разработки и масштабирования приложений.</blockquote><p>Если конкретнее, то MongoDB — это нереляционная база данных, разработанная для работы с большими объемами данных и обеспечивающая высокую гибкость в управлении ими. Она использует схему хранения в формате BSON, что дает возможность динамически изменять структуру данных в зависимости от потребностей приложения.</p><p>BSON (Binary JSON) — это двоичный формат хранения данных, который используется в MongoDB. Он представляет собой бинарное представление JSON, но с рядом улучшений, которые делают его более эффективным для хранения и обработки данных. В отличие от обычного JSON, BSON поддерживает дополнительные типы данных, такие как даты, массивы байтов, и предоставляет оптимизированное сжатие и более быструю сериализацию.</p><p>Отсюда следует ее ключевая особенность — отсутствие строгой схемы данных. Это означает, что каждая запись в коллекции (эквивалент таблицы в реляционных БД) может иметь различную структуру полей. Эта гибкость делает MongoDB особенно популярной для работы с неструктурированными и полуструктурированными данными, а также для приложений, структура данных которых склонна к изменениям.</p><p>Кроме того, MongoDB поддерживает горизонтальное масштабирование благодаря механизму шардирования — распределения данных по нескольким серверам, что позволяет эффективно управлять большими массивами информации. Она также обладает встроенными механизмами репликации и автоматического восстановления, что обеспечивает высокий уровень доступности данных и отказоустойчивость.</p><p>За счет своей производительности, гибкости и возможности масштабирования, особенно популярной MongoDB стала в сфере веб-разработки и среди компаний, работающих с большими данными.</p><p>Монго поставляют три основных варианта использования своей базы данных:</p><ol><li><a href="https://www.mongodb.com/docs/atlas?tck=docs_server">MongoDB Atlas</a> — полностью управляемый сервис для развертывания MongoDB в облаке;</li><li><a href="https://www.mongodb.com/docs/manual/administration/install-enterprise/#std-label-install-mdb-enterprise">MongoDB Enterprise</a> — самоуправляемая версия MongoDB на основе подписки;<br /></li><li><a href="https://www.mongodb.com/docs/manual/administration/install-community/#std-label-install-mdb-community-edition">MongoDB Community</a> — бесплатная, доступная с исходным кодом и самостоятельно управляемая версия MongoDB.<br /></li></ol><p>Поэтому перед началом разработки вы можете выбрать ту поставку, которая подходит конкретно под ваш проект.</p><p>Для того, чтобы быстро разобраться с новой БД, MongoDB сделали хорошее <a href="https://www.mongodb.com/docs/manual/tutorial/getting-started/">интерактивное руководство</a>, в котором можно на практике познакомиться с синтаксисом и особенностями использования этой базы данных.</p><p>Посмотрим на примерах, как для такого типа данных будет работать фильтрация.</p><p>Пусть у нас есть такая запись:</p><p>Предположим, мы хотим найти всех пользователей, которые живут в городе Moscow. Поле city находится внутри вложенного объекта address, поэтому используем точечную нотацию:</p><p>Этот запрос вернёт всех пользователей, у которых в поле address.city значение равно "Moscow".</p><p>Если нужно найти документы по значениям в массиве, например, у пользователей, которые увлекаются hiking, мы можем отфильтровать по массиву hobbies:</p><p>Можно использовать операторы $gte, $lte и др. для сравнения для фильтрации. Например, чтобы найти всех пользователей старше 25 лет:</p><p>Здесь $gte означает «больше или равно».</p><p>Можно комбинировать несколько условий. Например, если нужно найти всех пользователей, которые живут в городе "Moscow" и старше 25 лет:</p><h3>Кратко о преимуществах и недостатках MongoDB</h3><p>Подведем итог описания базы данных кратким перечнем плюсов и минусов использования MongoDB.</p><p><b>Основные преимущества MongoDB</b></p><ul><li>Гибкость и адаптивность под изменяющиеся требования за счет хранения данных в формате BSON;</li><li>Простота масштабирования;</li></ul><p><b>Основные недостатки MongoDB</b></p><ul><li>Ограниченная поддержка сложных транзакций, что особенно ощущается при работе с сильно взаимосвязанными данными;</li><li>Возможные проблемы с производительностью при неправильной конфигурации.</li></ul><h2>А какие вообще бывают базы данных?</h2><p>Базы данных можно разделить на два основных типа: реляционные и нереляционные.</p><p>Реляционные БД представляют данные в формате таблиц и используют Structured Query Language (SQL) для управления данными. Примеры таких баз данных: MySQL и PostgreSQL.</p><p>Нереляционные базы данных еще называют NoSQL. В отличие от реляционных БД они не привязываются к четко описанной и редкоизменяемой структуре данных. Они предназначены для приложений, где представление данных может меняться.</p><p>Существует несколько подтипов NoSQL-баз данных:</p><ul><li><b>Документоориентированные</b> БД хранят информацию в формате, который может не соответствовать строгой структуре, например, JSON или BSON. Примеры: MongoDB, CouchDB.</li><li>В <b>колоночных</b> базах данных информация хранится не по строкам, как в обычных реляционных бд, а по столбцам. Данные каждого столбца сохраняются отдельно, что позволяет быстрее обрабатывать большие объемы данных, особенно когда нужны выборки только по нескольким столбцам. Примеры: Apache Cassandra, HBase.<br /></li><li>Представление информации в виде пар <b>ключ-значение</b> — простой и быстрый способ хранения данных. Примеры таких БД: Redis, DynamoDB.<br /></li></ul><p>Также стоит отметить некоторые специфические типы, например:</p><ul><li><b>Базы данных на основе графов</b>, которые используются в приложениях, где важны сложные взаимосвязи между объектами. Примеры: Neo4j, Amazon Neptune, JanusGraph;</li><li><b>Базы данных на основе временных рядов</b> оптимизированы для хранения данных, организованных по временным меткам (например, показания датчиков). Примеры: InfluxDB, Prometheus, TimescaleDB;<br /></li><li>В <b>объектно-ориентированных</b> базах данных данные представляются в виде объектов, как в ООП. Примеры: db4o, ObjectDB.<br /></li></ul><h2>Сравнительный анализ основных баз данных</h2><p>Для того что ответить на вопрос, чем именно MongoDB отличается от других БД, сравним ее с самыми популярными представителями остальных типов.</p><p>Рассмотрим основные параметры следующих баз данных: MongoDB, MySQL, PostgreSQL, CouchDB, Apache Cassandra, Redis, Neo4j, InfluxDB, db4o.</p><p>Для удобства будем рассматривать параметры последовательно и в виде таблицы.</p><figure><img src="https://media.tproger.ru/user-uploads/100452/2024-10-18/94bc1665-90fb-4839-9b54-a05a1da23aa3.png" alt="" /><figcaption>Первая четверка параметров: «Тип СУБД», «Структура данных», «Язык запросов» и «Модель консистетности».</figcaption></figure><ul><li><b>Типы СУБД</b> мы рассматривали ранее и дополнительного описания этот параметр не требует.</li><li>MongoDB, в отличие от остальных рассматриваемых БД, использует <b>структуру данных</b> BSON. Среди прочих NoSQL БД такой вариант выгодно отличается скоростью работы из-за бинарного формата данных.</li><li><b>Язык запросов</b>: в MongoDB запросы формируются через JSON-подобный синтаксис, более естественный для работы с объектами и не требующий сложных объединений данных.</li><li><b>Модель консистентности</b>: в реляционных базах строгая консистентность — данные сразу становятся согласованными. MongoDB поддерживает конечную консистентность, что позволяет ей быть более гибкой в распределённых системах, хоть и с временными расхождениями данных.<br /></li></ul><p>Следующие на очереди параметры «Скорость записи», «Скорость чтения», «Типы хранения данных» и «Лицензия».</p><figure><img src="https://media.tproger.ru/user-uploads/100452/2024-10-18/30b6e78e-9fdc-4814-ac00-dfc6a7ec5644.png" alt="" /><figcaption>Параметры: «Скорость записи», «Скорость чтения», «Типы хранения данных» и «Лицензия»</figcaption></figure><p>Отмечу, что оценки скорости средние и могут отличатся в зависимости от особенностей конкретной структуры БД и параметров сервера.</p><ul><li><b>Скорость записи.</b> MongoDB оптимизирована для высокой скорости записи благодаря поддержке горизонтального масштабирования и асинхронной записи. Это особенно эффективно при работе с большими объёмами данных. Реляционные базы данных (например, MySQL, PostgreSQL) могут быть медленнее из-за строгих транзакционных гарантий.</li><li><b>Скорость чтения.</b> В MongoDB чтение данных может быть быстрее при распределении данных по шардированным кластерам, хотя это зависит от настроек индексов и распределения данных. Реляционные базы часто показывают высокую скорость чтения, но могут терять производительность на сложных запросах с несколькими таблицами.</li><li><b>Типы хранения данных.</b> Тут выделяется только Redis, его вариант хранения данных позволяет существенно ускорить количество операций записи и чтения.</li><li><b>Лицензии.</b> В таблице обозначены их названия, более подробную информацию стоит прочитать на сайтах этих БД.<br /></li></ul><p>Также важными параметрами для выбора БД являются «Масштабируемость», «Отказоустойчивость», «Поддержка транзакций» и «Поддержка индексов».</p><figure><img src="https://media.tproger.ru/user-uploads/100452/2024-10-18/8cc13bf1-cb40-4812-ba44-991c0925662c.png" alt="" /><figcaption>Параметры: «Масштабируемость», «Отказоустойчивость», «Поддержка транзакций» и «Поддержка индексов».</figcaption></figure><ul><li><b>Масштабируемость. </b>MongoDB поддерживает горизонтальное масштабирование через шардирование. Реляционные базы данных чаще масштабируются вертикально. Однако они поддерживают и другие варианты масштабируемости (см. в таблице).</li><li><b>Отказоустойчивость.</b> MongoDB предлагает встроенные механизмы репликации и автоматического восстановления данных при сбоях, что обеспечивает высокую доступность системы. В реляционных базах также возможна отказоустойчивость через репликацию, но она чаще требует более сложной настройки.</li><li><b>Поддержка транзакций.</b> Ранее MongoDB не поддерживала полноценные транзакции, но начиная с версии 4.0, она обеспечивает поддержку ACID-транзакций. В реляционных базах, таких как PostgreSQL и MySQL, поддержка транзакций является стандартом и более развита.</li><li><b>Поддержка индексов.</b> Многие БД поддерживают индексацию, и MongoDB тоже.<br /></li></ul><p>MongoDB продолжает привлекать внимание разработчиков благодаря своим уникальным возможностям и подходам к работе с данными. В мире современных приложений, где требования к гибкости и масштабируемости данных постоянно растут, она выделяется на фоне традиционных реляционных баз данных.</p><p>Несмотря на плюсы и минусы MongoDB, выбирать подходящую базу данных нужно по типу задачи. Приложения, которые работают с большими объемами неструктурированных данных или где структура данных подвержена частым изменениям, выигрывают от использования MongoDB. Однако для задач, требующих сложных запросов с множеством связей между таблицами и строгими требованиями к консистентности, реляционные базы данных всё ещё могут оказаться предпочтительнее.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как использовать Redis для кэширования и очередей в веб-приложениях</title>
      <link>https://tproger.ru/articles/kak-ispolzovat-redis-dlya-kewirovaniya-i-ocheredej-v-veb-prilozheniyah</link>
      <comments>https://tproger.ru/articles/kak-ispolzovat-redis-dlya-kewirovaniya-i-ocheredej-v-veb-prilozheniyah?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анастасия Полищук]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-ispolzovat-redis-dlya-kewirovaniya-i-ocheredej-v-veb-prilozheniyah</guid>
      <description><![CDATA[<p>Как использовать Redis для кэширования и очередей в веб-приложениях. Показываем основные возможности Redis. Рассматриваем пошаговую инструкцию ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-ispolzovat-redis-dlya-kewirovaniya-i-ocheredej-v-veb-prilozheniyah">Как использовать Redis для кэширования и очередей в веб-приложениях</a>»</p>]]></description>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 09 Sep 2024 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Ускорение работы приложений — один из краеугольных камней веб-разработки. Практически все веб-приложения основаны на взаимодействии пользователя или клиента с данными, хранящимися на удаленном сервере.</p><p>Для того чтобы получение и отправка данных были не только успешными, но и занимали как можно меньше времени, создано множество паттернов и инструментов. К ним относится, например, key-value система Redis, позволяющая загрузить необходимую информацию в оперативную память сервера.</p><p>О том, как установить и настроить Redis на популярных ОС и какие подходы использовать для повышения отказоустойчивости, читайте далее. Если вас интересует более глубокое погружение в веб-разработку, рекомендуем <a href="https://tproger.ru/courses/onlajn-kurs--fulstek-razrabotchik--ot-yandeks-praktikuma?erid=LjN8KVGvm">курс «Фулстек-разработчик»</a>.</p><h2>Что такое Redis?</h2><p>Redis — это система хранения данных в формате «ключ-значение». Основная его особенность в том, что данные хранятся in-memory, то есть в оперативной памяти, и время для их получения значительно сокращается.</p><h3>Основные возможности Redis</h3><p>Перечисление возможностей Redis начнем с поддерживаемых типов данных:</p><p>Строка (String): «Это текст»</p><ol><li>Список (List): [A B C B A D]</li><li>Множество (Set): {B, D, C, A}</li><li>Упорядоченное множество (Sorted set): {1:AA, 2:AB, 3:B, 4:BD, 5:C}</li><li>Битовое поле (Bitfield): {122435}</li><li>Битовая карта (битовый массив) (Bitmap): 001101010011010</li><li>Геопространственные данные (Geospatial): {A: 12.03, B: 78.56}</li></ol><p>Несмотря на то, что Redis нельзя назвать полноценной СУБД, он умеет многое из того, что доступно в нереляционных базах данных. Перечислим ключевые особенности его функциональности:</p><ul><li>Возможность хранения данных в оперативной памяти сервера.</li><li>Обработка и изменение хранящихся в памяти данных.</li><li>Библиотеки для работы с Redis есть во всех популярных языках программирования.</li><li>Разнообразные подходы к конфигурации системы позволяют достичь высокого уровня отказоустойчивости без потери скорости работы.</li><li>Специализированные клиенты с графическим интерфейсом позволяют просматривать информацию в удобном виде.</li><li>Плагины для работы с Redis имеются в популярных CRM-системах, в том числе в WordPress.</li></ul><p>Дополнительно стоит обратить внимание на существенные плюсы Redis:</p><ul><li>Высочайшая скорость работы и доступа к данным</li><li>Простота горизонтального масштабирования</li><li>Поддержка репликации</li><li>Высокая отказоустойчивость Redis Cluster</li><li>Легкость обслуживания</li></ul><h3>Сценарии использования Redis</h3><p>In-memory система позволяет существенно снизить нагрузку на основную базу данных и ускорить обработку запросов благодаря работе в оперативной памяти. Среди основных целей использования Redis были и остаются:</p><ul><li>Кеширование</li><li>Работа с пользовательскими сессиями</li><li>Хранение счетчиков и метрик</li><li>Отображение лент событий и сообщений</li><li>Обработка очередей</li></ul><p>Примеры и кейсы использования Redis рассмотрим чуть позже.</p><h2>Установка и настройка Redis</h2><p>Разберемся, как установить Redis на самые популярные серверные ОС. Так как Redis был написан под Linux, на серверы с Windows он устанавливается через wsl, а на Ubuntu, Debian и другие дистрибутивы  – с помощью пакетного менеджера.</p><p>Для установки Redis на Windows Server первым делом нужно запустить WSL, после этого можно будет выполнять линукс-приложения и команды. В командной строке пишем:</p><p>На сервере начнется установка дистрибутива Ubuntu. После ее окончания нужно будет задать имя пользователя и пароль. Если Linux уже был установлен ранее, он просто будет запущен. В последующем можно будет пользоваться командой wsl без параметров.</p><p>Дальше установка идет одинаково для обоих вариантов ОС. В Windows вводим linux-команды после запуска wsl, в unix-системах – без дополнительных действий.</p><figure><img src="https://media.tproger.ru/user-uploads/105094/2024-09-04/9fbb6152-ef87-4788-b8d3-f5c57bf36ecb.png" alt="Redis для кэширования и очередей" /></figure><p>Если нужный пакет не найден, добавляем репозиторий и повторяем попытку:</p><h3>Основные команды и конфигурация Redis</h3><p>Когда Redis установлен, запускаем redis-cli и пингуем сервер для проверки работоспособности:</p><figure><img src="https://media.tproger.ru/user-uploads/105094/2024-09-04/9e9c28eb-d240-4223-8b7d-e835dc0c9465.png" alt="Использование Redis" /></figure><p>В ответ на эту команду в консоли должно отобразиться: PONG.</p><p>По умолчанию система использует порт 6379, при желании его можно изменить в конфигурации Redis. Там же можно дать доступ к серверу из внешней сети (после установки он доступен только по локальной) и установить пароль. Для этого открываем файл:</p><p>В файле делаем следующее:</p><ul><li>Меняем bind 127.0.0.1 -::1 на bind 0.0.0.0 — это обеспечит доступ всем сетевым интерфейсам, однако снизит безопасность!</li><li>Раскомментируем строку requirepass, тем самым включая необходимость ввода пароля.</li><li>В этой же строке прописываем сам пароль, желательно его сделать достаточно сложным, например: requirepass VeRy_vErY-GooDpa$$w0rD.</li><li>Имя пользователя по-умолчанию — default.</li></ul><figure><img src="https://media.tproger.ru/user-uploads/105094/2024-09-04/1318db8f-fed3-4eac-8356-114a3576bf73.png" alt="" /></figure><p>Чтобы настройки применились, перезапускаем Redis:</p><figure><img src="https://media.tproger.ru/user-uploads/105094/2024-09-04/32299124-5594-48bd-904f-d1db74b8ed29.png" alt="" /></figure><p>Проверяем активацию пароля с помощью redis-cli.</p><h3>Клиенты для работы с Redis</h3><p>Работать с Redis можно напрямую из, например, Python, а можно использовать специализированные GUI-клиенты с визуальным отображение данных — ниже некоторые из низ.</p><h4>RedisInsight</h4><p>Самый популярный на сегодня бесплатный инструмент для работы с Redis от Redis-Labs. Поддерживает все доступные типы данных и может их визуализировать. Ведет мониторинг нагрузки на сервер и использования оперативной памяти в режиме реального времени.</p><h4>REST.app (RedisDesktopManager)</h4><p>Еще один GUI-клиент для работы с данными, хранящимися в Redis. Обновлений не было с 2022 года, но на GitHub можно найти работоспособную версию с установщиком для Windows.</p><h4>Redsmin</h4><p>Облачный клиент для удаленного управления запущенным Redis-сервером в онлайн-режиме через веб-интерфейс.</p><h2>Использование Redis для кэширования</h2><p>Скорость отклика системы на действия пользователя — один из важнейших параметров веб-приложения, особенно когда это касается коммерческих разработок. Пользователи не любят долго ждать. Если сайт не загрузится или не обновит информацию за несколько секунд, велика вероятность того, что клиент просто закроет вкладку и уйдет к конкуренту.</p><h3>Что такое кэширование и зачем оно нужно</h3><p>Что позволит разработчику существенно ускорить отклик приложения без усиления серверного «железа»? В первую очередь с такой задачей справится кэширование: создание эдакого слепка наиболее часто используемых данных, который хранится либо на пользовательском устройстве, либо на стороне сервера. Причем кэш может быть как «теплым», так и «холодным». В первом случае данные подтягиваются в процессе работы с приложением, во втором — пакет информации формируется на сервере в момент его запуска. Холодный кэш актуален для сложных проектов, где нужно хранить, например, много актуальных счетчиков и метрик.</p><p>Благодаря наличию кэшированных данных пользователю не приходится ждать, пока приложение отправит запрос на сервер и получит от него ответ: информация отобразится практически без задержки.</p><h3>Реализация кэширования с Redis</h3><p>Один из популярных способов хранения кэша на сервере — запуск key-value хранилища Redis. Эта система позволяет хранить достаточно большое количество данных, ограниченное по сути лишь ресурсами системы, и быстро получать к ним доступ по уникальному ключу.</p><p>При записи данных в кэш Redis каждому ключу присваивается время жизни: этот параметр повлияет на то, как будут применяться механизмы удаления при переполнении выделенной для Redis памяти.</p><p>Чуть позднее мы рассмотрим варианты работы с истекшими ключами. Также данные можно удалить из Redis принудительно.</p><p>Считывание и удаление данных можно проводить как единично, так и передать в функцию массив ключей.</p><h3>Паттерны кэширования</h3><p>Разберемся, какие варианты реализации кэширования доступны в Redis:</p><ul><li>Приложение в первую очередь ищет данные в кэше, а в случае отсутствия — подтягивает их в кэш из основного хранилища, а потом отдает клиенту.</li><li>Кэш обновляется в случае, если в нем обнаружена ошибка.</li><li>Кэш обновляется каждый раз при обновлении данных в основном хранилище.</li><li>Кэш обновляется по истечении определенного периода времени.</li></ul><p>При разработке можно как пользоваться одним из указанных паттернов кэширования, так и применять их комбинацию.</p><h3>Решение проблем с кэшированием</h3><p>Рассмотрим основные проблемы, которые могут возникнуть при кэшировании данных с Redis, и приведем варианты их решения.</p><h4>Возникновение ошибки в данных</h4><p>Операции в Redis выполняются атомарно, а значит, случаются ситуации, когда какая-то часть информации не добирается до кэша. Например, в него записывается имя пользователя, но не записывается его фамилия. Решается применением первого паттерна, когда кэш синхронизируется с основным хранилищем при нахождении пробела в данных.</p><h4>Оперативная память или ее объем, выделенный для хранения кэша, переполняется</h4><p>Redis поддерживает несколько политик освобождения места. Выбрать их можно при конфигурации хранилища:</p><ul><li>volatile-lru — удаляются ключи с истекшим сроком действия, которые при этом не были недавно использованы</li><li>volatile-ttl — удаляются ключи с истекшим или коротким оставшимся сроком службы</li><li>volatile-random — удаляется случайный ключ из тех, чей срок службы завершился</li><li>allkeys-lru — удаляет ключи, которые не использовались недавно, выбирая из среди всех ключей</li><li>allkeys-random — удаляет любые случайные ключи</li></ul><h4>Заполнение кэша занимает большое количество времени.</h4><p>Для сложных приложений и сайтов актуальны случаи, когда в кэш нужно загрузить большое количество сложно собираемых данных. Например, это могут быть счетчики и метрики для крупного интернет-магазина. Решается такое созданием так называемого «холодного кэша»: данные рассчитываются при старте сервера и грузятся в кэш еще до того, как клиент пытается получить к ним доступ.</p><h2>Использование Redis для очередей сообщений</h2><p>О существовании и механизме действия очередей знает, пожалуй каждый, кто бывал в магазине, поликлинике или на почте. Это такая вереница людей, присоединиться к которой можно только вслед за последним в ней стоящим. А первым к прилавку или заветной двери идет тот, кто первым занял место в этой самой очереди. Разберемся, как связаны очереди и веб-разработка.</p><h3>Что такое очереди сообщений и зачем они нужны</h3><p>Очереди сообщений или Message Queue — это метод взаимодействия между частями приложения в случае его микросервисной архитектуры. По сути, один из механизмов их реализации мало отличается от очереди в поликлинике: положить новые данные можно только в конец очереди, а получить — лишь из ее начала. Такие очереди называются FIFO (first in – first out), а их альтернатива, LIFO-очереди (last in – first out), знакомы многим под названием «стек». В стек данные добавляются «сверху», как блины в стопку, и для получения доступен именно последний добавленный элемент.</p><p>Очереди сообщений позволяют передавать данные от одной части приложения к другой, не храня в каждом из них лишней информации и делясь лишь тем, что требуется в текущий момент. При этом каждый сервис забирает свое сообщение из очереди только тогда, когда готов его обработать, а очередь выступает в роли буферной зоны даже в случае неработоспособности сервиса-получателя.</p><h3>Реализация очередей с Redis</h3><p>Несмотря на то, что чаще всего веб-разработчики используют Redis для кэширования, он позволяет также реализовать и очереди, хотя и имеет в этом отношении несколько существенных недостатков.</p><p>Во-первых, Redis «из коробки» не является полноценным брокером сообщений и атомарное выполнение операций не дает уверенности в 100% доставке пакета. Как и в случае с потерей данных при загрузке в кэш, из очереди может быть передано сообщение №1, но не передано или утеряно сообщение №2. Если клиент пропустил сообщение, не принял, подписался на доставку позже — информация потеряна. Сервис-подписчик взял сообщение, но завис и перезагрузился — потеряна снова.</p><p>Тем не менее, очереди в Redis реализуемы при помощи переменных типа List или SortedSet в зависимости от задач. Приведем пример работы с ними:</p><p>Для работы с очередями в Redis доступна также команда getQueueLength (определяет размер массива элементов).</p><h3>Обработка очередей в многопоточном окружении</h3><p>С обработкой данных в один поток все предельно ясно: FIFO и LIFO очереди вполне справляются со своими задачами. Но что делать в случае многопоточности, когда к одной из очередей одновременно могут обратиться несколько клиентов или, наоборот, ни одного? Фактически, скорость работы Redis позволяет работать с многопоточным окружением точно так же, как и с однопоточным. Но ускорить взаимодействие и снизить шансы отказа может кластеризация, о которой мы поговорим позднее.</p><h3>Паттерны использования очередей с Redis</h3><p>Вот три основных подхода к очередям в Redis:</p><ul><li><b>Формат публикация-подписка (pub/sub).</b> Доступ ко всем сообщениям в конкретной очереди имеют все «подписчики». Если никто не запрашивает данные (никто не подписан на очередь), они удаляются.</li><li><b>FIFO-очередь с помощью простого list.</b> Сообщение из очереди получает только первый его запросивший.</li><li><b>Собственный тип данных Stream (поток).</b> Похож на вариант pub/sub, но при отсутствии подписчиков сообщение не удаляется, а ждет запроса.</li></ul><h3>Обработка ошибок и повторное выполнение задач</h3><p>Как уже было упомянуто, атомарность операций в Редисе может стать причиной ошибок. Часть данных может быть не записана или не доставлена, при этом Redis не определит проблему до того, как клиент попытается считать необходимую информацию. Именно поэтому бремя отслеживания ошибок в данных ложится на программиста. Например, может использоваться отдельный массив, в него клиенты будут добавлять ключи, информация по которым не была найдена. А дополнительный сервис будет отслеживать его наполнение и инициировать повторное выполнение задач по записи необходимых данных в Redis.</p><h2>Оптимизация производительности с Redis</h2><p>Redis по праву считается очень быстрой системой, но как нет предела совершенству, так нет предела и оптимизации производительности. Даже миллисекунды, отыгранные у процессов, в итоге вырастают в ощутимое ускорение работы приложения. Далее рассмотрим, какие настройки и способы помогут увеличить скорость обработки данных в Redis.</p><h3>Настройка памяти и политики вытеснения</h3><p>Редис хранит данные в оперативной памяти, а она, как известно, не бесконечна, если не приравнивать к ней файлы подкачки. Последний лежит на жестком диске, и даже на твердотельном накопителе обрабатывается в разы медленнее. Redis позволяет отключить возможность автоматического создания файла подкачки и ограничить доступный объем оперативной памяти. Для этого в конфигурационном файле указывается maxmemory, например, 1gb. Если в момент добавления новых данных он уже достигнут, Редис произведет запись, а после этого начнет автоматически удалять некоторые ключи. Какие именно — определяется политикой вытеснения. о которой и поговорим ниже.</p><p>Мы уже касались параметра «время жизни», когда говорили о кэшировании. Теперь подробнее разберем, какие варианты удаления ненужных данных может реализовать Redis.</p><ul><li>Установка параметра maxmemory-policy noeviction в файле конфигурации. При достижении лимита памяти ключи не удаляются, сервер выдает ошибку при попытке записи новых данных и отвечает только на команды чтения.</li><li>Алгоритм LRU (last recently used). При переполнении памяти начинается удаление ключей, к которым не было обращений в последнее время.</li><li>Алгоритм LFU (Least Frequently Used). Редис первыми удаляет ключи, имеющие минимальный счетчик обращений в целом.</li></ul><h3>Кластеризация Redis и шардирование</h3><p>Повысить отказоустойчивость системы можно, убрав единую точку вероятного нарушения работоспособности. Для этого при запуске Redis создают кластеры: системы из нескольких экземпляров. Если одни из них выйдет из строя, клиент сможет получить нужное от другого.</p><p>Дополнительный бонус от кластеризации Redis в том, что работа системы не остановится в случае обработки тяжелого запроса, следующую задачу сможет выполнить другой экземпляр.</p><figure><img src="https://media.tproger.ru/user-uploads/105094/2024-09-04/8b633f86-18a4-4082-9e8c-c517edb07999.png" alt="кластеризация Redis" /></figure><p>Также при запуске нескольких экземпляров Redis можно использовать шардирование, то есть распределение данных по разным экземплярам, а не их дублирование. Есть разные способы шардирования, но по сути они все сводятся к двум вариантам:</p><ul><li>Логику распределения данных определяет приложение-клиент, отправляя запросы на чтение и запись к определенному экземпляру системы.</li><li>В случае с запуском Redis-кластера логика может быть задана на сервере. Так, информация о данных, хранящихся на каждом Slave, доступна каждому Master, а запросы клиента автоматически перенаправляются.</li></ul><h3>Мониторинг и управление Redis</h3><p>Как театр начинается с вешалки, так и многие веб-приложения сегодня начинаются с Redis. А значит, его работоспособность и скорость отклика — важнейшие параметры, которые необходимо отслеживать, причем желательно делать это постоянно и оперативно. Для управления и мониторинга запущенного экземпляра Redis можно использовать, например, GUI-клиенты из тех, которых мы касались выше. Также мониторинг доступен с помощью Prometheus, специального сервиса для сбора и хранения различных метрик.</p><p>Просматривать его данные можно через веб-интерфейс или графические клиенты, например, Grafana.</p><p>Можно узнать многое о текущем состоянии системы и через терминал — для этого в redis-cli нужно ввести команду info. При ее выполнении в командной строке отобразятся:</p><ul><li>redis_mode – выбранная структура системы (одиночный экземпляр, кластер и другие)</li><li>порт</li><li>расположение файла конфигурации</li><li>количество подключенных клиентов</li><li>объем используемой памяти</li><li>общее количество подключений и другие параметры</li></ul><figure><img src="https://media.tproger.ru/user-uploads/105094/2024-09-04/edfe7883-b472-4410-81b5-5e7c8f540c51.png" alt="" /></figure><p>Основные изменения конфигурации системы вносятся в файл config, после его редактирования сервер необходимо перезапустить для применения внесенных правок.</p><h2>Безопасность и резервное копирование данных в Redis</h2><p>Как и для любой другой системы, для Redis важна безопасность как с точки зрения недоступности для злоумышленника, так и в вопросе сохранения данных в случае отключения.</p><h3>Защита Redis</h3><p>Чтобы повысить безопасность Redis в первую очередь стоит придерживаться базовых правилам:</p><ul><li>Пользоваться межсетевым экраном</li><li>Установить действительно сложный пароль</li><li>Ключевые команды (отключение или очищение кэша) можно переименовать</li><li>Не запускать Redis с root-доступом</li><li>Настроить политики доступа для разных пользователей</li></ul><p>Конечно, не стоит забывать и об элементарных и так любимых системными администраторами вещах, как хранение пароля на наклейке под монитором, доступ посторонних к серверу и прочем.</p><h3>Резервное копирование и восстановление данных</h3><p>Но даже если к вашему серверу и не могут получить доступ злоумышленники, он все равно не застрахован от рисков. Даже банальное отключение электричества и не справившиеся ИБП могут стать причиной потери всех данных. А значит, лозунг «делай бэкапы!» актуален и в случае с Redis. Для резервного копирования в нем можно использовать моментальные копии базы данных, получаемые по команде save или bgsave. Разница этих команд в том, что save приостанавливает работу Redis, а bgsave выполняется в фоновом режиме. При их запуске создается файл rdb, из которого при желании можно восстановить базу данных на текущем или любом другом Redis-сервере.</p><p>Еще один вариант повышения сохранности данных — их репликация в редис-кластере на экземпляр, находящийся на другой физической или виртуальной машине.</p><h2>Практические примеры и кейсы</h2><p>Перейдем к некоторым кейсам практического применения Redis.</p><h3>Реализация сессий с помощью Redis</h3><p>Для хранения пользовательских сессий требуется запущенный экземпляр Redis и доступ к нему для веб-сервера. Если на сервере используется php, то нужно будет установить пакет php5-redis и поменять в файле php.ini следующие строки:</p><p>После этого все сессии будут автоматически сохраняться в Redis без каких-либо дополнительных действий со стороны разработчика.</p><h3>Кэширование API-запросов</h3><p>Кэширование — один из наиболее часто используемых вариантов применения Redis. В данном случае рассмотрим ситуацию, когда в кэше будут храниться данные, полученные по запросу от API вашего приложения. Для реализации этого нужно будет пройти следующие шаги:</p><ul><li>Установить и запустить Redis.</li><li>Определить, какие именно данные нужно хранить в кэше.</li><li>Написать на используемом вами языке программирования их получение или рассчет по API-запросу.</li><li>Добавить полученные данные в кэш Redis.</li><li>При запросе от API сначала проверять, имеется ли нужная информация в кэше, и запрашивать их у основной системы только при отсутствии.</li></ul><p>Кэш для API может применяться, например, для хранения информации о подключениях пользователей к приложению. При каждом взаимодействии пользователя с нашим API мы должны обновлять значение last_visit в таблице user и отображать эти данные. Но если пользователей много и они очень активно взаимодействуют с API, то они просто заспамят основную базу данных постоянными запросами update. Удобный и простой вариант — хранить эти данные в redis, например, в sorted_set, и написать механизм периодической отправки большого обновления в основную БД.</p><h3>Управление очередями задач в крупномасштабных приложениях</h3><p>Один из примеров того, как можно использовать Redis и его механизм очередей в больших и сложных приложениях — это задача кредитного скоринга. Когда подсчитаны данные по конкретному клиенту, система должна уведомить об этом менеджера, отправив ему письмо на email. Для того, чтобы не занимать ресурсы сервера, эту задачу можно делегировать отдельному сервису, а сами подсчитанные данные хранить в Redis для быстрого доступа и отправки. Микросервис, отвечающий за рассылку сообщений, сможет получать из очереди электронные адреса менеджеров и предназначенные им пакеты информации.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как быстро и эффективно работать с большими JSON-файлами</title>
      <link>https://tproger.ru/articles/kak-bystro-i-effektivno-rabotat-s-bolwimi-json-fajlami</link>
      <comments>https://tproger.ru/articles/kak-bystro-i-effektivno-rabotat-s-bolwimi-json-fajlami?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Лена Капаца]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-bystro-i-effektivno-rabotat-s-bolwimi-json-fajlami</guid>
      <description><![CDATA[<p>Как работать с большими JSON файлами. Показываем основные способы работы с Big JSON и возможные проблемы. Рассматриваем пошаговую инструкцию ✔ Tproger
</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-bystro-i-effektivno-rabotat-s-bolwimi-json-fajlami">Как быстро и эффективно работать с большими JSON-файлами</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[JSON]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Big Data]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 12 Aug 2024 15:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Разработчики используют API каждый день, и подавляющее их число отдает данные в виде JSON-массивов, будь то логи бота или резюме кандидатов с площадок по поиску работы. С небольшими файлами.json учат обращаться на многих курсах программирования, но что делать, если объем такого вывода становится некомфортно большим? Или вы регулярно «упираетесь» в ошибки, вызванные разнородной структурой элементов? В этой статье мы познакомим вас с тремя решениями, которые помогут эффективно работать с большими JSON файлами.</p><p>Если вы только-только начали изучать способы хранения, знания JSON можно освежить <a href="https://tproger.ru/articles/chto-takoe-json-vvedenie">здесь</a>.</p><h2>Способ первый: параллельная обработка</h2><p>Классическое решение, задействующее навыки параллелизации. К примеру, если признак name каждого элемента требует обновления:</p><p>Мы можем задать две функции. Первая из них добавляет значению name префикс Test:</p><p>А вторая пушит обновление:</p><p>В итоге мы распараллеливаем запуск этих функций с помощью asyncio:</p><h2>Способ второй: пакетная обработка в binary</h2><p>В комьюнити Hadoop и Spark (для хранения больших данных) особое признание обрел формат Parquet. Когда речь идет об огромных объемах информации, удобство ее обработки превалирует над читаемостью. Здесь вообще рекомендую избегать подключения pandas и перевода в человекочитаемый формат в промежутке.</p><p>Такой код:</p><ol><li>«Заморозит» вашу программу, если файл слишком большой;</li><li>Не учитывает массивы с меняющейся структурой.</li></ol><p>А до этого RAM вообще может закончится на шаге конвертации JSON в датафрейм.</p><p>В такой ситуации поможет библиотека ijson:</p><p>К примеру, конверсия кортежа в binary:</p><p>Превратит числа вот в такую компактную и быстродейственную абракадабру:</p><h2>Способ третий: перейти в другой формат</h2><p>На курсах повышения квалификации нашу группу познакомили с Redis — альтернативой классическим базам вроде PostgreSQL. Так здорово осознавать, что до тебя немало людей уже отстрадались на ниве JSON и даже создали целое решение, «бьющее» самые распространенные проблемы — разнородность элементов массива, вложенные узлы.</p><p>Представьте, сколько энергии потребуется даже с ChatGPT, чтобы написать скрипт на Python, который «схлопнет» до табличного состояния данные кандидатов ниже?</p><p>Пару лет назад я занималась подобным перед загрузкой логов бота в BigQuery (SQL-подобная база), а потом была вынуждена обрабатывать ситуацию «забытых» полей (они проявлялись реже, чем раз в неделю, на которой опробовали скрипт выгрузки). Это приводило к необходимости обновлять схему таблицы, заниматься перезаливом и в целом фрустрироваться ситуацией.</p><p>Теперь понимаю: лучший способ сократить мороку при обращении с массивами — отойти от формата строго заданной структуры как можно раньше. Redis буквально создан для этого. В подгружаемом массиве через месяц появился экземпляр кандидата с новым полем portfolio? «Редиска» положит к себе и такое, причем без множественных ошибок. Захотите в дальнейшем использовать данные таблично? Вычитайте сет с помощью самописной функции:</p><figure><img src="https://media.tproger.ru/user-uploads/79101/2024-08-11/41302dc2-592a-4eae-bfd9-03a029629440.png" alt="Методы работы с Big JSON" /></figure><p>И что немаловажно, данные хранятся в оперативной памяти сервера, что ускоряет обращение с ними, даже в случае с большими порциями. Если ваша компания, конечно, не испытывает проблем с масштабируемостью.</p><p>Приятный бонус: логика сета (это аналог таблицы в базе) подразумевает уникальные значения. То есть очистка от повторений будет произведена автоматически. И тут ощущаются спасенные человекочасы.</p><figure><img src="https://media.tproger.ru/user-uploads/79101/2024-08-11/958b52d3-0efd-441c-a7a7-f6d27159d2a7.jpg" alt="Как работать с большими JSON файлами" /></figure><p>Многие провайдеры облачных серверов предлагают преднастроенный Redis, который за 5-10 минут встанет из-под Docker-контейнера, и цены на такие услуги стремятся к тем же минимумам, что и голый Ubuntu на миникалках (300 рублей в месяц против 130).</p><p>Среди недостатков «редиски» отмечу, что переход от таблиц к сетам может вызвать у разработчика с информационной перегрузкой дополнительный стресс: документация весьма непростая и перестроиться на нетабличное восприятие поначалу потребует много энергии. Но тут очень здорово помогает ChatGPT.</p><h2>Заключение</h2><p>Если вы дорасли до проектов с массивными объемами данных, это уже прекрасно. Порой стоит позволить себе наошибаться при обращении с ними, пока не подберете наилучшее для ситуации решение. ijson немного сложнее поддерживать, Redis плохо подходит новичкам, asyncio тоже не идеален. В каждом проекте свои тонкости — они и определят, какое из решений оптимальное.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как снизить нагрузку на CRM-систему</title>
      <link>https://tproger.ru/articles/kak-snizit-nagruzku-na-crm-sistemu</link>
      <comments>https://tproger.ru/articles/kak-snizit-nagruzku-na-crm-sistemu?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сергей Лалетин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-snizit-nagruzku-na-crm-sistemu</guid>
      <description><![CDATA[<p>Рассказали, как справляемся с нагрузкой внутренней CRM-системы: какие технологии и практики используем и почему.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-snizit-nagruzku-na-crm-sistemu">Как снизить нагрузку на CRM-систему</a>»</p>]]></description>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[NoSQL]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 14 Jun 2024 10:19:35 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>Я занимаюсь разработкой CRM-системы, которая собирает, связывает и анализирует информацию о клиентах. Благодаря ей мы можем создавать рекламные предложения и предоставлять важную для банка информацию таргетированно — и быть уверены, что клиент видит в ленте именно то, что нужно (или что-то, что настолько понравится, что станет нужным). В этом тексте расскажу, как мы снижаем нагрузку на CRM-систему, и разберу тонкости разработки.</blockquote><h2>Как выглядит наша CRM-система</h2><p>Мы используем микросервисную архитектуру, микросервисы в основном написаны на Java. Маршрутизатором трафика работает Traefik: он собирает в себя все запросы и определяет, в какой микросервис, в какой API, отправлять реквест. Схема работы выглядит так:</p><p>Клиент —&gt; Traefik —&gt; (mcp-api-cleaner, mcp-reseration, mcp-proressing)</p><p>Микросервисы взаимодействуют с NoSQL и Redis, из которой информация попадает в топики Kafka и дальше в mySQL. Данные хранятся до 90 дней и после удаляются.</p><p>Благодаря такой логике клиенты получают ответ более быстрый, чем при работе с SQL базами данных .</p><h2>Redis взяли по нескольким причинам</h2><p>Первая: в банковском секторе нужна скорость. Вторая — у Redis быстрое и удобное взаимодействие с Java. Третья — это очень популярная система управления базами данных и применяется во многих компаниях.</p><p>Но у работы с ней есть нюансы. Погнавшись за скоростью, можно не рассчитать нагрузку, что фатально повлияет на работу приложения.</p><h2>Мы тоже столкнулись со сложностями</h2><p>При экспоненциальном росте нагрузки на CRM-систему Redis переполнялся и падал с неопределенной периодичностью. Изучив проблему, мы поняли, что в базе копятся записи с одинаковым ключом.</p><p>Например, мы провели тренинг, на котором сотрудники выполняли одни и те же действия: зашли в профиль, выполнили задачи А и Б и так далее. Система проанализировала контакты и объединила пользователей по интересам. В результате мы получили дерево из тысяч записей с одинаковым ключом. Мало того что это увеличило нагрузку на аналитическую систему и замедлило работу приложения, так еще и подвергло риску отказоустойчивость внутри контура банка.</p><p>Первое, что пришло в голову — создать отдельный стенд для обучения, но глобально проблему это бы не решило, потому что заранее узнать, на каком аккаунте возникнет рост, мы не сможем. Да и создание такого демостенда — накладно и не очень честно по отношению к обучаемым, потому что учеба должна проходить на реальным платформе с реальными примерами.</p><p>Также думали и ограничить коммуникацию с одним пользователем в рамках какого-то времени, но это доставило бы неудобства реальным пользователям: мы могли заафектить реальных клиентов, к тому же при каждой записи получать COUNT по ID было бы очень дорого.</p><p>Последняя идея, пришедшая к нам, была такой: искать высоконагруженные ID в mySQL и уже потом удалять их из Redis. Но это бы тоже не сработало, потому что постоянно сканировать счетчик ID долго и дорого.</p><h2>Не найдя комфортного решения на текущей архитектуре, мы начали и тестировать другие инструменты</h2><p>Взяли MongoDB, это тоже NoSQL база данных, которая хранит информацию не в кэше, а в формате BSON, в виде документов, содержащих пары ключ-значение. Мы сравнили скорость работы с Redis и выяснили, что MongoDB работает медленнее. Поэтому остановились на компиляции Redis и MongoDb с помощью паттерна Cache aside. На тестирование ушло несколько месяцев.</p><p>Схема простая: данные попадают в Redis и сразу же записываются в MongoDB. Так мы получаем дублированный кэш с разным временем жизни. На стороне Redis данные хранятся ограниченное время, пару дней. При частых запросах мы не теряем в перформансе и не перегружаем Redis, потому что при нагрузке мы уже смело можем очищать данные по определенному ID.</p><p>Клиент в это время получает информацию из MongoDB (да, информация придет чуть позже, это медленнее процентов на 15, но решает проблему с нагрузкой).</p><p>На перформансе, при частых запросах, потерь в скорости почти нет: даже если данные успели стереться из Redis, они не превышают 10%. В дальнейшем данные уже попадают в Redis, и скорость возвращается.</p><h2>Так мы решили проблемы с нагрузкой</h2><p>Было приятно поделиться этой информацией, если хотите погрузиться в тему детальнее, пишите вопросы в комментариях.</p>]]></content:encoded>
    </item>
    <item>
      <title>5 архитектурных ошибок, которые мы совершаем на старте проектов</title>
      <link>https://tproger.ru/articles/5-arhitekturnyh-owibok--kotorye-my-soverwaem-pri-starte-proektov</link>
      <comments>https://tproger.ru/articles/5-arhitekturnyh-owibok--kotorye-my-soverwaem-pri-starte-proektov?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Татьяна Жукова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/5-arhitekturnyh-owibok--kotorye-my-soverwaem-pri-starte-proektov</guid>
      <description><![CDATA[<p>Какие архитектурные ошибки чаще всего совершают разработчики при запуске проектов и как их избежать? Разбираем пять критичных промахов, которые мешают продукту масштабироваться и усложняют поддержку.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/5-arhitekturnyh-owibok--kotorye-my-soverwaem-pri-starte-proektov">5 архитектурных ошибок, которые мы совершаем на старте проектов</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[HR]]></category>
      <category><![CDATA[Django]]></category>
      <category><![CDATA[Сервисы]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Redux]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Маркетинг]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Kubernetes]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Vue]]></category>
      <category><![CDATA[микро]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[CI/CD]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 14 Jul 2023 12:10:24 GMT</pubDate>
      <content:encoded><![CDATA[<p>Можно запустить MVP за пару недель, но потом годами разгребать архитектурные долги. Ошибки на старте проекта легко не заметить: код вроде бы работает, фичи выкатываются, пользователи приходят. Но когда продукт растёт, каждая непродуманная деталь в архитектуре оборачивается багами. Разбираем пять ключевых ошибок, которые разработчики совершают чаще всего, и объясняем, как их избежать.</p><h2>Ошибка 1. Нет четких границ между слоями приложения</h2><p>На старте проекта код часто пишется «в лоб»: контроллеры ходят в базу напрямую, внутри функций появляются бизнес-правила и расчёты, а валидация размазывается по коду. Это кажется удобным, пока приложение небольшое. Но при росте команды и количества фич изменения начинают ломать соседние части кода, баги растут, а скорость разработки падает.</p><p>Можно выделить три базовых уровня:</p><ul><li>Контроллеры (или хэндлеры) принимают запросы, проводят базовую валидацию (например, через pydantic-схемы) и передают данные дальше.</li><li>Слой бизнес-логики реализует правила работы приложения: транзакции, расчёты скидок, проверку лимитов, работу с несколькими сервисами.</li><li>Слой доступа к данным отвечает за запросы к базе и возвращает данные в удобной для бизнес-логики форме.</li></ul><p>Например, в FastAPI проще начать с хэндлеров, которые сразу ходят в SQLAlchemy-модель. Но если сразу заложить сервисный слой, расчёты комиссий и проверку бизнес-правил можно будет тестировать изолированно, не поднимая всю API. Слой репозиториев позволяет в будущем легко заменить Postgres на другую базу или добавить кэш, не переписывая логику приложения.</p><p>Чёткое разделение позволяет быстро находить, где живёт бизнес-логика, где происходит доступ к данным и куда добавлять новые фичи. Продукт растёт, а код остается предсказуемым, удобным для изменений и масштабирования.</p><h2>Ошибка 2. Игнор масштабирования с первого дня</h2><p>Когда проект только запускается, кажется, что про масштабирование можно подумать потом. Приложение обслуживает сотню пользователей, запросы проходят быстро, всё работает. Но как только запускается маркетинг или приходит первый крупный клиент, внезапно всё начинает тормозить, а в коде нет ни одной зацепки, куда безопасно вставить кэш или вынести тяжёлые операции в фон.</p><p>Важно сразу закладывать в архитектуру точки роста, даже если пока они не нужны на каждый день.</p><p>Например:</p><ul><li>Добавить очереди и фоновые задачи через Celery или RQ, если есть риск появления тяжёлых операций (генерация отчётов, массовая рассылка).</li><li>Разносить чтение и запись: даже простое разделение на эндпоинты, где читающие операции не блокируются длительными транзакциями, уже помогает при росте нагрузки.</li><li>Придумать, как кэшировать самые тяжелые запросы: например, использовать Redis, чтобы хранить агрегированные данные, а не считать их каждый раз.</li><li>Закладывать возможность горизонтального масштабирования: не привязывать логику к локальному состоянию приложения, использовать хранилище с возможностью разделения нагрузки, не писать монолит, который нельзя будет разбить на части при росте.</li></ul><p>На практике проект растёт быстрее, чем кажется. Сначала в API добавляется массовая выгрузка CSV, потом приходят интеграции с другими сервисами, а затем накатывается нагрузка от новых пользователей. Если архитектура не подготовлена, команде приходится ставить костыли, переписывать эндпоинты под фоновые задачи или внедрять кэш в экстренном порядке, исправляя баги в проде.</p><p>Закладывая масштабирование в архитектуру с первого дня,  команда экономит себе месяцы переработок и спасается от бесконечных хотфиксов. Это инвестиция, которая позволяет команде развивать продукт спокойно.</p><h2>Ошибка 3. Преждевременное усложнение архитектуры</h2><p>Многие разработчики боятся, что проект не выдержит нагрузку, поэтому с самого старта закладывают сложные паттерны, микросервисы, брокеры событий и сразу три уровня кэширования. Но пока в системе нет ни пользователей, ни подтверждённой бизнес-модели, такие решения снижают скорость разработки и приводят к куче багов.</p><p>Рядовой пример — проект сразу запускается на Kubernetes с несколькими сервисами, которые обмениваются сообщениями через Kafka. В реальности такие проекты вначале требуют десятков часов на поддержание инфраструктуры, а баги приходится искать сразу в нескольких сервисах, между которыми гуляют события. При этом единственная реальная задача в начале — быстро проверить гипотезы и получить первых пользователей.</p><p>Упрощённая архитектура на старте позволяет команде сосредоточиться на продукте: монолит с чёткими слоями (контроллеры, сервисы, репозитории) куда быстрее дорабатывается и легче деплоится, чем микросервисы с отдельными контурами.</p><p>Что можно делать:</p><ul><li>Запускать монолит на FastAPI или Django, а не дробить на микросервисы до появления реальных узких мест.</li><li>Использовать Postgres без брокеров событий, пока не появятся требования к масштабированию.</li><li>Добавлять кэш Redis точечно, когда видна реальная нагрузка, а не вслепую кэшировать каждый запрос.</li></ul><p>Сложные архитектуры требуют времени на поддержку и экспертизу, чтобы не допустить критичных ошибок (например, потерю событий в брокере или гонки данных между сервисами). Поэтому вложения в сложную архитектуру оправданы, когда проект достигает уровня, где без этого уже не обойтись.</p><p>Если команда делает стартап или MVP, преждевременное усложнение архитектуры только тормозит развитие продукта. Гораздо эффективнее заложить возможности для масштабирования (очереди, фоновые задачи, кэш), но держать архитектуру простой до тех пор, пока проект не начнёт расти и не появятся реальные вещи, требующие изменений.</p><h2>Ошибка 4. Непродуманная работа с зависимостями</h2><p>В начале проекта обычно кажется, что зависимости — это просто. Но со временем проект разрастается, зависимости множатся, версии начинают конфликтовать, а обновление одной библиотеки ломает другую.</p><p>Эта ошибка обычно проявляется в нескольких местах. Например, в проект могут без разбора ставиться зависимости про запас или ради одной строчки удобной функции, хотя можно обойтись стандартной библиотекой. Ещё одна частая проблема — зависимости фиксируются слишком жестко, что не дает обновляться безопасно, или наоборот, версии не фиксируются вовсе, и проект начинает падать после автоматического обновления библиотек.</p><p>Чтобы избежать проблем, важно с самого начала заложить порядок в работе с зависимостями:</p><ul><li>Использовать инструмент управления зависимостями, который позволяет контролировать версии и изолировать окружения.</li><li>Разделять зависимости для разработки и продакшена.</li><li>Периодически обновлять зависимости, чтобы не закапываться в старые версии, но делать это контролируемо и с прогоном тестов.</li></ul><p>Например, при работе с Python удобным подходом будет использование Poetry: можно зафиксировать версии зависимостей в pyproject.toml, автоматически создавать lock-файл, следить за актуальностью библиотек и легко пересоздавать окружение. Если проект запускается на CI/CD, можно установить точные версии и сразу выявить, где обновление ломает тесты.</p><p>В длинных проектах непродуманная работа с зависимостями множится в геометрической прогрессии. Любой новый разработчик тратит время на настройку окружения, зависимости конфликтуют, часть библиотек остаётся неиспользованной. Порядок с зависимостями экономит часы и дни, снижает риск падений в проде и влияет на развитие проекта.</p><h2>Ошибка 5. Нет стратегии управления состоянием</h2><p>Состояние — это данные, которые хранятся между запросами или действиями пользователя: корзина, прогресс пользователя, кэшированные результаты запроса, состояние WebSocket-подключений. Если не продумать, как эти данные хранятся, обновляются и синхронизируются, приложение быстро начинает вести себя непредсказуемо.</p><p>На старте часто используют облегченный подход: данные передаются по цепочке вызовов, хранятся в сессии или глобальных переменных, кэшируются в памяти процесса. Это удобно, пока пользователей мало и сервер один. Но при росте нагрузки и масштабировании начинаются проблемы: сессии теряются между инстансами, кэш расходится, данные теряются при перезапуске приложения.</p><p>Чтобы избежать хаоса, управление состоянием нужно продумать заранее:</p><ul><li>Выяснить, какие данные должны храниться между запросами и как долго.</li><li>Решить, где хранить состояние: в базе данных, Redis, сторонних сервисах.</li><li>Сразу заложить сериализацию и валидацию состояния, чтобы избежать рассинхронизации форматов.</li></ul><p>Например, хранение кэша в памяти может подойти для небольших проектов, но если приложение начинает горизонтально масштабироваться, лучше вынести кэш в Redis, чтобы все инстансы работали с единым источником. Для сессий пользователей можно использовать JWT, если нужно масштабирование без общего состояния, или централизованное хранилище сессий, если требуется возможность их отзыва.</p><p>При работе с фронтендом стратегия управления состоянием не менее важна. Например, при использовании React или Vue проект может сначала обходиться локальным состоянием компонентов, но при росте сложности приложение начинает сыпаться из-за рассинхрона между компонентами. Важно заранее заложить подход с централизованным состоянием, а также понять, какие данные держать в состоянии клиента, а какие запрашивать заново.</p><p>Без стратегии управления состоянием проект становится сложным в отладке и поддержке. Грамотное управление состоянием ускоряет разработку и снижает количество багов в будущем.</p><p>А какие ошибки допускаете вы? Делитесь в комментариях или в тг-канале <a href="https://t.me/+a1v-IRDDUqI0MDhi">Веб-страница</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Почему не hadoop: создаём свое решение на node + mongo + lxd</title>
      <link>https://tproger.ru/articles/highload-mongo-node-lxd</link>
      <comments>https://tproger.ru/articles/highload-mongo-node-lxd?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/highload-mongo-node-lxd</guid>
      <description><![CDATA[<p>Клиент рассчитывал применить Hadoop для отчётов объёмом более 100 тысяч строк, каждый из которых создавался от 12 до 24 часов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/highload-mongo-node-lxd">Почему не hadoop: создаём свое решение на node + mongo + lxd</a>»</p>]]></description>
      <category><![CDATA[Архитектура ПО]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Высокие нагрузки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 19 Aug 2020 07:50:29 GMT</pubDate>
      <content:encoded><![CDATA[<p>Иногда к нам приходят клиенты с уже определенным стеком технологий для своего проекта. Так было и в этот раз. Клиент хотел проект по обработке больших объемов данных, твердо уверенный, что Hadoop это серебряная пуля.</p><p>Disclaimer В данной статье речь пойдет о проекте которому на текущий момент около трех лет. Некоторые решения, в том числе и выбор технологий обуславливался временем разработки.</p><p>Клиенту нужно было регулярно проводить электронную инвентаризацию оборудования, каждый отчет по такой инвентаризации содержал более 100k строк. Клиент видел лишь одну проблему — отчеты генерировались слишком долго (в районе 12 часов – 24 часа на каждый отчет). По мнению клиента, эта проблема вытекала из следующих факторов:</p><ul><li>Используется неподходящая СУБД. MongoDB не может в много данных и их обсчет. В результате этого, сложный анализ этих данных занимал слишком много времени и памяти.</li><li>Выходной формат отчетов — excel (xlsx). Для этого использовались скрипты на Node.js, каждый из которых выдавал отчет в своем виде. При построении таких отчетов, оперативная память заполнялась (~8Гб) и любое обращение к базе данных —  MongoDB просто падало с ошибкой.</li></ul><p>Задача: Поднять Hadoop кластер, настроить миграцию данных из БД Клиента и обработку этих данных средствами Hadoop.</p><p>На тот момент времени, у Hadoop была очень слабая поддержка, неразвитая инфраструктура, длительное время не было обновлений и неполная документация. Это всегда накладывало свой отпечаток на время и стоимость проекта.</p><p>Примечание На текущий момент, Hadoop имеет более дружелюбную инфраструктуру, благодаря docker и k8s.</p><p>При анализе необходимых действий для того, чтобы перенести все необходимые данные в Hadoop, мы выявили что мало того, что данные не имеют четкой структуры, так и в некоторых местах существенно различаются по типу и свойствам (спасибо Mongo за гипергибкость, которая попадает в руки не заботящихся о проектировании данных людей). Ну и в чем проблема, спросите вы. А проблема заключается в том, что Hadoop это SQL-based система. Т.е. все интерфейсы взаимодействия с данными идут через структурированный SQL. И у нас нет возможности мигрировать в эту систему данные без схемы, типов и прочего.</p><p>Для того чтобы нормально проработать схемы данных, потребовалось бы очень много времени. А клиенту нужно было как можно скорее. Поэтому на очередной сессии мозгового штурма, мы выделили какие есть проблемы на наш взгляд и придумали как можно их решить.</p><p>После глубокого исследования, мы составили свой список причин, почему у них проблема с производительностью:</p><ul><li>Нет четкой схемы данных в БД. Это приводит к тому, что агрегации должны выполнять больше количество действий для расчета результата.</li><li>В текущей БД свалка данных. При разработке системы, которая генерировала эти данные, формат меняли несколько раз, при этом сама БД никогда не очищалась. В результате чего, операции обсчета документов выполнялись даже над теми документами, которые не соответствуют нужной схеме.</li><li>Часть логики по обработке данных было перенесено на сторону Node.js.</li><li>Отчеты генерировались в памяти, и после полной генерации сохраняли это все в excel файл (естественно, когда данных много, оперативной памяти не хватает и вся система рушится).</li></ul><p>А также придумали как эти проблемы решить.</p><ul><li>За архитектурную модель мы взяли принцип по которому работают все распределенные системы, в том числе Hadoop. (Есть один мастер и несколько агентов.)</li><li>Мастер управляет нагрузкой и взаимодействием с внешним миром.</li><li>Каждый из агентов выполняет свою задачу по обработке данных.</li><li>Все данные нужно разделить на несколько взаимонезависимых scope, основываясь на локальности данных при их использовании, для того чтобы можно было максимально распараллеливать задачи по обработке этих данных.</li><li>Каждый scope хранить в отдельной БД, для снижения нагрузки на БД.</li><li>Использовать документоориентированную базу данных, чтобы упростить переход, но прописать вариации схем данных.</li><li>Для отказоустойчивости и предотвращения потери данных, каждая БД должна быть представлена replica set как минимум на 3-х разных физических машинах.</li><li>Мы понимали, что система будет большой и сложной для поддержки. Поэтому нам необходим был инструмент, который бы автоматизировал большинство действий с системой за нас.</li></ul><p>Общую схему как выглядела система вы можете увидеть на следующем рисунке.</p><figure><img src="https://media.tproger.ru/uploads/2020/08/HA2.png" alt="" /></figure><p>Изображение компании Dunice</p><p>Superhost и каждый хост это отдельная физическая машина, со своими ресурсами и настроенным Raid 0 (или Raid 5).</p><p>Superhost (он же мастер) состоит из:</p><ul><li>Основного сервиса (Main Web Server, MWS), который обрабатывает запросы от пользователей по специальным ендпоинтам, мониторит состояние всех хостов, запускает и мониторит состояние миграций данных из внешней БД. Извне, мы в любой момент можем увидеть статистику по использованию ресурсов и текущему состоянию системы.</li><li>Metadata service — это сервис хранилище данных о состоянии кластера (какой scope на какой реплике, какой агент с этой репликой лучше работает, когда была последняя миграция для каждого scope и т.д.). Взаимодействовал с этим сервисов только MWS и CLI</li><li>CLI — это терминальный интерфейс для развертывания и управлением кластером. Назван в честь самой системы.</li><li>Планировщик задач. Этот сервис отвечал за запуск миграций в нужное время.</li></ul><p>Каждый из host N состоит из:</p><ul><li>Один или несколько контейнеров с агент-сервисом на каждом хосте. Нужен, для того чтобы взаимодействовать с базами данных, выполнять миграцию, запускать агрегацию, в режиме потока строить отчет нужного формата и отправлять на MWS.</li><li>Контейнер с базой данных, которая реплицирована на несколько хостов (min 3).<br />В базе данных хранились мигрированные данные из основной БД клиента, но разбиты по scope на разные группы реплик. При инициации миграции, данные брались с некоторым условием, так чтобы в одной группе реплик были данные только для одного scope.<br />Это позволило нам сократить нагрузку на обработку данных и увеличить скорость выдачи отчета. После окончания миграции, мы запускали процесс агрегации (предвычисления). Эта операция предназначена для того, чтобы посчитать все нужные данные для отчетов и положить их в отдельную таблицу. В итоге при построении отчета, мы просто берем данные с фильтрацией из этой таблицы-кеша (получается по сути индекс). Данное решение позволило нам генерировать отчеты на 30 колонок и 200к+ строк за пару минут.<br />Когда приходило время новой миграции, мы таблицу с закешироваными данными переименовывали и работали с ней. После того как миграция окончилась и появилась таблица с новыми закешироваными данными, мы удаляли старую и работали с новыми данными.</li></ul><p>А для того чтобы как то удобно говорить об этой системе, мы решили дать ей имя MoNoRe — по первым слогам ключевых технологий (Mongo, Node, Redis).</p><p>Принцип работы при миграции данных:</p><ol><li>Посылаем запрос на адрес &lt;superhost&gt;/migration/&lt;scopeId&gt;/start</li><li>При первом обращении к MWS с запросом на миграцию будет выбран наименее нагруженный хост и на нем создается первый «data» контейнер с настройками репликации. После этого по этому же принципу будут выбрано некоторое количество хостов для создания «data» контейнеров в количестве указанном в файле inventory.ini как replicaCount. Как только все необходимые контейнеры будут готовы, система инициирует создание replicaSet, дожидается окончания этого процесса и возвращает MWS IP адреса всех участников.</li><li>MWS создает расписание на ежедневную миграцию для указанного scope</li><li>Записывает данные о репликах в Redis</li><li>Запрашивает наименее нагруженный «compute» и отправляет ему команду на запуск процесса миграции для указанной фирмы.</li><li>Агент, получив команду на миграцию соединяется с replicaSet, создает(если она отсутствует) новую базу и начинается процесс миграции.</li><li>Прогресс миграции периодически отправляется в MWS и сохраняется в Redis. В любой момент, извне, мы можем запросить состояние миграции нужного scope у MWS &lt;superhost&gt;/migration/&lt;scopeId&gt;/status.</li><li>Когда все данные успешно перенесены, на суперхост будет отправлено сообщение об успешном выполнении миграции.</li><li>Запускается механизм агрегации данных для построения будущих отчетов в коллекцию report_rows, при этом, если на данный момент такая коллекцию существует — она будет переименована в report_rows_cache, а запись будет осуществляться в новую report_rows.</li><li>Прогресс агрегации периодически отправляется в MWS и сохраняется в Redis. В любой момент, извне, мы можем запросить состояние агрегации нужного scope у MWS &lt;superhost&gt;/migration/&lt;scopeId&gt;/status.</li><li>Когда коллекция report_rows будет готова, на суперхост будет отправлено сообщение об успешном выполнении агрегации.</li><li>Записывает данные о времени последней миграции в Redis</li></ol><p>При последующих запросах на миграцию суперхост берет всю необходимую информацию из Redis, запрашивает наименее нагруженный «compute» и отправляет ему команду на запуск процесса миграции для указанного scope.</p><p>Принцип работы при получении отчета:</p><ol><li>Посылаем запрос на адрес &lt;superhost&gt;/report/&lt;reportId&gt;?additionalParams="</li><li>При первом обращении к MWS с запросом на миграцию будет выбран наименее нагруженный хост и на нем создается первый «data» контейнер с настройками репликации. После этого по этому же принципу будут выбрано некоторое количество хостов для создания «data» контейнеров в количестве указанном в файле inventory.ini как replicaCount. Как только все необходимые контейнеры будут готовы, система инициирует создание replicaSet, дожидается окончания этого процесса и возвращает MWS IP адреса всех участников.</li></ol><p>Как видно из схемы для реализации нашей системы мы решили взять следующие инструменты:</p><ol><li>Node.js (with express) — Использовался для создания HTTP(S) серверов MWS и CS</li><li>MongoDB (with replication) — Так как у команды клиента есть опыт работы с MongoDB, при этом она была достаточно мощной для обработки больших массивов данных за счет Agregation Pipeline, было принято решение ее и использовать как основное хранилище данных.</li><li>ПримечаниеLXD / LXC (Linux Containers) — Система виртуализации, реализующая концепт контейнеров. Использовался для добавления слоя абстракции между элементами системы и серверами на которых происходит разворачивание.  В настоящее время имеет смысл использовать Docker + Docker Swarm)</li><li>SSH – Использовался как транспорт для управления между хостами</li><li>Node.js (with Yargs and Ora) — Использовался при построении CLI.</li><li>Cron(Linux) — выполнял роль планировщика задач.</li><li>PM2 — Использовался для повышения отказоустойчивости Node-Express сервисов.</li><li>Redis — Выполнял роль Metadata service.</li><li>Nginx — На master машине выполняет роль прокси сервера, для взаимосвязи с внешним миром. На каждом хосте выполняет роль прокси сервера для общения между MWS и агентом.</li></ol><h2>Развертывание и управление большой системой = боль?</h2><p>Система на первый взгляд выглядит непросто. Мы понимали что управлять всем этим руками будет не удобно и будет вызывать много вопросов и проблем у заказчика. Чтобы руками развернуть такую систему необходимо очень четко понимать все ее модули, взаимодействие между ними и потратить пару часов. А это очень не устраивало нас как 0-пользователей системы.</p><p>Чтобы решить эту проблему, мы создали CLI c набором команд, позволяющий привести систему в рабочее состояние за 5 минут. Для этого необходимо просто заполнить inventory.ini (см. рис 2) файл с необходимыми данными и запустить команду monore setup. Система сама зайдет на все указанные хосты системы, развернет там необходимые модули, создаст нужные файлы с настройками(nginx, mongo и так далее) и переведет систему в рабочее состояние.</p><figure><img src="https://media.tproger.ru/uploads/2020/08/image2-2.png" alt="" /></figure><p>Изображение компании Dunice</p><figure><img src="https://media.tproger.ru/uploads/2020/08/image4-1.png" alt="" /></figure><p>Изображение компании Dunice</p><p>На первом демо, у тех. директора со стороны заказчика был крайне негативный настрой (оказалось, что до самой демонстрации он не был подключен в наш разговор, и решение о замене Hadoop на наше решение Клиент принимал самостоятельно). Однако, по мере демонстрации возможностей и характеристик системы, интерес возрастал, негодование сменилось удовлетворением, а глаза начали блестеть.</p><p>Это было тяжелое демо с нашей стороны, но все участники были довольны достигнутым, оставалось лишь одно НО…. Уровень отказоустойчивости который у нас был на текущий момент, оказался недостаточным для инициирования процесса приёма-передачи продукта. У нас появилось новое требование, нужно чтобы с системой могло произойти все что угодно (отключили от сети или вышло из строя железо хоста или супер хоста, временные проблемы с доступностью хоста), но система должна продолжать работу, и при этом нормально реагировать на возращении хоста (предотвращение коллизий и несогласованности данных).</p><p>Таких жестких требований к жизнеспособности системы не мог поддержать и Hadoop. Однако мы не стали спорить с Клиентом, и принялись думать как нам это реализовать.</p><h2>Тот самый момент, когда грамотная архитектура решает проблемы за тебя</h2><p>На самом деле, часть этих требований у нас уже были покрыты за счет правильно подобранной архитектуры и программных решений. Например, вопросом коллизий и согласованности данных полностью занималась MongoDB с настроенным Replica Set. А за счет того, что мы храним некоторые метаданные о кластере, потеря одного хоста вообще не является проблемой.</p><p>Однако для достижения поставленных целей этого недостаточно. Все еще могут быть проблемы, если откажет Superhost или больше половины host.</p><p>Решить эту проблему мы смогли с помощью более крупной кластеризации за счет LXD Cluster (теперь контейнеры на всех хостах находились как бы на одном хосте, видели друг друга и могли спокойно общаться, однако появилось требование, чтобы хосты (в том числе и суперхост) должны быть в одной подсети (16 или 24 ранга)), обновлении логики работы MWS (упростился механизм выборки и взаимодействия с агентами, появилась логика регенерации) и CLI (усложнилась логика разворачивания системы, однако упростился механизм сбора статистики с каждого модуля, появилась логика регенерации), и обновление количества и качества хранимых метаданных о системе.</p><p>Шаг с кластеризацией, помог нам более гибко управлять кластером из любого места кластера. Т.е. по факту, любой хост мог стать супер хостом.</p><p>Общий алгоритм работы:</p><ul><li>Если теряется связь с хостом, то админу отправляется оповещение со всей необходимой информацией о хосте. А так же, о том что в систему имеет смысл добавить еще хостов, для поддержания высокой отказоустойчивости. При добавлении нового хоста, через CLI, система в автоматическом режиме начинает его полноценное использование.</li><li>Если выходит из строя суперхост, то устраивается кворум, и один из хостов становится суперхостом. Инициируется процесс регенерации. Устанавливается Redis, MWS, CLI. Происходит сбор информации о кластере (за счет того, что в каждой реплике БД есть минимальное количество метаданных о самой реплике, о данных которые в ней хранятся и о последней миграции). В итоге через примерно 30 секунд на кластере из 20 машин, система снова становится обрабатывать запросы извне. Так же отправляется письмо администратору, с информацией о произошедшем и результатом.</li></ul><figure><img src="https://media.tproger.ru/uploads/2020/08/image1-1.png" alt="" /></figure><p>Изображение компании Dunice</p><p>Итогом данного проекта стало успешное внедрение сначала в тестовый контур, а в последствии и в продакшен. Итоговые характеристики системы при построении отчета:</p><ol><li>Оперативная память не поднимается выше 500мб при формировании отчета ~200к строк из таблицы с &gt; 1млн записей</li><li>Время получения отчета ~60 секунд (для ~200к строк).</li></ol><p>А для нас, результатом был ценнейший опыт построения Enterprise систем. Мы еще раз убедились, что правильно подобрать инструмент под задачу, это всего лишь полдела, нужно еще уметь правильно готовить с помощью этого инструмента (ну, и грамотная архитектура дает огромную свободу развития и масштабирования приложения).</p><p>Изначально, клиент был недоволен результатом, который получился у его команды. Мы же, взяли те же инструменты, добавили немного новых, правильно определили архитектуру и применили весь наш опыт в разработке Enterprise систем.</p>]]></content:encoded>
    </item>
    <item>
      <title>Инструмент Managed Databases от DigitalOcean получил поддержку MySQL и Redis</title>
      <link>https://tproger.ru/news/managed-databases-mysql-redis</link>
      <comments>https://tproger.ru/news/managed-databases-mysql-redis?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Екатерина Никитина]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/managed-databases-mysql-redis</guid>
      <description><![CDATA[<p>Сервис DigitalOcean запускает кластер баз данных на MySQL, Redis или PostgreSQL за несколько минут: нужно выбрать движок, тариф и дата-центр.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/managed-databases-mysql-redis">Инструмент Managed Databases от DigitalOcean получил поддержку MySQL и Redis</a>»</p>]]></description>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 21 Aug 2019 14:34:24 GMT</pubDate>
      <content:encoded><![CDATA[<p>Команда DigitalOcean анонсировала Managed Databases для управления базами данных на MySQL и Redis. Управление базами на PostgreSQL доступно с февраля 2019 года.</p><p>Инструмент за несколько минут запускает кластер баз данных на любом из трёх движков. Нужно только выбрать движок, тарифный план в зависимости от потребностей проекта и дата-центр. Если потребности изменятся, можно будет изменить и тарифный план.</p><p>Managed Databases сам ежедневно делает бэкапы, они хранятся по семь дней. Безопасность передачи данных обеспечивает оконечное шифрование. Все базы работают в пределах приватной сети. Можно даже полностью запретить запросы к ним из публичной части Интернета, а разрешить — лишь для нескольких источников из «белого списка».</p><p>Инструмент поддерживает MySQL версии 8, Redis версии 5 и PostgreSQL версий 10 и 11.</p>]]></content:encoded>
    </item>
    <item>
      <title>Гайд по использованию Lua-скриптов в Redis</title>
      <link>https://tproger.ru/translations/redis-lua-guide</link>
      <comments>https://tproger.ru/translations/redis-lua-guide?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Corewood]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/redis-lua-guide</guid>
      <description><![CDATA[<p>Язык Lua встраивается почти в любое приложение, включая Redis: скрипты добавляют базе собственные расширения и работают как умные транзакции.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/redis-lua-guide">Гайд по использованию Lua-скриптов в Redis</a>»</p>]]></description>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Lua]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 24 Nov 2018 12:36:09 GMT</pubDate>
      <content:encoded><![CDATA[<h3>Что такое язык Lua?</h3><p>Язык программирования Lua появился в 1993 году. По своей структуре он является компактным языком программирования, который можно встраивать практически в любое приложение — от World of Warcraft до веб-сервера Nginx. Ну и конечно же, в Redis, о котором далее и пойдёт речь.</p><p>Благодаря Lua в Redis возможно встраивать собственные скриптовые расширения для базы данных. Вызов скриптов выглядит следующим образом:</p><p>Далее сам Lua-скрипт:</p><p>Что более важно, вы можете запускать скрипты в качестве “умных транзакций”. Это позволяет обрабатывать ошибки в рантайме, не останавливая приложение. Безусловно, то, насколько “умными” будут эти транзакции, зависит целиком и полностью от вас.</p><h3>Ключи и аргументы</h3><p>Для обращения к скрипту неплохо бы было передать ему ключи и аргументы. Например, как в нижеприведённом коде:</p><p>В конце EVAL мы видим ноль — это количество ключей, переданных скрипту. Но если вместо ноля написать 2 foo bar fizz buzzthen, первые два элемента foo и bar будут переданы в качестве ключей, а fizz и buzz — в качестве аргументов.</p><p>Ключи доступны Lua-скрипту в таблице KEYS. Таблица в Lua — это ассоциативный массив, который также используется в качестве массива из одного элемента. Если помимо ключей используются аргументы, они будут доступны в таблице ARGV, например:</p><p>В Lua .. используется в качестве оператора объединения, так что здесь в качестве возвращаемого значения мы получаем аргумент, привязанный к пробелу, и название ключа, которое было передано в качестве аргумента:</p><p>Никакой магии в KEYS, это просто строка, так что нам по-прежнему нужно получить их значение.</p><p>Что касается вызова Redis из Lua, можно использовать функцию redis.call(). Например:</p><p>Прим. автора  Если в процессе объединения вы получаете ошибку “attempt to concatenate a boolean value” (попытка объединения значений булевого типа), скорее всего name:first не было присвоено какое-либо значение.</p><p>Таким образом, скрипт взял параметр, нашёл значение ключа и вернул в качестве вывода созданную строку.</p><p>Однако помимо EVAL есть и другой способ передать скрипт на сервер. Например, используя аргументы команды redis-cli. Попробуем написать чуть более сложный скрипт:</p><p>Сохраним его как longhello.lua и выполним в командной строке:</p><p>Итак, мы запускаем redis-cli, добавляем параметры -h, -p и -a для связи с базой данных. Затем идёт --eval, позволяющий указать имя файла, в виде которого скрипт отправится на сервер. Также нашему скрипту понадобятся ключи и аргументы. В данном случае ключами являются указанные до первой запятой параметры команды.</p><h3>Сложные скрипты</h3><p>Следующий случай наглядно демонстрирует, как Lua решает одну небольшую проблему. Допустим, разные пользователи или разработчики увеличивают счётчики в большой структуре данных. Например, region:one увеличивает count:emea, count:usa, count:atlantic, в то время как region:two затрагивает лишь count:usa. Эти счётчики могут быть добавлены позже, но вдруг вам важно убедиться, что добавятся они все разом? Самое время вспомнить про “умные транзакции”.</p><p>Добавим все наши регионы в список:</p><p>Создадим локальную переменную:</p><p>Начнём с переменной-счётчика — он будет считать все операции по увеличению наших округов.</p><p>Теперь запросим у Redis все значения списка, относящиеся к первому ключу.</p><p>Здесь мы запустили цикл, в котором функция ipairs() просмотрит каждую задействованную Lua-таблицу и передаст из неё ключ.</p><p>С каждым вызовом мы увеличиваем указанный ключ, а заодно и наш счётчик.</p><p>После окончания цикла мы получаем конечное число итераций. Сохраним итоговый скрипт и запустим его на сервере:</p><p>В таблице будут содержаться следующие значения:</p><p>Значения для region.two:</p><p>Но что, если бы во время выполнения скрипта произошла ошибка? Он просто продолжил бы выполняться, игнорируя её. Для вывода деталей ошибки следует использовать redis.pcall().</p><h3>Кэширование скриптов</h3><p>Для того чтобы не загружать скрипт каждый раз перед выполнением, можно использовать команду SCRIPT LOAD для загрузки скрипта в кэш. Рассмотрим пример использования из командной строки:</p><p>Здесь $(cat broadcast.lua) превращает наш скрипт в аргумент. А шестнадцатеричное число ниже — SHA1-подпись нашего скрипта. Её можно использовать для его последующего вызова командой EVALSHA:</p><p>Также есть команды для проверки наличия скрипта на сервере и его удаления – SCRIPT EXISTS и SCRIPT FLUSH соответственно.</p><h3>Не всё так просто</h3><p>После довольно небольшого промежутка времени (по умолчанию — 5 секунд) Lua-скрипты начнут выдавать ошибки в ответ на запросы — в таком случае возможно только “убить” скрипт командой KILL SCRIPT либо выключить сервер командой SHUTDOWN NOSAVE. С другой стороны, 5 секунд — очень щедрое ограничение, ведь ваши скрипты должны выполняться буквально в течение миллисекунд. И на это есть очень веская причина: во время выполнения ваших скриптов все остальные процессы приостанавливаются.</p><h3>Заключение</h3><p>Итак, в этой статье мы рассмотрели примеры написания простых и сложных Lua-скриптов, их вызов из Redis, а также запись и хранение на сервере.</p>]]></content:encoded>
    </item>
    <item>
      <title>GitLab снова упал после обновления, но на этот раз его восстановили менее, чем за полчаса</title>
      <link>https://tproger.ru/news/gitlab-down-again-due-to-redis-cluster-problems</link>
      <comments>https://tproger.ru/news/gitlab-down-again-due-to-redis-cluster-problems?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Михайлишин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/gitlab-down-again-due-to-redis-cluster-problems</guid>
      <description><![CDATA[<p>Сбой сервиса вызвали проблемы с кластером Redis после обновления до версии 8.17.0 EE RC1: фоновые обработки приостанавливали, но быстро вернули.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/gitlab-down-again-due-to-redis-cluster-problems">GitLab снова упал после обновления, но на этот раз его восстановили менее, чем за полчаса</a>»</p>]]></description>
      <category><![CDATA[GitLab]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 09 Feb 2017 22:29:19 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сегодня в <a href="https://twitter.com/gitlabstatus">официальном твиттер-аккаунте GitLab</a> появилось сообщение о том, что сервис снова не работает из-за проблем с кластером Redis.</p><p>Проблемы начались после обновления до версии 8.17.0 EE RC1, изначально команда GitLab не предполагала каких-либо временных ограничений в доступности сайта, однако проблемы всё-таки случились. Затем были временно приостановлены любые фоновые обработки, но также были относительно быстро восстановлены.</p><p>Некоторые пользователи Reddit <a href="https://www.reddit.com/r/programming/comments/5szyh0/gitlab_goes_down_again_due_to_issues_with_the/ddjdyxa/">высказывают мнение</a>, что ребятам из GitLab нужно просто несколько притормозить с развитием сервиса и сосредоточиться на обеспечении стабильности, так как они уже значительно сократили разрыв с популярным конкурентом в лице GitHub и сейчас удовлетворяют потребности 99% своих пользователей.</p><p>Напомним, не так давно в Сети активно обсуждался <a href="https://tproger.ru/news/gitlab-accidentally-deleted-data/">инцидент</a> со случайным удалением системным администратором GitLab 300 ГБ данных, после которого последовал довольно длительный и болезненный период восстановления, который транслировался в прямом эфире. Виновника, к слову, не уволили, но, говорят, что отобрали права sudo.</p><p><a href="https://gitlab.com">GitLab</a> — быстро развивающийся веб-сервис для организации Git-репозиториев, предоставляющий также вики-движок, системы отслеживания задач и автоматизации сборки и тестирования. Изначально сервис был написан на Ruby украинцами Дмитрием Запорожцем и Валерием Сизовым, впоследствии некоторые части были переписаны на Go. GitLab используют, например, такие компании, как IBM, Sony, NASA, Alibaba и другие.</p>]]></content:encoded>
    </item>
  </channel>
</rss>