Женя Янченко


Kanal geosi va tili: Rossiya, Ruscha
Toifa: Martaba


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

Bog‘liq kanallar

Kanal geosi va tili
Rossiya, Ruscha
Toifa
Martaba
Statistika
Postlar filtri


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

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

Вы разрабатываете сервис приема заказов 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 секунд 😅


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

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


😍😍😍

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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


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

В прошлом посте у нас загадочно пропало обращение 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. В тот момент сошлись, что не стоит.

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


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

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

"В выгрузке потерялось обращение 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? Удалил кто-то из тех, у кого был доступ?

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

2k 0 6 15 52


2.1k 2 152 5 77

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

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

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


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

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

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


Для топика настроено retention.ms=86400000 (24 часа). Консьюмер больше суток не читал сообщения. Что произойдет со старыми сообщениями?
So‘rovnoma
  •   Они могут могут быть удалены независимо от того, прочитал их кто-то или нет
  •   Кафка будет хранить их, пока хотя бы одна консьюмер-группа не прочитает их
  •   Кафка будет хранить их дольше 24 часов, если не исчерпан лимит места на диске (retention.bytes)
129 ta ovoz


Consumer получил сообщение с offset 42, обработал его, но упал до commit offset. После рестарта консьюмера наиболее вероятно:
So‘rovnoma
  •   Kafka автоматически удалит сообщение 42
  •   Сообщение 42 останется в логе, но консьюмер начнет обработку с сообщения 43
  •   Сообщение 42 будет обработано повторно
176 ta ovoz


Топик имеет 10 партиций. В одной consumer group запущено 15 консьюмеров. Сколько консьюмеров одновременно смогут получать сообщения?
So‘rovnoma
  •   5
  •   10
  •   15
  •   Зависит от replication factor
212 ta ovoz


Соотношение чтений к записям (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-модель: сохранять исходные данные в одной таблице, а асинхронно наполнять другую, оптимизированную под конкретные запросы. Я на практике встречала такой подход.

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

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

2k 0 33 12 48

Привет!

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

Спасибо


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

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

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

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

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

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


➡️ 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


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

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

➡️ 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 нужен блок с привычной пагинацией и переходом на конкретную страницу


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

🍕🍕

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


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

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

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

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

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


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

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


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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

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

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

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

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

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

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

3.2k 0 30 69 63

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

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

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

3k 0 38 79 174
20 ta oxirgi post ko‘rsatilgan.