jane_yanchenko | Unsorted

Telegram-канал jane_yanchenko - Женя Янченко

4817

Блог про архитектуру, работу и карьеру в ИТ. Автор - бэкендер, тимлид, руководитель разработки. Истории из опыта, инструменты и лайфхаки. Написать мне: @miralasse

Subscribe to a channel

Женя Янченко

Приключения моего лендинга

Вообще я хотела вчера написать технический пост. В бэклоге есть идеи, одну я обдумывала, набросала черновик и собиралась выделить несколько часов на написание.

Но пришлось заниматься немного другим.

С чего все началось.
Для курса по Кафке мне нужен был сайт. Обдумав функциональность, я решила запилить его на WordPress, чтобы легко дописывать тексты в случае необходимости. Плюс изначально задумывала больше форм.

Свою платформу для школы я не разрабатывала, все же это задача посерьезнее, требует времени, а время лучше уделить подготовке самого курса, поэтому платформу выбрала готовую.

Сайт сделала, осталось выбрать хостинг. Тогда были проблемы у ДЦ Яндекса, многие уходили на Селектел, так что я решила, что у них и так много нагрузки сейчас, и выбрала Бегет.

Задеплоила, всё ок.

И ровно в день анонса курса прилетают новости про критические уязвимости в PHP. Помните такие?

Читаю официальные рекомендации, советуюсь со знакомым лидом, у которого проекты на PHP. Получается, что проще всего поднять версию PHP. Захожу в настройки хостинга, а в списке доступных нужной версии нет 🙈 Ну оно и логично, патчи только вышли. Что делать?

С одной стороны, вряд ли кому-то нужен мой сайт, чтобы его взламывать.

С другой стороны, так ли мне нужен бэкенд на лендинге? Тем более от лишней формы я отказалась: протестировала пользовательский путь и решила, что чем меньше обязательных полей, тем лучше.

Когда проблемы с моими сервисами на работе, я умею смотреть на ситуацию спокойно: сначала быстрый фикс, если нужно, потом разбираем подробно и продумываем, как сделать, чтобы ситуация не повторилась. Даже если что-то серьезно сломалось, я примерно понимаю, что делать, и не воспринимаю каждую проблему как личную неудачу.

Тут же вроде и не баг, сайт работает, можно было даже просто забить и подождать новой версии PHP на хостинге, но я весь день правила мелкие огрехи на сайте, наконец анонсировала его, и тут такое. Я конечно расстроилась и стала ругать себя, зачем вообще притащила этот PHP и Wordpress. Как говорится, "так исторически сложилось" и "архитектура спроектирована под первоначальные требования".

Поговорила с друзьями, пришли к решению переписать сайт на статику. Спасибо Кодексу, получилось это сделать в тот же вечер, параллельно отвечая на вопросы ребят про курс. Большое спасибо всем, кто купил в тот же день! ❤️

Задеплоив новый вариант, я выдохнула, посмотрела на работающий сайт и подумала, что теперь все ок 😅

Но у инфраструктуры были другие планы.

Вчера утром я вижу новости, что у Бегета серьезные технические проблемы. Пу-пу-пу… Захожу на сайт - лежит. Моя первая мысль была: "ну, хорошо, что переписала, статический сайт перенести будет проще".

Вздыхаю, выбираю другой хостинг, переношу сайт.

Параллельно приходит письмо от платформы школы, что у них тоже проблемы с хостингом, и что по основному адресу они не доступны, но есть резервный. Ура, школа не подвела! Меняю на сайте ссылки, деплою заново.

Остается перепривязать домен к новому хостингу, домен у меня на reg.ru. Захожу в личный кабинет, чтобы поменять адрес, и ... у них тоже проблемы! 🤬

Официально ничего не писали, но в личный кабинет я не могла зайти около часа или двух. Наконец получилось, перепривязала домен, подключила сертификат, всё на удивление быстро обновилось, и сайт снова заработал. Фуууууух!

Вот так, казалось бы, простой одностраничник устроил мне приключение с уязвимостями, переездом и недоступными сервисами.

На работе я привыкла думать о том, что сервис может упасть (а для чего тогда все эти Transactional Outbox-ы и прочее), а баги - это вообще часть нашей профессии, их главное вовремя исправить. Но когда делаешь что-то свое, во что вложила столько времени и сил, почему-то каждый такой сбой воспринимается гораздо болезненнее, и профессиональное спокойствие куда-то испаряется. Рада, что во втором раунде смогла взять себя в руки и реагировать на ситуацию "по-рабочему".

В общем, сайт работает, курс готовится к запуску, а я очень надеюсь, что на этом приключения моего лендинга закончились.

Читать полностью…

Женя Янченко

Куда пропали 11 заказов

Однажды бизнес пришел к нам с багом: в отчете из нашей системы не хватает 11 позиций, которые есть в других системах.

Давайте для примера считать, что это были заказы.

«В отчете за прошлую неделю не хватает 11 заказов. В системе оформления они есть, менеджеры с ними работали, а в отчет они почему-то не попали. Можете проверить?»


Отчеты формировались отдельным микросервисом, который получал из Кафки события о создании заказов, сохранял данные в свою БД, обогащал их и по запросу пользователя формировал нужный отчёт.

Начали разбираться.

Сначала проверили БД микросервиса отчетов, может заказы есть, но не попали в отчет из-за какого-то бага?
Нет, в БД микросервиса отчетов этих 11 заказов нет.

Проверили upstream-сервис, который является продюсером для нашего микросервиса отчетов: у него все заказы есть, включая пропавшие.

Продюсер отправил все успешно, ошибок в логах нет.

Консьюмер в сервисе отчетов идемпотентный, но в любом случае пропавшие заказы не являются дублями уже существующих.

В БД и соответственно в отчетах, построенных по данным БД, отсутствуют не все заказы, начиная с какого-то времени, а только эти 11, созданные в определённый промежуток времени, а после них новые заказы снова есть.

Получается, что заказы от продюсера ушли, но до консьюмера то ли не дошли, то ли он их не сохранил 🤔

Открываем Kafka UI, к счастью срок хранения еще не вышел, так что находим в партициях все заказы, включая отсутствующие в отчете, и следующие за ними. То есть Кафка ничего не потеряла.

Ошибок в логах консьюмера нет. Сейчас свежие заказы обрабатываются нормально, сохраняются в БД и попадают в отчеты.

Перед интервалом времени, в который были отправлены те 11 заказов, был релиз. Релиз содержал рефакторинг микросервиса отчетов: его разделили на два разных микросервиса, но настройки консьюмера не менялись, автокоммит выключен, как и был. Проверили по истории изменений git, что изменений кода в блоке настроек консьюмера в релизе не было.

Продолжали читать тот же топик, с тем же количеством партиций.

Как вы думаете, куда пропали 11 заказов? Почему в Кафке они есть, а в сервисе отчетов их нет?

Чуть позже напишу, чем закончилось расследование 👀

Читать полностью…

Женя Янченко

Ребята, я сделала курс по Кафке! 🎉

Кажется, по моим постам вы уже знаете, что мне нравится подробно разбирать технические темы. Хотелось сделать курс, который будет полезен и тем, кто только знакомится с Кафкой, и тем, кто уже с ней работает и хочет разобраться глубже. Чтобы было много примеров, схем, практики и ответов на вопросы "а почему это работает именно так?"

Очень рада представить вам:

🎉 Кафка для самых маленьких 🎉


Курс подойдет, если вы:

✔️ еще не работали с Кафкой или уже сталкивались, но не погружались в детали

✔️ знакомы с базовой теорией, но не хватает практических кейсов, хочется разобрать реальные паттерны работы и истории с прода

✔️ проектируете интеграции или пишете код с ИИ, но не всегда можете объяснить его решения

✔️ хотите расширить и систематизировать знания, усилить экспертизу


Для каких профессий:

🟢 системных аналитиков
🟢 бэкенд-разработчиков на любом языке
🟢 QA-инженеров
🟢 архитекторов


Очень кратко, что будет:

➡️ сначала разберемся, в каких сценариях нужна Кафка, подробно посмотрим, как она устроена внутри, научимся работать с ней через UI-инструменты

➡️ затем разберем продюсеров, что такое идемпотентный продюсер, где нужно использовать outbox, на какие настройки обратить внимание

➡️ будет большущий модуль про консьюмеры: про настройки, идемпотентность, гарантии доставки, consumer lag, DLQ, static membership, расчет партиций под нагрузку, транзакции

➡️ разберемся с CDC через Kafka Connect, настроим свой CDC

➡️ научимся проектировать интеграции через Кафку, тестировать и мониторить.

Я сильно заморочилась с тем, чтобы курс при всей своей насыщенности оставался понятным: там много схем, все сложные механизмы я разбираю пошагово на конкретных примерах, даю ответы на частые вопросы с собесов, а также рассказываю реальные истории.

Моя цель, чтобы после обучения у вас сложилась цельная картина работы Kafka: как она устроена, как с ней работать и как принимать обоснованные технические решения в реальных задачах.


Формат: видеоуроки + живые созвоны + чат.
Старт: 9 ноября 2026
Длительность: 5 недель

🔗 САЙТ КУРСА
🔗 САЙТ КУРСА
🔗 САЙТ КУРСА

Читать полностью…

Женя Янченко

Случился у меня тут спор со знакомым архитектором. Тема довольно душная, потому что можно и так и так 😂

Рассказываю: в один топик Kafka отправляют события разных видов (для примера OrderCreated и OrderUpdated) в формате JSON. Где лучше разместить поле eventType - в headers или в value?

У сообщения в Kafka есть:
🔴Key - ключ сообщения, например ID заказа.
🔴Value - содержимое сообщения.
🔴Headers - дополнительные метаданные в формате ключ-значение.
🔴Служебные метаданные, например timestamp.


Вариант 1. Хранить eventType в headers

Событие создания заказа будет содержать отдельно:


headers:
eventType: OrderCreated

value:
{
"orderId": "123",
"total": 1500
}


Событие обновления заказа (с другой ДТОшкой):


headers:
eventType: OrderUpdated

value:
{
"orderId": "123",
"status": "PAID"
}


Плюсы:

➕ Можно определить тип события, не разбирая JSON. Например, если консьюмер обрабатывает только OrderCreated, он может сначала проверить header и пропустить ненужные сообщения без десериализации JSON. Правда, Kafka всё равно передаст ему все сообщения из назначенных партиций, а экономия будет только при соответствующей реализации десериализации.

➕ Разделение бизнес-данных и метаданных (если мы считаем тип события метаданными).

Минусы:

➖ Value сообщения не самодостаточен. Если мы по ошибке сохраним/передадим дальше только value сообщения, то информация о типе события потеряется.


Вариант 2. Хранить eventType в самом value

Тогда событие создания заказа:


value:
{
"eventType": "OrderCreated",
"payload": {
"orderId": "123",
"total": 1500
}
}


Событие обновления заказа:


value:
{
"eventType": "OrderUpdated",
"payload": {
"orderId": "123",
"status": "PAID"
}
}


Плюсы:

➕ Самодостаточность value сообщения. В JSON есть всё необходимое, чтобы понять, какое событие произошло. Можно сохранять/передавать в исходном виде, тип не потеряется.

➕ Единый контракт. Можно описать всю структуру события одной схемой, включая допустимые значения eventType и соответствующие им payload.

Минусы:

➖ Для определения типа нужно прочитать JSON. Если консьюмеру нужны только определенные события, ему придется хотя бы частично разобрать value, чтобы понять, нужно ли обрабатывать сообщение.

- - -

В общем, универсального правила, как лучше, здесь нет. Типичное "зависит от ситуации".

Для сценария с большим потоком сообщений разных типов, когда при этом нашему консьюмеру нужен только один тип, можно чуть сэкономить время обработки на десериализации, если выбрать вариант headers (и соответствующе настроить десериализацию).

Если же речь идет о том, что наш консьюмер обрабатывает все виды событий, то я за хранение напрямую в value, хотя хранение в headers мне кажется архитектурно более чистым (за этот вариант топил арх).

Я уже сталкивалась с ситуациями, когда сначала все работало ок, а потом при изменениях что-то где-то не учли. Если мы сохраняем, обогащаем, перекладываем сообщение в другую систему, то мне кажется подход "всё свое ношу с собой" (то есть eventType в самом value сообщения) будет менее подвержен багам.

Какой вариант хранения eventType вам нравится больше?

🐼 — в headers

🦆 — внутри value

Читать полностью…

Женя Янченко

Как race condition оставил без электричества 50 миллионов человек

14 августа 2003 года. Оператор энергосети США Джим смотрит на экран: всё спокойно, никаких аварийных предупреждений. Джим не знает, что система уже перестала показывать ему реальную ситуацию.

В это же время в энергосети нарастала аварийная ситуация, которая через несколько часов оставит без электричества около 50 миллионов человек в США и Канаде.

И одну из ключевых ролей в этой истории сыграла ошибка синхронизации в коде.

➡️ Что произошло?

Энергокомпания FirstEnergy использовала систему управления электросетями GE XA/21, которая была написана на C/C++ и работала с 1995 года.

Система получала информацию с оборудования, отслеживала состояние электросети и предупреждала операторов о проблемах, например, об отключении линии электропередачи.

14 августа в 14:14 модуль обработки предупреждений перестал работать в штатном режиме.

Позднее инженеры General Electric (GE) выяснили, что причиной была race condition - гонка между двумя параллельно выполнявшимися процессами.

Оба процесса имели доступ к одной структуре данных в оперативной памяти. Из-за бага они смогли одновременно получить доступ на запись, в результате данные оказались повреждены, а приложение, обрабатывавшее предупреждения, попало в бесконечный цикл.

Интересный факт, что эта ошибка могла возникнуть только если оба процесса столкнулись в коде практически одновременно: окно, в котором это было возможно, измерялось миллисекундами!

И именно 14 августа сложилась нужная комбинация событий.

Но основная проблема заключалась в том, что модуль предупреждений не завершился с ошибкой, а завис в бесконечном цикле и перестал выполнять основную функцию. При этом система никак не сообщила операторам, что они больше не получают предупреждения об авариях.

В тот день было жарко. Из-за нагрева и нагрузки несколько ЛЭП провисли, коснулись близко растущих деревьев и отключились. Но из-за внешне невидимой проблемы Джим и другие операторы не увидели на своих экранах никаких алертов.

Тем временем новые события продолжали поступать, но зависший обработчик больше не мог их обрабатывать. Очереди росли, входные буферы переполнялись.

Продолжение ⬇️

Читать полностью…

Женя Янченко

Недавно рассказывала про Avito.Tech.Conf — конференцию для лидов и менеджеров. На выходных я посмотрела запись онлайн-трансляции.

Хочу рассказать про два доклада, которые мне особенно понравились.

1️⃣ Доклад Артёма Арюткина о том, почему ускорять написание кода с помощью AI недостаточно.

Как мы знаем по собственному опыту, написание кода занимает только часть времени инженера. Остальное: разобраться в постановке, уже существующем коде, другой документации, с кем-то поговорить, потестить сделанное, провести код-ревью коллег, ну и, конечно, переписка и созвоны.

Последние пару лет индустрия в основном пыталась решить вопрос ускорения написания кода с помощью AI, и во многом это получилось. Но сразу появилось следующее бутылочное горлышко — код-ревью. Сейчас ускоряют его, а значит появится следующее.

Поэтому задачей руководителя становится не ускорение именно написания кода, а поиск мест, где конкретно в нашем процессе теряется время, и расшивание именно этих ограничений.

Причем определить это основываясь только на жалобах команды не всегда получится. Мне понравился пример из доклада: если разработчику приходится периодически перезапускать долгую сборку, он может вообще на это не жаловаться. Просто привыкает и начнет закладывать это время в оценку задачи.


Поэтому предлагается измерять опыт разработчиков системно: наблюдения, данные, регулярные опросы, короткие опросы после использования внутренних инструментов. И уже на основании этих данных решать, что именно имеет смысл ускорять с помощью AI.

Во второй части доклада Артём рассказывает, как выглядит целевая картина разработки с AI глазами менеджеров, и на чем планируется фокус в 2027.

✔️ Доклад стоит посмотреть, если вы занимаетесь процессами в команде, а также просто с точки зрения "интересно, куда руководители уровня CTO и CPO хотят двигать разработку".


2️⃣ Доклад Вадима Мадисона о том, как техлиду разговаривать с бизнесом и вышестоящим руководством.

Одна из главных мыслей полностью перекликается с моим опытом: ценность предложения нужно объяснять через то, что важно для человека, принимающего решение.

То есть для нас, технических специалистов, аргументами могут быть вопросы архитектуры, техдолга, сложности поддержки, но для бизнеса обычно нужно подбирать другие аргументы. Например, деньги, риски остановки процессов, влияние на пользователей и др.

Еще интересный момент доклада про изменение майндсета по мере роста руководителя. Если тимлид — это амбассадор и защитник своей команды, то чем выше уровень руководителя, даже технического, тем сильнее его фокус смещается на бизнес. Поэтому одну и ту же инициативу нельзя одинаково презентовать тимлиду и CTO: для них нужно подбирать разные аргументы, соответствующие их роли и зоне ответственности.

Еще понравилась часть про нереалистичные цели сверху. Когда такое приносят, первая реакция у технаря будет:

"Это невозможно, и сейчас я объясню почему"


Понятно, что такой ответ обычно не способствует укреплению отношений между бизнесом и разработкой 🤔
Можно и нужно посмотреть на ситуацию иначе: определить, какую проблему руководство пытается решить этими целями, и как мы можем помочь решить именно ее.

Это не означает превратиться в YES-man и подписаться под невыполнимыми обещаниями, а наоборот: разобраться в потребностях и предложить реалистичный способ приблизиться к нужному бизнес-результату.

✔️ На мой взгляд, доклад будет полезен тем, кто много общается с бизнесом: системным аналитикам, архитекторам, лидам и руководителям разработки.

На самой конфе кроме докладов было много активностей: тимлид-"техосмотр", управленческий покер, даже погребок с фирменными соленьями 🥒 В следующем году надо запланировать пойти оффлайн.

Где посмотреть записи докладов Avito.Tech.Conf:

- на YouTube
- в VK

Читать полностью…

Женя Янченко

Представьте ситуацию.

Дисклеймер: пример вымышленный, проблема реально встречающаяся 🐤

Вы разрабатываете сервис приема заказов order-service. В зависимости от состава заказа и истории покупателя вы можете предлагать разные виды оплаты: предоплату, частичную предоплату, постоплату. Логика анализа состава заказа реализована в order-service, а за информацией по покупателю он обращается в client-service другой команды.

Фронт вызывает:

POST /orders

{
"clientUid": "some-uuid",
"products": [{...}, {...}]
}

Ответ:

{
"orderUid": "some-uid",
"orderStatus": "WAITING_FOR_CONFIRMATION", //ожидает подтверждения
"paymentType": "ON_DELIVERY" //оплата при получении
}


На основании ответа фронт показывает страницу подтверждения заказа с типом оплаты.

Обычно client-service отвечал order-service за 500 мс. Но что-то пошло не так, и теперь он отвечает по 20 секунд
Таймаут между фронтом и order-service 30 секунд, сам order-service отрабатывает не более 500 мс, но часть запросов от фронта стала падать по таймауту.

Почему так происходит, ведь 20 cек client-service + 500 мс order-service < 30 сек таймаута?

Потому что эти 20,5 сек - это время выполнения запроса, только если он сразу получил все необходимые ресурсы, то есть "чистое" время выполнения. А при нагрузке запрос может сначала ждать свободный поток, соединение с БД или другой ограниченный ресурс и только потом начать свои 20 секунд ожидания client-service.

Допустим, код order-service работает так:

1️⃣открывается транзакция с БД
2️⃣сохраняются данные заказа
3️⃣вызывается по REST client-service
4️⃣после его ответа сохраняются еще данные в БД
5️⃣транзакция закрывается
6️⃣идет ответ фронту

Если приложение держит транзакцию, пока ждет ответ client-service, то все это время соединение с БД остается занятым, а размер пула соединений ограничен.

Например, в пуле 10 соединений.
Первые 10 запросов заняли все соединения и на 20 секунд ушли ждать client-service.

11-й запрос уже не может начать работу с БД и ждет свободное соединение.
Через ~20 секунд оно освобождается, 11-й запрос сохраняет заказ, но теперь ему предстоит еще 20 секунд ждать ответ client-service.

Итого около 40 секунд end-to-end: 20 сек ждал в очереди к БД + 20 сек ждал ответ от client-service.

Для фронта 11-й запрос упадет по таймауту, хотя сам client-service для каждого отдельного вызова отвечает за 20 секунд.


✅ Поэтому первым делом стоит проверить, не держим ли мы долгую транзакцию во время ожидания внешнего сервиса. И если вдруг держим, то позволяет ли бизнес-логика изменить границы транзакции так, чтобы вынести из нее внешний вызов.

У долгих транзакций есть и другие минусы. Когда в PostgreSQL мы делаем UPDATE строки, создается новая версия строки, а старая сразу не удаляется. Очисткой устаревших строк занимается фоновый процесс VACUUM. Он очищает версии, которые больше не нужны активным транзакциям. Долгоживущие транзакции могут мешать очистке старых версий строк, их накопление ведет к разрастанию таблиц и индексов и ухудшению производительности запросов.

А если долгих транзакций нет или БД вообще не используется, что тогда?

✅ Если погуглить, в таких ситуациях обычно предлагается использовать паттерн Circuit breaker: если внешний сервис деградировал, временно перестать отправлять в него новые запросы, чтобы не допустить каскадного отказа.

Это поможет сохранить доступность order-service, но возникает бизнес-вопрос: что делать с самим заказом, если без client-service мы не можем определить paymentType?

Варианты:

✅ Возвращать ошибку пользователю, но делать это быстро, не удерживая ресурсы десятки секунд

✅ Кэшировать ответы client-service - подойдет, если много заказов от одних и тех же клиентов, но надо уточнять, как инвалидировать кэш, как часто обновляется ответ по клиенту (может он исчерпает лимит на постоплату, а мы из-за кэша не заметим)

✅ Договориться с бизнесом, что при недоступности client-service paymentType определять только по локальной логике

🐼 И конечно разговаривать с командой client-service, почему сервис вместо 500 мс стал отвечать 20 секунд 😅

Читать полностью…

Женя Янченко

Только не говорите, что ещё не успели зарегаться на Avito.Tech.Conf 👀

Ко мне пришли ребята из Авито.Тех и попросили рассказать про Avito.Tech.Conf.
Это конференция для тимлидов, руководителей и всех, кто управляет процессами и командами. Собираются большим, но уютным коммьюнити, чтобы обсудить наболевшее, послушать доклады и просто пообщаться и переключиться 🚀

Какие темы будут обсуждаться:
– Как меняется сетап команды в эпоху AI-агентов;
– Как при этом не стать «эффективным менеджером» в плохом смысле;
– Когда стоит отказаться от стабильности и выбрать фокус на личных целях;
– Как расти без роста штата на практике.

Кстати, ещё выкатили обновление программы — а там в панельной дискуссии участвует учёный-астрофизик Владимир Сурдин! Он расскажет о личном пути в науке, о том, какие загадки космоса по-прежнему по-настоящему увлекают физиков и астрономов, и почему фундаментальные исследования важны, даже если их эффект нельзя положить в презентацию с ROI.

26 сентября в Москве, участие бесплатное.

Мест на офлайн уже не осталось, но можно зарегистрироваться на онлайн-трансляцию!

Так что если думаете — не думайте. Забирайте последние места по ссылке.

Читать полностью…

Женя Янченко

Куда пропало обращение

Однажды от руководителя техподдержки пришло письмо, суть которого сводилась к следующему:

"В выгрузке потерялось обращение 58122. Его кто-то удалил?"


Руководитель техподдержки регулярно выгружал себе обращения, с которыми работали его сотрудники и менеджеры других отделов. Там была история движения по статусам, время, кто работал и т.д.

В качестве номера обращения использовался id записи в БД. Обычно обращения шли по порядку, но в этой выгрузке были такие номера:

58120
58121
58123
58124

58122 отсутствовал.

Команда, которая поддерживала функциональность модуля техподдержки, начинает проверять.

БД PostgreSQL. В таблице ticket id объявлен как BIGSERIAL (при вставке значение автоматически берется из связанной sequence).

Проверяют в БД: нет обращения с таким id.

Могли ли его удалить?

Эндпойнта удаления обращений вообще нет, то есть через API удалить обращение нельзя.

Доступа к продовой БД у техподдержки нет, у разработчиков тоже нет. Есть у лида 🤔

Подключаются ко всем репликам БД и мастеру, нигде нет такой записи.

Смотрят соседние записи:

58121 - создано в 12:23
58123 - создано в 12:49


Это дает примерный ориентир по времени.

Делают поиск в логах приложения по этому id. Находят!

12:41:03 Creating ticket, id=58122


То есть обращение создавалось, но в базе его нет и других строк в логах с id=58122 нет.

Как вы думаете, куда пропала запись 58122? Удалил кто-то из тех, у кого был доступ?

Чуть позже напишу, чем закончилось расследование 👀

Читать полностью…

Женя Янченко

🎞 Готова запись стрима про Кафку:

https://youtu.be/2aRKsD-MWDA

Большое спасибо всем, кто пришел, слушал и задавал вопросы! Благодаря вам получилось очень живое и интересное обсуждение 💖

Читать полностью…

Женя Янченко

Соотношение чтений к записям (read/write ratio)

На всех собесах, где поднимались архитектурные вопросы (сисдиз или нет), интервьюер никогда не подсвечивал это сам, но обычно одобрительно кивал, когда я озвучивала эту характеристику системы. Давайте посмотрим, для чего это может быть полезно.

➡️ На сисдиз собесе

1️⃣Влияет на выбор подходящей БД

Архитектура хранения зависит в том числе от того, будут ли в системе преобладать чтения или записи, а также от абсолютных значений нагрузки: условно 1000 RPS или 1000000 RPS.

Для систем с преобладанием чтения (read-heavy) стоит выбирать хранилище, оптимизированное для сценариев чтения (поиск, фильтрация). Например, реляционку или подходящую NoSQL БД.

Для write-heavy систем часто хорошо подходят базы данных на основе LSM-деревьев (на мой вкус про них очень хорошо написано в кабанчике), например Cassandra.

К сожалению, я не работала с Кассандрой в проде, но однажды успешно прошла собес предложив и обосновав ее использование для проектируемой системы.

Конечно, и PostgreSQL можно использовать в write-heavy сценариях, особенно если нам важны полноценные ACID-транзакции. Вопрос скорее в масштабе нагрузки и других требованиях системы.

2️⃣Для предложения кэширования или наоборот асинхронной обработки

Для read-heavy систем, если одни и те же данные читаются многократно, часто ставят кэш перед БД, чтобы снизить нагрузку на нее.

Для write-heavy кэш, конечно, не решит проблему высокой нагрузки на запись. Тут, если нужно и система позволяет, может пригодиться асинхронный подход: между приемом данных и их обработкой можно поставить очередь. Она позволит сглаживать пики нагрузки и обрабатывать записи с той скоростью, которую выдерживает downstream-система.

Конечно, если средняя скорость поступления данных постоянно выше скорости обработки, очередь проблему не решит. Обычно на сисдизах тут появляется горизонтальное масштабирование обработчиков.

3️⃣Для обсуждения репликации

Для read-heavy систем может использоваться подход, когда запись производится на мастер (лидер), а часть чтений уходит на реплики.

Для write-heavy систем репликация тоже нужна, в первую очередь для отказоустойчивости (если лидер вышел из строя, переключились на реплику).

➡️ В жизни

На практике мы не часто сталкиваемся с построением с нуля большой системы с выбором всех технологий. Тем не менее оценка соотношения чтений и записей может пригодиться и для локальных доработок.

Например, когда проектируем новую фичу, требующую новых таблиц в БД:

Если будет heavy-read, то можно сразу задуматься об индексах. По каким полям будет частый поиск? Фильтрация? Сортировка?

Если будет heavy-write, то наоборот не стоит перегружать таблицу индексами, чтобы не замедлять запись.

Если нагрузка высокая, и данные при записи сильно отличаются от того, что нам удобно для чтения, можно поддерживать отдельную read-модель: сохранять исходные данные в одной таблице, а асинхронно наполнять другую, оптимизированную под конкретные запросы. Я на практике встречала такой подход.

А что преобладает в вашей части продукта?

🦆 - чтения
🐼 - записи
🦄 - в разных частях по-разному

Читать полностью…

Женя Янченко

Сегодня новый #женя_есть_вопрос

Женя, привет!

Сейчас я лид мобильной разработки в большом финтехе: у меня 10 прямых и около 25 непрямых репортов — в основном разработчики.

Чем дольше работаю, тем сильнее ощущение, что мобильная разработка для меня становится слишком узким направлением. В текущей роли я постоянно пересекаюсь с backend, архитектурой и другими частями продукта, а иногда сам пишу небольшие backend-фичи. При этом кажется, что рынок для backend-разработчиков существенно шире, чем для mobile.

Из-за этого начал всерьёз рассматривать переход из mobile в backend.

Но здесь есть развилка. Можно попробовать сделать переход внутри текущей компании, однако это, скорее всего, означает заметную потерю в деньгах и достаточно большой риск: на первых квартальных ревью отсутствие production-опыта в backend почти наверняка будет восприниматься как снижение уровня. При этом формального процесса перехода у нас нет — всё достаточно неформально, и позиции T-shaped инженера тоже не существует.

Второй вариант — продолжать развиваться в текущем направлении, а backend постепенно осваивать самостоятельно и уже потом искать внешнюю позицию. Но тогда возникает вопрос, насколько разумно фактически начинать карьерный трек заново после нескольких лет роста в leadership.

Как бы вы смотрели на такую ситуацию? Насколько оправдан переход из mobile в backend на этом этапе карьеры, и какой путь вы бы выбрали: внутренний переход с временным откатом по уровню/деньгам или подготовку к внешнему переходу без потери текущего уровня?

Спасибо

Читать полностью…

Женя Янченко

➡️ Keyset-пагинация (seek-метод)

Если при OFFSET мы говорим БД:

"пропусти первые 70 000 записей и дай следующие 500",


то при Keyset говорим:
"дай следующие 500 после той записи, на которой мы закончили прошлый раз".


То есть выборка опирается на уникальное поле последнего элемент предыдущей страницы.

Упрощенный пример:

SELECT ...
FROM payments
WHERE id > 70000
ORDER BY id
LIMIT 500;


При таком запросе время обработки не увеличивается пропорционально номеру запрашиваемой страницы, потому что базе не нужно проходить записи предыдущих страниц, она сразу начинает выборку с указанной строки.

Для этого на ключевом поле (в упрощенном примере - это id) должен быть индекс. В примере предполагается, что id упорядочены.


Часто используется сортировка по дате создания и id:

SELECT ...
FROM payments
WHERE (created_at, id) < ('2026-09-08 12:30:00', 70000)
ORDER BY created_at DESC, id DESC
LIMIT 500;


Здесь для эффективной работы нужен составной индекс по (created_at, id).

Также keyset решает проблему с дублирующимися записями в следующем ответе, которая возникает в случае вставки новых записей между запросами.


💭 Что в случае keyset пагинации передает фронт

Для простого случая с сортировкой только по id:

GET /payments?limit=500&lastId=1000


Упрощенный ответ бэка:
{
"items": [...],
"lastId": 1500 //если идем по возрастанию
}


Keyset-пагинация — это способ выбрать следующую страницу.

Cursor-based пагинация — это подход к реализации API, в котором клиент получает некий курсор и возвращает его серверу.

Бэк берет нужные поля, упаковывает их в ДТО, кодирует его (например Base64URL, а при необходимости подписывает или шифрует) и отдает:

{
"items": [...],
"cursor": "eyJpZCI6ODQyMX0"
}


Фронт в следующий запрос прикладывает полученный курсор:
GET /payments?limit=500&cursor=eyJpZCI6ODQyMX0


Бэк его раскодирует и действует согласно своей логике.

Это удобно тем, что бэк может поменять поля в ДТО по своему усмотрению, и это не потребует никаких доработок от фронта, ему как приходила строка, так и приходит. Правда, может понадобиться поддержать обратную совместимость со старым курсором.


С помощью keyset-пагинации нельзя реализовать переход на конкретную страницу, но зато удобно делать бесконечный скролл 📱


Плюсы:

➕ Стабильная производительность даже при миллионах строк при подходящем индексе. Нет замедления по мере продвижения по списку.

➕ Нет проблем со сдвигами, если данные добавляются

➕ Удобно для бесконечного скролла


В случае OFFSET пагинации на UI часто показывают общее количество страниц. Для получения totalElements обычно выполняют дополнительный SELECT COUNT(*).

Если мы реализовали keyset-пагинацию и хотим в ответе бэка сообщить, есть ли еще данные или это конец списка, то это можно реализовать без дополнительных запросов в БД.

Просто делаем исходный запрос с LIMIT на 1 больше:

... LIMIT 501;

Если вернулась 501 строка, то отвечаем:
{
"items": [...], // тут запрошенные 500
"hasNext": true // есть еще записи, запрашивайте
}

Если вернулось меньше 501 строки, то отвечаем:
{
"items": [...], // тут запрошенные 500
"hasNext": false // больше записей нет
}


Но у keyset-пагинации есть и минусы.

При произвольных сортировках преимущество keyset может сильно уменьшиться. Keyset опирается на индекс, а мы не можем создавать индексы на все комбинации полей.

Поэтому для keyset API часто заранее ограничивают допустимые варианты сортировки и создают индексы под несколько востребованных сценариев. Да и NULL в полях сортировки усложняет keyset.

Есть библиотеки, которые поддерживают keyset-пагинацию для разных языков.
Можно написать свою обертку или конкретную функцию для таких случаев.

Подытожим минусы:

➖ сложнее реализация
➖ нельзя сделать переход на конкретную страницу
➖ для эффективной работы ограничивают варианты сортировки

Когда использовать Keyset-пагинацию:

✅ на UI бесконечный скролл

✅ список часто пополняется новыми записями

✅ для API c большими коллекциями, где клиент последовательно выгружает данные и ему не нужен переход сразу на страницу 456

Читать полностью…

Женя Янченко

Давайте разбираться.

Мне кажется, проблема тут не столько в системном аналитике, сколько в лиде. Насколько я поняла, лид сейчас не хочет отстаивать ваши интересы и привлекать лида аналитики к решению вопроса.

Возможно, у аналитика действительно не хватает опыта или знания проекта, но специалиста можно вырастить. Лид аналитики может выделить наставника, который будет помогать и ревьюить, может порекомендовать какие-то обучающие материалы, может договориться о помощи от разрабов и QA, если не хватает знаний проекта. Да просто может тактично обозначить человеку, что такая проблема есть. Возможно, ваша СА уделяет много внимания бизнес-части и не подозревает, что есть пробелы в технической части.

Тем не менее, не зная подробностей, не рискну предлагать разговаривать с аналитиком. Люди разные, у тебя накопилось недовольство, как бы это не привело к конфликту. Вместо этого предлагаю зайти через изменение процесса.

Ты не написала, есть ли у вас грумминг, буду исходить из того, что есть. Если нет, предлагай его внедрить.

Нужно на ретро или другой встрече попросить готовить постановку хотя бы за день до грумминга, чтобы разработчики могли ознакомиться заранее.

До грумминга внимательно читай спеку, ищи пропущенные части, а на грумминге обо всем этом спрашивай. Не конфликтуй! Без претензий, просто задавай вопросы. Привлекай других разрабов к обсуждению, правьте связи в БД, добавляйте эндпойты прямо на созвоне. Подробности СА может расписать потом, если требуется, главное сами новые задачи не потерять.

Если у вас разрешены сторонние ИИ, можно отдавать спеку на ревью кодексу или клоду, чтобы он накидал вопросов с опорой на кодовую базу. Он может проверить существующие эндпойнты и найти те, которые затронет новая фича.

Еще привлекай QA к анализу спеки заранее. Если тестировщик читает постановку, когда код уже готов, то даже с пробелами в спеке он составит свое представление о функциональности и пойдет тестить, что там разрабы сделали.
А если тестировщик использует shift-left: проверяет специфицикации и пишет тест-кейсы еще по постановке, то он может задать даже больше вопросов, чем разраб. Ведь QA знают проект лучше всех.
Проси QA посмотреть спеку до грумминга, задавай ему/ей вопросы, пробуй применить его экспертизу по продукту к этой задаче заранее.

Да, это отнимет у тебя время на подготовку к груммингу, но зато снимет большую часть срочных доработок потом. Плюс такое поведение покажет тебя ответственным и вовлеченным сотрудником. Если на грумминг ходит ваш менеджер, он это заметит. Это может даже привести к твоему повышению.

А еще возможно, что такие обсуждения помогут вашему аналитику прокачаться. Если проблема не в том, что человек просто забивает, а в недостатке опыта, то она может заметить свои частые ошибки, почитать статьи про них и вырасти как специалист.

📍Задать свой вопрос можно здесь: https://forms.gle/SPE6NEALG9vcnF3s7

Ребят, что бы вы рекомендовали в такой ситуации?

Читать полностью…

Женя Янченко

В комментах под одним из постов развернулось обсуждение того, что считать микросервисом.

Еще в начале карьеры разработчика я услышала и запомнила два забавных определения:

Если над сервисом работает команда, которую можно накормить всего 2 пиццами, то это микросервис

🍕🍕

Логика микросервиса должна полностью умещаться в голове разработчика.


Это сложно назвать определением, хотя про логику замечание валидное 😃

Со временем я сформулировала для себя такие свойства:
🔴сфокусированность на отдельной бизнес-задаче
🔴возможность реализации разных нефункциональных требований
Например, у микросервиса обработки изображений могут отличаться требования к ресурсам по сравнению с сервисом каталога товаров.

Давайте посмотрим, какие определения дают известные авторы:

➡️ Крис Ричардсон (автор книги "Паттерны микросервисов")

Микросервисы — это сервисы, которые:
independently deployable - можно поставлять независимо (без необходимости одновременно выкатывать другие сервисы)
loosely coupled - слабо связаны друг с другом
Обычно они организованы вокруг бизнес-возможностей, а отдельный сервис часто находится в зоне ответственности одной небольшой команды.


➡️ Сэм Ньюман (автор "От монолита к микросервисам", "Создание микросервисов")

Микросервисы — независимо релизящиеся сервисы, смоделированные вокруг бизнес-домена.


➡️ Влад Хононов (автор книги "Изучаем DDD")

У него есть интересная статья про баланс сложности в распределенных системах.

Локальная сложность — сложность внутри отдельного сервиса
Глобальная сложность — сложность связей и зависимостей между сервисами

Если всё поместить в один компонент, глобальная сложность почти исчезает, зато может вырасти локальная. Если нарезать систему слишком мелко, отдельные сервисы могут стать проще, но количество взаимодействий и зависимостей между ними резко вырастет.

Это то, о чем иногда рассказывают в докладах бигтехов: когда сделали слишком много микросервисов и связей между ними, так что систему стало трудно развивать.

Хононов предлагает необычный взгляд:

Микросервис — это сервис с маленьким публичным интерфейсом.


Условно, сервис может содержать 100 тысяч строк кода, но наружу выставлять буквально несколько высокоуровневых операций:

- createOrder()
- cancelOrder()
- getOrder()

Поэтому прямой доступ одного сервиса в БД другого сервиса — антипаттерн: база фактически становится огромным публичным интерфейсом.


➡️ Отличия от монолита

Одно из ключевых отличий микросервисной архитектуры от монолита — независимость развертывания, а не размер кодовой базы. Об этом пишут и Ричардсон, и Фаулер, и Ньюман.

Монолит может быть модульным и хорошо спроектированным, но он деплоится всегда целиком. А микросервисы деплоятся отдельно.

При этом если наши микросервисы сильно связаны друг с другом, и релиз выглядит так:

Order v5 требует Payment v7
Payment v7 требует Customer v4,
поэтому вынуждены выкатывать всё вместе

то Ньюман и Фаулер называют это распределенным монолитом.

Кто сталкивался, тот сразу вспомнит такие релизы 😂

Нашла еще несколько статей, и во всех можно проследить повторение нескольких вещей:
🔴независимость изменений и деплоя
🔴слабая связанность
🔴осмысленная бизнес-граница

В итоге, если спросят, что такое микросервис, я бы ответила как-то так:

"Микросервис реализует ограниченную часть бизнес-домена. В идеале он должен быть слабо связан с другими сервисами, развиваться и релизиться независимо от них.
В микросервисной архитектуре нужно следить не только за сложностью каждого отдельного сервиса, но и за сложностью связей между ними".

Забавно, что на собесах мне такой вопрос ни разу не задавали. А вас спрашивали, что такое микросервис?

Читать полностью…

Женя Янченко

Куда пропали 11 заказов - развязка

В прошлом посте у нас пропали 11 заказов: у продюсера были, в Кафке были, а консьюмер почему-то их то ли не получил, то ли получил и не сохранил, не записав ничего в логи. А потом снова стал нормально сохранять.

В чем же было дело?

Спасибо, что предлагали варианты в комментариях к первому посту! Были интересные мысли и правильные догадки 🔥

Конечно, дело было в свежем релизе 😅 Несмотря на то, что он прошел успешно, и в нем не менялась ни бизнес-логика, ни явные настройки консьюмера, он содержал рефакторинг с делением на два микросервиса и изменением имен микросервисов. Имя микросервиса, в котором был и остался консьюмер, изменилось.
А имя консьюмер-группы group.id этого консьюмера в блоке настроек было задано не конкретной строкой, а как переменная, ссылающася на имя микросервиса.


group.id = ${APPLICATION_NAME}


Этот код не меняли, но так как микросервис при разделении переименовали, и значение APPLICATION_NAME изменилось в конфигах, то и group.id консьюмера изменился.

А значит для Kafka это стала совершенно новая consumer group, у которой ещё нет сохранённых оффсетов, то есть нет закладки, откуда этому консьюмеру начинать читать.
В этом случае для определения, откуда начать читать, используется значение настройки auto.offset.reset

Можно провести аналогию: это как вас добавили в чат, видна ли вам история этого чата.

🟢 earliest - читать с самого раннего доступного сообщения (там допустим сообщения 7-дневной давности)

В нашей аналогии это вы видите историю чата.

🟢 latest - начать читать свежепоступающие сообщения

Вы не видите историю чата до момента вашего добавления в него, только новые сообщения с момента вашего вступления.

Это значение по-умолчанию, оно и было у консьюмера.

🟢 by_duration - появилась в Kafka 4.0. Позволяет начать читать сообщения за определённый период до текущего момента.

Как если бы вам открыли не всю историю чата, а например за час до вашего подключения.

Этот вариант хорошо подошел бы для ситуации, но тогда его еще не было. Да и причина ситуации была в том, что просто не заметили, что имя группы тоже изменилось.

🟢 none - выбросить исключение, если нет сохраненного оффсета.

Когда проходил релиз с рефакторингом консьюмер на небольшое время выключили, за это время в топик нападали новые сообщения. А после релиза консьюмер включился в микросервисе с другим именем и начал работу как новая consumer-group с настройкой начать чтение со свежепоступающих сообщений (auto.offset.reset: latest), пропустив таким образом нападавшие за время отсуствия консьюмера заказы. При этом сама Kafka ничего не потеряла.

---

Если хотите разобраться с Кафкой подробнее, узнать то, что нужно знать, чтобы успешно справляться с рабочими задачами, то приходите на мой курс "Кафка для самых маленьких". Я бы сама хотела пройти такой курс, когда начинала работать с Кафкой, но тогда его не было, а теперь есть 🤗️️️️️️

Старт: 9 ноября 2026

🔗 САЙТ КУРСА
🔗 САЙТ КУРСА
🔗 САЙТ КУРСА

Читать полностью…

Женя Янченко

Как я стала разработчиком

Еще до разработки я работала менеджером проектов. Причем у меня даже была карьера: я выросла до лида проджектов в аутсорсе, прошла пару обучений на PM и даже подумывала о сертификации PMP. Но в какой-то момент меня стало тяготить то, что я ничего не могу делать своими руками. Ну типа вот дизайнер - он делает макеты, разрабы - пишут код, они умеют что-то конкретное. А мой обычный список дел был: "подготовить план спринта для обсуждения", "пропушить то-то", "договориться с заказчиком ", "сделать презентацию" и т.д., и мне казалось, что уметь тут особо нечего.

Сейчас я понимаю, что управлять проектом так, чтобы были довольны и бизнес-заказчик, и твой руководитель, и команда разработки — это совсем не просто, и одной ответственности здесь мало, нужные навыки приобретаются с опытом. Но тогда меня одолевал синдром самозванца.

Я смотрела на ребят-разрабов, которые получали от меня постановку, а выдавали реально работающее ПО, и думала, что хочу так же. Хочу уметь что-то создавать, а не только организовывать.

Но пока я думала эти мысли и сомневалась в себе, мне предложили перейти в крупную компанию с повышением зарплаты. Я решила, что мое то ли выгорание, то ли непонятно что пройдет со сменой работы, и согласилась.

Фотка к посту как раз из офиса той компании, 2016 год.


В крупной компании работа была бюрократизирована намного больше, и меня накрыло с новой силой. Дополнительно меня добивала необходимость ездить с утра в переполненном общественном транспорте. Сейчас мне даже неловко рассказывать об этом, но реально я ехала, придавленная толпой к двери или окну (как повезет 😂️️️️️️), и думала, как бы перестать оказываться в этой ситуации.

Так я пришла к идее поискать работу на удаленке. Шел 2016, я жила в регионе, и вакансий проджектов в ИТ было 2-3 штуки на весь город, естественно без всякой удаленки. Я стала смотреть вакансии в других городах, и оказалось, что удаленные вакансии вполне себе есть, но не для менеджеров, а для разработчиков.

Тут-то мой пазл и сложился. С одной стороны, желание разбираться в ПО и уметь делать его самой, с другой — возможность делать это на удаленке.

По образованию я инженер связи, и у нас в универе было и программирование на C++, и на микроконтроллерах, и даже на ассемблере. Также изучали базы данных, основы криптографии и др.

Но некоторые руководители из ИТ, с которыми я успела столкнуться за свою карьеру менеджера, успели вселить мне мысль, что девочка не может быть разрабом. Масла в огонь подливал Хабр тех времен: он создавал впечатление, что если ты не писал на паскале с 3 класса, то в программировании тебе делать нечего.

Мне пришлось прямо сильно поработать с собой (как сейчас говорят: со своими установками), чтобы перестать бояться и разрешить себе попробовать. К сожалению, тогда в интернете не было столько положительно мотивирующих историй о смене профессии. Но я все же решилась.

Помню, как читала свою первую книгу по разработке - Head First Java все в том же транспорте, так же прижатая к окну. Только тогда я уже не замечала ни толпы, ни происходящего вокруг, меня уносил новый мир.

Конечно, пришлось учиться очень много: не было такого, чтобы за знание синтаксиса языка брали, уже и веб-разработку надо было знать и работу с БД. Но по моим ощущениям, когда я уже устроилась на работу разработчиком, учиться пришлось еще больше 😄️️

Тогда еще было распространено сдавать экзамены на сертификаты Oracle. В моем городе не было экзаменационного центра, поэтому мы с мужем и годовалым сыном ездили в Воронеж, чтобы я могла сдать экзамен.

А еще помню, как нашла свою первую удаленку и обустраивала рабочее место на лоджии.

Сейчас кажется, что это было в прошлой жизни.

Забавно, что спустя годы я стала куда лучше понимать, сколько полезных навыков мне дала профессия проджекта. Немного жаль, что тогда я упорно считала, будто ничего особенного не умею, но с другой стороны, я рада, что тогда решилась сменить профессию.

А как вы пришли в текущую профессию?

🦄 — учился на нее в универе
🦆 — менял профессию внутри ИТ
🐼 — пришел из другой сферы

Читать полностью…

Женя Янченко

Давайте сегодня обсудим кейс: как пережить пик запросов. Кейс и для собеса и для жизни 😃

Представьте ситуацию: вы работаете над образовательной платформой, на которой студенты проходят курсы и выполняют задания.

Есть два сервиса:
• сервис проверки заданий, который проверяет ответы студентов и выставляет оценки - им занимается другая команда
• сервис результатов и статистики - это наш сервис.

Сервис проверки заданий отправляет вашему сервису HTTP-запрос с оценкой студента. Наш сервис должен сохранить оценку в PostgreSQL и перерассчитать средний балл по студенту.
В обычное время это занимает 300 мс: сохраняем оценку, перерассчитываем и сохраняем средний балл и отвечаем 200 OK.

У каждой оценки есть уникальный UUID, в БД на это поле установлен unique constraint, поэтому повторные запросы легко отличить от новых и исключить дублирование.

Недавно вы реализовали новые фичи: теперь сервис не просто считает средний балл, а делает более широкую сводную статистику с рейтингами студентов. Для этого он выбирает данные из БД и выполняет довольно много вычислений в памяти приложения. Теперь обработка каждой оценки с расчетом всего этого добра занимает по несколько секунд, но в таймаут укладываемся.

Наступает последний день сдачи и куча студентов начинает сдавать работы на проверку. Количество запросов от сервиса проверок резко возрастает. Наш сервис перегружен, обработка запросов растягивается. Плюс сервис проверок ретраит запросы. Хотя дубли мы отсекаем до основных расчетов, ретраи все равно создают дополнительную нагрузку.

Образовательная платформа активно развивается, подобные пики будут повторяться, нужно что-то предпринимать.

Как можно решить эту проблему?

Обычно для ограничения количества запросов используют Rate Limiter, но это защитный механизм. Сюда же Circuit Breaker на вызывающей стороне. Это отличные паттерны, они помогают уменьшить нагрузку на наш сервис, но оставляют проблему вызывающей стороне. Здесь же хочу разобрать подходы, как справляться с нагрузкой, а не отбиваться от нее.

Варианты:

1️⃣Вертикальное или горизонтальное масштабирование

Если от нагрузки страдает сам сервис, а не БД, то можем выделить ему больше ресурсов или поднять несколько инстансов.

Плюсы:
➕ просто, если есть инфраструктура, не нужны добработки

Минусы:
➖ дополнительные ресурсы
➖ нагрузка на БД может вырасти от увеличения параллельных запросов


2️⃣При поступлении запроса только сохранять оценку в БД, а статистику пересчитывать отдельно фоновой джобой по расписанию.

Но нужно продумать, как ограничивать нагрузку от фоновой джобы, чтобы ее вычисления не нагружали сервис дополнительно, мешая приему новых оценок.

Плюсы:
➕БД у нас уже есть, дополнительная инфраструктура не требуется

Минусы:
➖ статистика обновляется с задержкой, а не сразу после получения студентом новой оценки
➖нужно учесть, чтобы несколько экземпляров джобы не выполняли один и тот же пересчёт одновременно

3️⃣Договориться с сервисом проверки, чтобы вместо синхронных запросов, слали нам сообщения в очередь сообщений (или выполняющую ее роль Кафку).

Плюсы:
➕убираем зависимость сервиса проверки от скорости нашей обработки
➕ очередь буферизирует пиковую нагрузку

Минусы:
➖ работа для другой команды
➖ новая инфраструктура
➖ следить за длительностью обработки сообщения и общим лагом обработки

Если сообщения поступают быстрее, чем мы успеваем обрабатывать, лаг обработки и задержка статистики будут расти и расти.

4️⃣Или если коллеги пока не готовы, мы можем сделать себе внутреннюю очередь: при получении HTTP-запроса сразу отправляем событие в Кафку и возвращаем 200 OK после подтверждения успешной записи. В БД на этом этапе ничего не сохраняем, поэтому нам не требуется Transactional Outbox для согласования двух записей.

Наш консьюмер читает события из Кафки, сохраняет оценки в БД и делает нужные расчеты с дедупликацией по UUID.

Плюсы:
➕ быстро отвечаем сервису проверки
➕ очередь буферизирует пиковую нагрузку

Минусы:
➖ новая инфраструктура
➖ следить за длительностью обработки сообщения и общим лагом обработки.

Какой вариант вы бы выбрали?

Читать полностью…

Женя Янченко

Продолжение истории 2003 года

В системе было предусмотрено резервирование.
Когда в 14:41 отказал основной сервер, система автоматически переключилась на резервный. Но на резервный сервер переехали те же приложения, включая неисправный обработчик предупреждений.

В 14:54 отказал и резервный сервер.

IT-специалисты перезапустили основной сервер, проверили, что он работает и нужные процессы запущены, вот только обработчик предупреждений по-прежнему не выполнял свою функцию.

В 15:05 отключилась одна из важных высоковольтных линий, затем ещё несколько.

В диспетчерской Джима раздался телефонный звонок. В трубке звенел взволнованный голос коллеги из American Electric Power. Он сообщал об отключении линии электропередачи. Но у Джима на экране никаких предупреждений не было. Более того, это был уже второй звонок по поводу той же линии. Однако Джим и его коллеги, не зная о неисправности системы, не смогли правильно оценить ситуацию.

В 16:05 отключилась ещё одна критически важная линия. Началось каскадное отключение энергосистемы. В течение нескольких минут без электричества остались огромные территории США и Канады и около 50 миллионов человек. В отдельных районах электричество не могли восстановить до 4-х суток.

➡️ Разбор аварии

После аварии инженеры GE начали искать причину. Код модуля обработки предупреждений содержал около миллиона строк C/C++, а вся система — порядка четырёх миллионов.

По словам представителей GE, к тому моменту программное обеспечение накопило суммарно более трех миллионов часов эксплуатации у разных клиентов без проявления именно этого дефекта.

Разработчикам потребовалось около восьми недель, чтобы воспроизвести ошибку. Для этого они намеренно замедляли выполнение программы, добавляя задержки в код и подавая на вход разные последовательности событий.

Так им удалось воспроизвести состоние гонки и выяснить, почему обработчик уходил в бесконечный цикл.

Официальное расследование выявило целый набор причин аварии: проблемы с обслуживанием ЛЭП, недостаточную осведомленность диспетчеров, ошибки в организации работы и другие факторы.


➡️ Выводы:

1️⃣Даже годами работающий код может содержать ошибку, для проявления которой нужна редкая последовательность событий и совпадение по времени.

2️⃣Наличие резервного сервера не гарантирует устойчивости системы, если оба сервера подвержены одному программному дефекту.

3️⃣Живое приложение не обязательно означает правильно работающее приложение. Для критичных модулей желательно предусмотреть возможность проверять не только состояние, но и способность выполнять бизнес-функцию.

У меня об этом есть и своя история, к счастью не такая масштабная, когда формирование отчета зависало без каких-либо ошибок в логах.

А были ли у вас ситуации, когда алерты не приходили?

Читать полностью…

Женя Янченко

Redis vs Valkey

По моим ощущениям про Valkey стали говорить всё больше, решила сделать небольшой обзорный пост без погружения в технические тонкости работы кластеров.

➡️ Что это такое

Redis — это хранилище типа "ключ-значение", которое держит данные в оперативной памяти. Благодаря этому он работает очень быстро, поэтому обычно используется как кэш.

Valkey — изначально форк Redis и его прямой конкурент.

При этом и Redis, и Valkey умеют сохранять данные на диск, реплицироваться и работать кластером. Да и других фич много, например:

🔴TTL - можно задать время жизни ключа, после которого он автоматически удалится (например, чтобы хранить смс-код 60 секунд)

🔴Атомарный счетчик - можно атомарно увеличить или уменьшить значение (например, чтобы посчитать количество запросов).

🔴Sorted Set - множество элементов с числовым score, которое автоматически поддерживается отсортированным (например, удобно формировать оперативный рейтинг игроков).

➡️ Немного истории

До 2024 года Redis распостранялся под лицензией, которая позволяла брать код, модифицировать и даже строить на его основе коммерческие Redis-as-a-Service (так делали AWS, Google Cloud и др.)

В 2024 Redis Ltd. предложили две новые лицензии:

1️⃣Redis можно бесплатно использовать внутри компаний, но нельзя делать конкурирующий managed service

2️⃣Можно предоставлять Redis-as-a-Service, но возникают серьёзные требования по открытию исходников инфраструктуры этого сервиса

Для компаний, которые просто использовали Redis как кэш внутри своих продуктов, почти ничего не изменилось. А вот облачным провайдерам стало сложнее продавать Redis-as-a-Service.

На основе версии под старой лицензией (Redis 7.2.4) сделали форк (независимую копию) и назвали его Valkey.

Valkey стал активно развиваться параллельно Redis.

Он имеет опенсорсную лицению, на ней можно делать облачные managed service.

Redis в 2025 добавил третий вариант лицензии со своими особенностями.

Valkey сохранил совместимость с Redis 7.2 и более ранними версиями, поэтому со старых Redis можно переехать без изменения кода. В новых версиях продукты постепенно расходятся.

➡️ Базовое сравнение

▶️Лицензия

У Valkey: BSD-3-Clause.

У Redis: RSALv2, SSPLv1 или AGPLv3.

Для обычной компании, которая использует Redis внутри своего продукта, особо разницы нет. Если же вы делаете Redis-as-a-Service, то лицензия Valkey проще.

▶️Функциональность

Основная функциональность (кэширование, sorted sets, TTL и др.) есть и там и там.

Redis активно развивает специализированные фичи:

full-text search
vector search
time series
Bloom filter
top-k

Часть из них есть в модулях Valkey.

Сравнение по фичам на сайте Redis

▶️Производительность

Valkey сильно оптимизирует производительность и при сравнении с Redis 7 обычно выигрывает, а вот с Redis 8 - не всегда. Насколько я разобралась, все зависит от операции и характера нагрузки.

Ссылки на разные бенчмарки приложу в комментах.

▶️Однопоточность и использование остальных ядер

У Redis есть особенность: выполнение команд идет последовательно в одном потоке.

Условно для наглядности:

CPU
core 1 ██████████
core 2 ██
core 3 ██
core 4 ██


Основной поток занят выполнением команд, остальные ядра используются меньше - на них выполняются фоновые задачи и часть I/O.

Valkey особенно активно переносит такую вспомогательную работу из основного потока.

Например, в этой статье приводится пример с TLS handshake: криптографические вычисления при установке защищеного соединения выносятся в дополнительный поток на другое ядро.

Статья ангажирована в пользу Valkey, но в ней интересно описаны технические аспекты, которые относятся к эксплуатации больших production-кластеров (репликация, миграция шардов, дефрагменация памяти и др.)

➡️ Выводы

Для базового кэширования Redis и Valkey очень похожи: основные структуры данных, TTL, кластерный режим есть у обоих.

По производительности однозначного победителя нет: Valkey выигрывает в одних сценариях, новый Redis 8 - в других.

Для разработки managed service (продавать кэш как сервис) лицензия Valkey сильно проще.

Читать полностью…

Женя Янченко

Недавно знакомый разработчик ходил на собеседование, и там его спросили:

"Что такое идемпотентность?"


Мне казалось, что это очень частый вопрос для системных аналитиков, а вот разрабов как будто реже само определение спрашивают.

Хотя знакомый понимал смысл термина, готовой формулировки у него не было, начал объяснять, немного запутался. Ему повезло с собеседующим, тот послушал объяснение и понял, что человек ориентируется в теме, даже помог наводящими вопросами.

Но я тоже сталкивалась, когда на этот вопрос отвечают не совсем то, что нужно. Давайте разберемся с этим определением.

❌ Идемпотентность - это когда при повторных запросах приходит один и тот же ответ.

Нет, дело тут не в ответах, они могут отличаться.

Классический пример с DELETE: первый запрос на удаление может вернуть 200, а следующий - 404, при этом метод остается идемпотентным.

Или другой пример:

Идемпотентный GET не обязан каждый раз возвращать одинаковый ответ, между запросами данные в системе могли измениться (например, добавились записи в список), и второй ответ будет отличаться.

❌ Идемпотентность - это когда повторный запрос не выполняется.

Нет, повторный запрос может точно так же пройти через приложение и обратиться к БД. Идемпотентность не означает, что система обязана распознать дубль и проигнорировать его. Дедупликация - один из способов обеспечить идемпотентность, но не сама идемпотентность. Для некоторых ситуаций нужна дедупликация, а для GET-запроса, например, совершенно нормально повторно выполнить те же действия.

✅ Идемпотентность - это свойство операции, при котором многократное выполнение этой операции приводит к такому же эффекту, как и однократное выполнение.

То есть важно не то, сколько раз реально выполнился код и какие ответы мы получили, а какой эффект оказала операция.

А зачем вообще нужна идемпотентность? Почему о ней так много говорят?

Практический смысл этого свойства хорошо виден при ретраях. Например, из-за сетевого сбоя клиент не получил ответ и не знает, был ли первый запрос обработан. Если операция идемпотентна, запрос можно безопасно повторить.

Сюда же повторная доставка сообщений из Кафки, когда обработать успели, а закоммитить оффсет не успели.

POST не считается идемпотентным методом по семантике HTTP, но мы можем спроектировать и реализовать его идемпотентно, когда нужно. Уверена, многие с этим сталкивались.

Например, когда реализуем поиск с фильтрами:

POST /orders/search
{...}

Или когда генерируем на клиенте и передаем на бэк специальный уникальный идентификатор запроса - Idempotency-Key.

Когда такой запрос приходит первый раз, бэк его обрабатывает, а уникальный Idempotency-Key сохраняет. При повторном запросе с тем же Idempotency-Key бэк понимает, что это дубль, и бизнес-операцию повторять не нужно. Например, при запросе на списание платежа, при заказе такси и др.

Итого краткий ответ:

Идемпотентность — это свойство операции, при котором многократное выполнение этой операции приводит к такому же эффекту, как и однократное выполнение.

А дальше можно привести примеры идемпотентных и неидемпотентных операций, чтобы развернуть мысль.

А вас спрашивали на собесах определение идемпотентности?

Читать полностью…

Женя Янченко

В прошлой жизни, когда я была менеджером проектов, одним из первых мест работы у меня был большой телеком. И там у меня была потрясающая руководительница, к которой я могла прийти с любой проблемой, а она всегда говорила:

"Ничего страшного, сейчас разберёмся, что-нибудь придумаем"


😍😍😍

Но однажды она мне сказала:

"Ко мне ты можешь приходить с любыми вопросами. Но если ты хочешь расти дальше, то выше руководители будут другие. К ним нельзя приходить только с проблемой, они будут ждать, что ты сразу и решение предложишь, а они его или утвердят или скорректируют".


Я задумалась над её словами, начала так делать и продолжила, когда уже перешла в разработку.

Писала когда-то пост, как пыталась так подробно сформулировать вопрос лиду с вариантами решений, что сама нашла ответ.

Хотя мне везло с руководителями, и сама всегда старалась отвечать на любые вопросы ребят, встречала тех, кто исповедует подходы: "не отвечаю на то, что можно спросить у гугла" и "не отвечаю на то, что есть в документации" (особенно здорово, когда доку давно не обновляли и непонятно, как отличить, что актуально, а что нет 🙈).

В целом считаю так:

🟢 Если это вопрос в моей зоне ответственности, по которому хочется посоветоваться с техлидом, то я бы подготовила одно или несколько решений и обсудила их. Техлид может предложить свою альтернативу, но отметит, что к нему пришли "не с пустыми руками".

🟢 Если проблема находится не в зоне ответственности разработчика/аналитика, то можно прийти к своему лиду/менеджеру и просто с вопросом, как лучше поступить.

Например, если другая команда затягивает согласование контракта:

"Написала уже два письма, ходила в личку, но пока ничего не согласовали и сроков не называют. Пока продолжу их пинговать?"


И тут менеджер уже может что-то предложить, так как вопрос в его зоне ответственности.

Я бы даже сказала, что лучше не затягивать, если на своем уровне вопрос не решается. Потому что одно дело прийти с "тут назревает проблема", а другое, когда уже "пожар, сроки горят, что делать?"

Как вы считаете, имеет ли смысл заморачиваться и готовить варианты решений, если это вопрос руководителю?

Делаете ли вы так или обычно задаете вопрос, а там уже как пойдет обсуждение?

🦆 — обычно готовлю варианты решений
🐼 — обычно просто задаю вопрос

Читать полностью…

Женя Янченко

Куда пропало обращение - развязка

В прошлом посте у нас загадочно пропало обращение 58122. В БД его не было, хотя 58121 и 58123 были, а в логах нашли запись о создании:

12:41:03 Creating ticket, id=58122

и никаких следов дальнейших действий с этим id.

В комментариях к первому посту многие правильно определили, в чем была проблема 🔥

Дальше в логах этого запроса была ошибка базы:

org.postgresql.util.PSQLException: ERROR: null value in column "department_id" of relation "ticket_status_history" violates not-null constraint

Операция по добавлению нового обращения была частью транзакции:
➡️ сначала добавлялась строка в таблицу с обращениями ticket с новым id,
➡️ затем в связанную таблицу ticket_status_history тоже добавлялась первая запись для этого обращения.

Из-за бага в одном из сценариев обязательное поле в таблице ticket_status_history не заполнилось. В БД ушел запрос с null в обязательном поле. На это БД логично ответила ошибкой нарушения non-null constraint и откатила всю транзакцию:

BEGIN

INSERT INTO ticket ...
-- sequence выдала 58122

INSERT INTO ticket_status_history ...
-- ERROR

ROLLBACK



Таким образом откатилось и создание обращения с id = 58122, поэтому такой записи в БД нет.

А вот значение sequence, с помощью которой генерируются id-шники при BIGSERIAL, обратно не откатилось. Поэтому следующее успешно созданное обращение получило id = 58123.

То есть sequence выдает значение для id, INSERT выполняется, но затем вся транзакция откатывается. Строка исчезает, а номер sequence уже потрачен. Для новой вставки sequence отдаст уже следующий номер.

Получается, sequence может обеспечить выдачу новых значений, но не гарантирует отсутствие пропусков, даже если никто строки не удалял вручную.

В доке оказывается так и написано, что значения из sequence не используются повторно, чтобы избежать блокировки параллельных транзакций, получающих числа из одной и той же sequence. Причем пропуски могут появляться не только из-за rollback: например, при INSERT ... ON CONFLICT значение из sequence может быть получено еще до обнаружения конфликта. Поэтому PostgreSQL sequence не подходят как источник непрерывных последовательностей.

Ссылка на доку PostgreSQL на английском

Ссылка на доку на русском от PostgresPro


Баг, из-за которого возникал NOT NULL constraint violation, исправили. С бизнесом обсудили, стоит ли формировать номера обращений по отдельным правилам вместо использования для этого id. В тот момент сошлись, что не стоит.

А были ли у вас истории с "пропавшими" строками в БД?

Читать полностью…

Женя Янченко

🔗 Подборка постов про Кафку

Как обещала на стриме, собрала посты про Кафку в удобное оглавление:

Устройство:

🟢 Топики и партиции
🟢 Брокеры и репликация
🟢 Типы топиков
🟢 Продюсеры
🟢 Консьюмеры и консьюмер-группы
😱 История про захлебнувшихся консьюмеров
🔥 NEW: Очереди в Кафке
🟢 NEW: Diskless Topics

Паттерны:

🟣 Transactional Outbox
🟣 Dead Letter Queue
🟣 Идемпотетный консьюмер

Дополнительно:

🟡 Гарантии доставки в Кафке
🟡 Может ли нарушаться порядок сообщений
🟡 Как посчитать количество партиций
🟡 Карточки про Кафку и RabbitMQ

Читать полностью…

Женя Янченко

Сегодня стрим по Кафке в 19:00

Планируем не в формате доклада, а в формате вопрос-ответ, чтобы было веселее. Андрей будет задавать свои и ваши вопросы, попробуем смоделировать на симуляторе разные ситуации.

Приходите!
🔗 Ссылка на Timepad

Читать полностью…

Женя Янченко

Привет!

У тебя уже очень крутая карьера и хороший управленческий опыт. Лидить 10 человек напрямую и 25 косвенно в большом финтехе — это серьезный уровень 🔥

Из вопроса мне не совсем понятно, хочешь ли ты перейти в бэк и сохранить лидскую позицию или хочешь стать бэкенд-разработчиком 🤔

➡️ Если речь про переход в разработчики, то при выборе отталкивайся от того, как тебе будет комфортнее:

🔴при внутреннем переходе ты сможешь освоиться, собрать кейсы и при смене работы будешь чувствовать себя уверенно. Обсуди с руководителем, получится ли не просесть по деньгам, если ты будешь закрывать задачи?

я в свое время выбрала переход с откатом по деньгам, но не готова это рекомендовать, особенно сейчас

🔴последние годы столько людей устраиваются мидлами без опыта работы вообще, так что ты как опытный в разработке человек наверняка сможешь подготовиться и найти работу бэком сразу, если задашься такой целью. Ты еще и истории реальные на собесе расскажешь.

"насколько разумно фактически начинать карьерный трек заново после нескольких лет роста в leadership"


Зависит только от того, чем тебе хочется заниматься.

➡️ Если хочешь расти выше по карьерной лестнице и лидить не только мобильную разработку, но и всю разработку, то тебе нужен не столько опыт бэкенда, сколько опыт выстраивания процессов, работа со стейкхолдерами и возможно архитектурой.

➡️ Если речь про сохранение тимлидской позиции, то я бы предложила не откладывать поход по собесам, а наоборот максимально интенсивно подготовиться и сходить на несколько.

Конечно, не стоит выбирать техлидскую роль, где в команде будет пара джунов, а тебе придется проектировать и писать весь бэк высоконагруженной системы.

Другое дело, если вакансия предполагает управленческую направленность (тимлид). Планирование работ, организация процессов, взаимодействие с бизнесом, работа с командой - эти навыки и опыт у тебя есть. К формальной технической части (алгосы, если таковые будут, сисдиз, языковая секция) ты подготовишься. Управленческих кейсов у тебя наверняка много, архитектурные тоже вытащи из опыта, бэковые надо подсобрать.

Возможно, что ты найдешь подходящую тимлидскую позицию сразу в первый заход по собесам.

Если нет, то у тебя будет точное понимание, чего не хватило и поможет ли с этим переход в бэки на текущем месте.

Если даже ты перейдешь в разрабы, а не в лиды, то добрав технички ты быстро вырастешь снова в лиды, потому что уже обладаешь нужными навыками для этого.

➡️ Еще вариант, над которым можно подумать — это переход в архитекторы. Ты уже работаешь с архитектурой. Архитекторами становятся не только из разработчиков бэка, но и из фронтов, mobile и аналитиков, которые не пишут код, но при этом обладают опытом в проектировании систем.

С точки зрения зарплаты архитекторы не уступают лидам бэка. Тем более у тебя опыт в финтехе.

На мой взгляд переходы на зрелых этапах карьеры не страшны, потому что ты переходишь со всем своим инженерным опытом. Работа с требованиями, декомпозиция, архитектура, структуры данных, проектирование API — все эти знания остаются. Освоить придется новый стек, но не разработку как профессию заново ❤️

📍Задать свой вопрос можно здесь: https://forms.gle/SPE6NEALG9vcnF3s7

Случалось ли вам менять направление внутри ИТ? Были ли такие мысли?

Читать полностью…

Женя Янченко

Любите ли вы Кафку так, как люблю её я?

Мы с Андреем из Yet Another Analyst решили сделать коллаб — совместный стрим про Кафку.

Поговорим про распределение сообщений, консьюмер-группы, масштабирование, можно ли сломать порядок сообщений, какие гарантии доставки можно реализовать в Кафке.

Посмотрим очень классный симулятор работы Кафки!

Пишите вопросы в комментах к этому посту, постараюсь на стриме на все ответить.

🗓 Когда: в среду 16 сентября в 19:00
🔗 Ссылка на Timepad

Читать полностью…

Женя Янченко

Пагинация при работе с БД

Хочу рассказать не только базовую теорию, но и про подводные камни и практические подходы к оптимизации.

➡️ OFFSET пагинация

Это самый распространенный вариант.
С фронтенда приходит запрос вида:

GET /orders?page=3&size=20


Если считаем, что страницы нумеруются с 1 (не с 0), то для page = 3 бэк превратит это примерно в такой запрос:

SELECT ...
FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 20
OFFSET 40;


Без ORDER BY порядок строк не гарантирован, а дополнительный уникальный id пригодится на случай одинакового created_at.


Что возвращает бэк:
{
"items": [...],
"page": 3,
"size": 20,
"totalElements": 247,
"totalPages": 13
}



У OFFSET пагинации много плюсов:

➕ просто реализовать, есть встроенная реализация в некоторых фреймворках

➕ легко реализовать переход к любой странице

Например, когда книгу то читаю, то слушаю, мне бывает надо сразу перейти на страницу 134.
Или помнила, что в адмике нужная запись на 78 странице 🤪

Но у такого подхода есть и минусы.

Ключевое слово OFFSET 40 говорит базе, что нужно пропустить первые 40 записей и начать отдавать, начиная с 41-й (для 3-й страницы). Однако база не может сразу прыгнуть на 41-ю строку. Она работает так:

➡️ найти начало упорядоченной выборки
➡️ пройти первые 40 записей
➡️ отбросить их
➡️ вернуть следующие 20 записей

Тут наблюдается лишняя работа с первыми 40 записями. Чем больше номер страницы, тем больше OFFSET и тем больше времени понадобится базе на ответ — это и есть основной минус.

Из-за большего OFFSET записи с последних страниц будут отдаваться дольше, чем с первых, но иногда возможен лайфхак с разворотом сортировки.

Например, если мы показываем на UI сначала самые свежие заказы, то последняя страница заказов будет содержать самые ранние:

SELECT ...
FROM orders
ORDER BY created_at ASC, id ASC
LIMIT 20;

Оговорюсь, что такой запрос подходит для задачи "дай 20 самых ранних записей", но не является способом получить последнюю страницу классической пагинации, потому что возможно следующее несоответствие:

Всего 53 записи, размер страницы 20.
1 стр: 1-20 - 20 строк
2 стр: 21-40 - 20 строк
3 стр: 41-53 - 13 строк

ORDER BY created_at ASC LIMIT 20 вернет 20 самых старых строк вместо 13.


Проблема с OFFSET пагинацией может стать заметной, когда запрашиваются глубокие страницы: чем больше OFFSET, тем больше результатов базе приходится обработать и отбросить. Это может не проявляться на пользовательском UI с 10 страницами по 50 элементов, а вот если мы пишем публичный технический API, которым будут пользоваться соседние команды для получения каких-то больших списков, то там это может стать заметно:

SELECT ...
FROM payments
ORDER BY created_at DESC, id DESC
LIMIT 500
OFFSET 70000;

😱

Еще OFFSET пагинация может быть не очень удобна, когда список часто меняется.

Вообще это довольно редкий кейс, но давайте рассмотрим на всякий случай.

➡️ В БД лежат новости с id = 1 ... 100

➡️ Пользователь запросил 10 самых свежих новостей

➡️ Бэк отдал последние новости с id = 100, 99 ... 92, 91

➡️ Пока пользователь читал новости, модератор добавил две новых записи, они получили id = 101 и id = 102

➡️ Пользователь запросил следующую страницу более старых новостей

➡️ При выполнении OFFSET 10 БД отбросила записи с id = 102 ... 93 и отдала новости с id = 92, 91, 90 ...

➡️ Результат: пользователь дважды увидел новости с id = 92 и 91

Итого минусы:

➖ замедление ответа по мере увеличения номера запрашиваемой страницы
➖ нестабильность ответа, когда список меняется

Когда использовать OFFSET-пагинацию:

✅ пользователи обычно работают с первыми страницами списка, глубокие переходы редки (или количество строк в таблице небольшое, если очень грубо —несколько тысяч строк)

✅ список записей обычно не меняются, пока пользователь их смотрит

✅ на UI нужен блок с привычной пагинацией и переходом на конкретную страницу

Читать полностью…

Женя Янченко

Пятничный привет! Меня не было неделю — навалилось много дел, в том числе связанных с тем, что уже второй год 1 сентября для меня снова День знаний 😄
Вожу в школу своего второклассника.
Сегодня я уже немного выдохнула, надеюсь дальше продолжать в обычном режиме.

В рубрику #женя_есть_вопрос прислали непростой вопрос про ситуацию на работе:

Привет, очень надеюсь на твой совет.

У нас в команде есть бэки, фронты, бизнес-аналитик и два системных. Постановка по задачам зона ответственности системных аналитиков. Я работаю в разработке. Одна из аналитиков всё время недоописывает все входящие в доработку таски. Если нужно доработать больше 2-3 ручек, то наверняка какую-то она в постановке пропустит. Получается DoR есть, но только формально, фактически многие детали не учитываются. По технической части тоже не все гладко, например, путаница в связях one-to-many и many-to-many, но это не так критично.

Системные аналитики декомпозируют свои истории, и задачи раздают разным бэкендерам параллельно. Так как часть задач не описана, они не поступают в разработку. Их приходится потом доделывать тому, кто это обнаружит когда будет заниматься другой задачей, или тому, на кого тестировщик решит завести баг. Столкнувшись с подобными срочными доделками, я начала сама перед разработкой созваниваться с аналитиками и вытягивать из них полную постановку, а потом ещё синхронизироваться с другими разработчиками, которые работают над этой же историей. Но всё это занимает довольно много времени, которого никто дополнительно не даёт.

Пробовала разговаривать со своим лидом, он сказал, что проблему видит, но ссориться с лидом аналитиков, который всегда прикрывает своих, не хочет. Разговор прошел в пустую. С коллегами тоже говорила. Фронтендеры не делают по аналитике, а ждут наш бэк и делают по нему, так что им некритично качество постановки, главное, что макеты дали и бэк готов. Другие бэки согласны, что проблема есть, но похоже ничего делать с этим они не хотят.

Может ты предложишь, что можно сделать?

Читать полностью…

Женя Янченко

Прочитала на Хабре интересный анализ рынка труда 2026 года.

Один из основных выводов, который сильно перекликается с моими наблюдениями:

Российский IT‑найм — это несколько гигантов.

1% работодателей публикует 29% всех вакансий. Верхние ~40% компаний закрывают более 80% рынка.


Топ-5 самых активных работодателей:

1️⃣Сбер (с большим отрывом)
2️⃣Яндекс
3️⃣VK
4️⃣Т-Банк
5️⃣Wildberries

Еще из интересного:

Аналитика востребована больше разработки, но при этом найм аналитиков данных монополизирован сильнее остальных профессий (мне кажется, логично: у бигтехов много данных и направлений их анализа, а значит именно им нужно больше аналитиков данных).

Мне не очень привычно, когда "Аналитику" объединяют в одно направление, ведь аналитик данных и системный аналитик — это заметно отличающиеся профессии на мой взгляд. Но в статье есть и раздельный анализ по некоторым пунктам.

Про уровень зарплат.

Согласно проведенному исследованию:

Размер работодателя не связан с уровнем зарплат — гиганты платят не больше остальных.


В первой части исследования автор писал, что зарплату довольно часто не указывают, поэтому это вывод только по тем вакансиям, где зарплату указали.

С другой стороны, если 40% компаний закрывают больше 80% вакансий, то уровень зарплаты в этих 80% вакансий — это же и есть «рыночный» уровень?

Про географию.

Зарплаты для аналитиков и менеджмента в Москве существенно выше. Для разработки удаленка все еще неплохо себя чувствует.

У меня было ощущение, что многие вакансии — это гибрид из Москвы, но исследование показывает, что удаленка жива, и зарплаты там даже выше. При этом, удивительный факт:

среди удалённых вакансий 59% живут вне hh против 14% у офисных


То есть ищем удаленку не только на hh, товарищи.

Ссылка на статью

Поделитесь, пожалуйста, вашими впечатлениями от рынка 2026:

🎉 — успешно сменил работу в этом году
👀 — сейчас в поиске или скоро планирую
🦆 — ну его нафиг, пока не планирую менять работу

Читать полностью…
Subscribe to a channel