acks=all гарантирует, что сообщение считается записанным только после подтверждения от всех реплик – потеря данных исключена.
enable.idempotence=true предотвращает дублирование сообщений при повторных попытках продюсера (даёт exactly-once семантику на стороне записи).
Партиционирование по ключу (например, userId) гарантирует строгий порядок сообщений в рамках одной партиции.
Consumer с isolation.level=read_committed читает только зафиксированные транзакции, избегая чтения незавершённых записей.
❌ Почему не другие варианты:
A (RabbitMQ): Обеспечивает at-least-once, но не exactly-once без сложных доработок на стороне потребителя (дедупликация). Порядок сохраняется только в рамках одной очереди при одном потребителе, но при масштабировании порядок может нарушиться.
C (SQS FIFO): Технически тоже подходит (гарантирует exactly-once и порядок), но имеет ограничения по пропускной способности (3000 сообщений в секунду) и менее гибок для сложной потоковой обработки.
D (БД как очередь): Не является брокером сообщений, создаёт высокую нагрузку на БД, сложно масштабируется и не даёт встроенных гарантий exactly-once без дополнительной логики.
Вывод: Для систем с требованиями порядка, exactly-once и отказоустойчивости Kafka — индустриальный стандарт.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
4734. В RabbitMQ при ошибке обработки сообщение возвращается в очередь, вызывая бесконечный цикл. Как этого избежать?
Anonymous Quiz
1%
Добавить больше потребителей
3%
Удалять сообщения при первой ошибке
2%
Перезапускать потребителя при каждом сбое
94%
Настроить Dead Letter Queue для сообщений после нескольких неудачных попыток
Dead Letter Queue (DLQ) — это отдельная очередь, куда попадают сообщения, которые не смогли обработаться после нескольких попыток. Как это работает:
1️⃣ Вы задаёте правило: например, при ошибке кладём сообщение обратно, но не сразу, а через 5, 30, 60 секунд (экспоненциальная задержка).
2️⃣ Если после 3-х попыток сообщение так и не обработалось — оно отправляется в DLQ.
3️⃣ В DLQ эти «проблемные» сообщения ждут ручного разбора, анализа или автоматической обработки по другому сценарию.
❌ Почему не подходят другие варианты:
A (больше потребителей) — как увеличить количество рабочих на конвейере с бракованной деталью: они будут просто быстрее прогонять по кругу тот же брак.
C (удалять при ошибке) — данные безвозвратно теряются. А вдруг это был важный заказ или платёж?
B (перезапуск потребителя) — проблема не в потребителе, а в данных. Перезапуск не исправит кривой JSON.
Вывод для аналитика: В требованиях к интеграциям всегда указывайте необходимость DLQ. Это гарантия, что система не «зависнет» из-за одного плохого сообщения, а данные не потеряются. 📨🛡
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
4736. При интеграции с платежным сервисом система получила код 429 (Too Many Requests). Разработчик обработал его как фатальную ошибку, хотя бизнес ожидал автоматических повторов. Где возникла проблема?
Anonymous Quiz
1%
Разработчик неверно понял код ответа
9%
Тестировщик не проверил сценарий с 429
76%
Аналитик не описал поведение системы для разных HTTP-кодов
13%
Архитектор не заложил механизм retry
👍1 1
❌ Почему не другие варианты:
A (Разработчик) — он строго следовал ТЗ, где не было указаний по обработке 429. Если в требованиях написано «при ошибке прерывать операцию», разработчик поступил корректно.
B (Тестировщик) — тестировщик проверяет только задокументированные сценарии. Без требования сценарий с 429 не включается в тест-план.
D (Архитектор) — архитектор может предложить общий механизм повторных попыток (retry policy), но конкретные условия — какие коды должны вызывать повторы, сколько попыток делать, с какими интервалами — это зона ответственности аналитика, который описывает функциональное поведение системы.
Корень проблемы: Аналитик не заложил в требованиях обработку различных HTTP-кодов ответа от внешнего сервиса. Интеграционные сценарии часто содержат множество нюансов: временные ошибки (429, 503), требующие повтора; ошибки валидации (400), требующие корректировки данных; ошибки авторизации (401, 403), требующие обновления токена.
Что должен был сделать аналитик:
Изучить документацию внешнего сервиса и выписать все возможные коды ответа.
Для каждого кода согласовать с бизнесом ожидаемое поведение системы:
200 — успех, идём дальше.
400 — ошибка в данных, показываем пользователю, что нужно исправить.
429 — временная ошибка, автоматически повторяем до 3 раз с интервалом 5, 30, 60 секунд.
500 — ошибка сервера, повторяем 3 раза, если не успешно — уведомляем администратора.
Задокументировать это в виде понятной таблицы или матрицы решений.
Последствия отсутствия таких требований:
Разработчик принимает решение на основе своего опыта (часто ошибочного).
Тестировщик не проверяет непрописанные сценарии.
В прод уходит функционал, который не соответствует ожиданиям бизнеса.
Пользователи видят ошибки там, где могли бы быть автоматические повторы.
Вывод: Полнота требований к интеграциям — прямая ответственность аналитика. Чем детальнее прописано поведение системы во всех возможных ситуациях, тем меньше рисков на этапах разработки и тестирования. 🎯
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
4737. Банк обновил версию API для проверки кредитных историй. Разработчики протестировали новую версию, всё работало. Через неделю старая версия API отключилась, и система перестала выдавать кредиты. В чём первопричина?
Anonymous Quiz
13%
Разработчики не учли отключение старой версии
22%
Тестировщики не проверили сценарий без старого API
51%
Аналитик не описал требование к graceful degradation при недоступности внешнего сервиса
14%
Архитектор не заложил механизм автоматического переключения
❌ Почему не другие варианты:
A (Разработчики) — они реализовали поддержку новой версии, но не знали, что старая скоро отключится, и не закладывали логику переключения.
B (Тестировщики) — тестировали то, что написано в требованиях. Без требования о graceful degradation этот сценарий не включается в план тестирования.
D (Архитектор) — может предложить решение (балансировку, fallback), но только если в требованиях указана необходимость непрерывности работы.
Что должен был сделать аналитик:
При описании интеграции указать: «Система должна поддерживать обе версии API в течение переходного периода».
Прописать поведение при недоступности API (например, отложить обработку, уведомить администратора, использовать кэшированные данные).
Включить в приёмочные тесты сценарии отключения старой версии и потери связи с новой.
Вывод: Надёжность системы закладывается на уровне требований, а не обнаруживается после падения в проде. 📉
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Как находят работу за бугром в IT
Наити работу на немецком рынке. И не через релокацию! Сам нашел и сам переехал!
Это история Бизнес аналитика, который работает в Deutsche Bank и язвительно пишет из солнечного Франкфурта-на-Майне.
Из ХЗ в ТЗ — блог про работу в финтехе и как там у них. Антон также исследует рынок РФ и продолжает ходить на собеседования.
Истории, которые уже вышли:
🟢 собеседование в банк Азии на 380К. Кринж😬
🟢 красные флаги в тестовом задании. И референс ответа.
🟢 как мы делали mit den Jungs в банке
🟢 стал бы я в 2026-ом накручивать опыт в резюме?
Переходите знакомиться: @anton_alekseev
Наити работу на немецком рынке. И не через релокацию! Сам нашел и сам переехал!
Это история Бизнес аналитика, который работает в Deutsche Bank и язвительно пишет из солнечного Франкфурта-на-Майне.
Из ХЗ в ТЗ — блог про работу в финтехе и как там у них. Антон также исследует рынок РФ и продолжает ходить на собеседования.
Истории, которые уже вышли:
🟢 собеседование в банк Азии на 380К. Кринж😬
🟢 красные флаги в тестовом задании. И референс ответа.
🟢 как мы делали mit den Jungs в банке
🟢 стал бы я в 2026-ом накручивать опыт в резюме?
Переходите знакомиться: @anton_alekseev
4738. Вы пишете запрос для отчета по продажам. Нужно получить список товаров, у которых общая сумма продаж превышает 100 000 рублей. Как правильно написать условие?
Anonymous Quiz
32%
WHERE SUM(sales_amount) > 100000
53%
HAVING SUM(sales_amount) > 100000
4%
FILTER SUM(sales_amount) > 100000
11%
GROUP BY с условием в SELECT
WHERE применяется до группировки (GROUP BY) и фильтрует отдельные строки таблицы.
HAVING применяется после группировки и фильтрует уже сгруппированные результаты (агрегатные функции, такие как SUM(), COUNT(), AVG()).
В вашем случае SUM(sales_amount) вычисляется после группировки по товарам, поэтому условие должно быть в HAVING. Если написать WHERE SUM(sales_amount) > 100000, СУБД выдаст ошибку, потому что на момент выполнения WHERE агрегатные функции ещё не вычислены.
Пример правильного запроса:
```sql
SELECT product_id, SUM(sales_amount) as total_sales
FROM sales
GROUP BY product_id
HAVING SUM(sales_amount) > 100000;
```
❌ Почему не другие варианты:
A — WHERE не работает с агрегатными функциями.
C — FILTER существует в SQL, но это нестандартный синтаксис для агрегации (используется редко, например, в PostgreSQL для частичной агрегации), и здесь он не применим.
D — условие нельзя поместить в SELECT, потому что оно должно отсеивать целые группы, а не просто отображать значение.
Вывод для аналитика:
При работе с группированными данными всегда используйте HAVING для фильтрации по агрегатам, а WHERE оставьте для фильтрации отдельных записей до группировки. Это фундаментальное правило SQL, которое помогает избежать ошибок в отчётах и анализах. 📊
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2🤩1
4739. Аналитик ищет клиентов без заказов: «ID клиента отсутствует в списке ID заказов». В списке ID заказов есть NULL. Что вернет запрос?
Anonymous Quiz
16%
Только клиентов без заказов
39%
Всех клиентов, кроме тех, у кого есть заказы
28%
Пустой результат (ни одного клиента)
18%
Ошибку выполнения запроса
🤔7❤1
Пример для понимания:
Представьте, что у нас есть клиенты с ID 1, 2, 3. В таблице заказов есть ID клиентов: 1 и NULL. Запрос мысленно проверяет каждого клиента:
Клиент 2 отсутствует в списке {1, NULL}? Сравнение с 1 даёт TRUE (2 не равно 1), но сравнение с NULL даёт UNKNOWN (2 не равно неизвестности). Итог — UNKNOWN, условие не выполняется.
Аналогично для всех клиентов — ни один не проходит.
Как избежать ошибки:
Использовать проверку через NOT EXISTS (она корректно обрабатывает NULL).
Исключить NULL из подзапроса, добавив условие WHERE customer_id IS NOT NULL.
Вывод для аналитика:
Всегда учитывайте возможность NULL в данных при написании запросов с отрицанием. Это частая причина некорректных отчётов и потери данных. 🎯
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
4740. Вы проектируете таблицу для хранения действий пользователей. Прогноз: 10 млн записей в день. Самый частый запрос: «показать все действия конкретного пользователя за последние 30 дней». Как обеспечить максимальную производительность?
Anonymous Quiz
23%
Создать составной индекс (user_id, action_time) в одной таблице.
56%
Партиционировать таблицу по месяцам и создать локальный индекс по user_id.
13%
Хранить действия каждого пользователя в отдельном JSON-документе в MongoDB.
9%
Шардировать таблицу по user_id на несколько физических серверов.
10 млн записей в день → ≈ 300 млн записей в месяц, ≈ 3.6 млрд в год.
Типичная строка лога: user_id (8 байт) + action_time (8 байт) + action_type (2 байта) + details (переменная). Оценим средний размер строки в 100 байт.
Размер таблицы за месяц: 300 млн × 100 байт = 30 ГБ.
Размер за год: 360 ГБ (без учёта индексов и накладных расходов).
Индекс на (user_id, action_time) в такой таблице займёт не менее 20–30% от объёма данных (B-дерево, ссылки на строки). Это ещё + 100 ГБ через год.
Запрос «за последние 30 дней» будет читать 1/12 часть от годового объёма (если нет партиционирования). Но даже чтение 30 ГБ с диска — это секунды или минуты, даже при полном сканировании индекса. Однако проблема не только в объёме, но и в структуре доступа.
2. Почему вариант A (просто индекс) не спасёт
Индекс (user_id, action_time) ускоряет поиск по конкретному пользователю, но данные в таблице физически разбросаны по всей её территории (записи вставляются в хронологическом порядке, но для одного пользователя они распределены по всем блокам).
Чтобы получить все действия пользователя за 30 дней, СУБД должна:
Найти в индексе все записи для этого user_id за указанный период (это быстро).
Выполнить случайные чтения (random I/O) каждой строки из таблицы по указателям из индекса. При большом количестве строк (например, у активного пользователя 1000 действий за месяц) это приведёт к тысячам случайных обращений к диску.
Даже если используется covering index (включить все нужные поля в индекс), то индекс сам по себе станет огромным и его сканирование за 30 дней всё равно потребует чтения миллионов записей индекса.
Индекс не решает проблему устаревания данных: удаление старых записей (например, через год) потребует тяжёлого DELETE с блокировками и фрагментацией.
3. Почему партиционирование (вариант B) — оптимальное решение
Партиционирование по диапазону дат (например, по месяцам) — стандартный паттерн для временных рядов. Оно даёт:
✅ Partition Pruning (отсечение партиций)
Планировщик запроса понимает, что условие action_time BETWEEN ... AND ... затрагивает только определённые партиции (например, текущий и предыдущий месяц). Он не будет сканировать остальные партиции с данными за прошлые годы. Вместо 360 ГБ читается максимум 30–60 ГБ (1–2 партиции).
✅ Локальные индексы
Индекс по user_id внутри каждой партиции имеет гораздо меньшую высоту B-дерева (т.к. работает с 30 млн строк, а не с 3.6 млрд). Это ускоряет поиск и снижает потребление памяти.
✅ Управление жизненным циклом данных
Удаление старых данных — это DROP PARTITION, а не DELETE. Операция занимает миллисекунды и не вызывает фрагментации.
Архивация: партицию можно быстро отсоединить от таблицы (DETACH PARTITION) и сохранить как отдельную таблицу или файл.
Обслуживание (реиндексация, анализ) можно проводить на отдельных партициях, не блокируя всю таблицу.
✅ Производительность записи
Записи распределяются по партициям в соответствии с датой. Это не создаёт «горячих точек» (hotspots) — вставки идут в текущую партицию, но это нормально. Если нужно распределить нагрузку ещё сильнее, можно использовать субпартиционирование (например, по дням или по хешу user_id внутри месяца).
4. Почему MongoDB (вариант C) — не лучший выбор
Ограничение размера документа: BSON-документ не может превышать 16 МБ. У активного пользователя за несколько лет действий может накопиться гораздо больше (даже за месяц — тысячи событий, JSON с ними легко превысит 16 МБ).
Запросы внутри документа: Если хранить все действия в массиве, то выборка за последние 30 дней потребует разбора всего массива и фильтрации на стороне приложения — это неэффективно и нагружает сеть.
Индексирование: MongoDB поддерживает индексы на полях вложенных массивов, но при частых обновлениях документа (добавлении нового действия) происходит перезапись всего документа — это дорого и вызывает фрагментацию.
Транзакционность: Если нужны транзакции или сложные joins (например, с пользователями), MongoDB не подходит.
Когда MongoDB хороша: для редких изменений и чтения всего документа целиком.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Продолжение к объяснению ❗️
5. Почему шардинг по user_id (вариант D) неэффективен для этого запроса
Шардинг (горизонтальное масштабирование) распределяет данные по физическим узлам на основе ключа (например, user_id).
Запрос «действия за последние 30 дней» не ограничен по ключу шардирования — он требует данные за определённый период, но у разных пользователей эти данные лежат на разных узлах.
Придётся выполнить запрос ко всем шардам (fan-out), собрать результаты и отсортировать. Это создаёт огромную сетевую нагрузку и задержки.
Шардинг полезен, когда все запросы содержат ключ шардирования (например, «показать действия пользователя X»). Но в нашем случае запрос включает ещё и период, поэтому шардинг по user_id не даст partition pruning по времени.
Шардинг требует дополнительной инфраструктуры (маршрутизаторы, балансировка) и усложняет операции JOIN и транзакции.
Когда шардинг оправдан: если данные не имеют естественного временного разреза и запросы всегда содержат ключ шардирования (например, социальная сеть: все посты пользователя).
6. Дополнительные рекомендации для аналитика
При проектировании таблиц с временными метками всегда задавайте вопрос: «Какой объём данных будет накоплен через год? Через три года?»
Партиционирование должно быть заложено в требования к физической модели данных с самого начала. Это не оптимизация постфактум — добавить партиционирование на таблицу с миллиардами строк очень трудоёмко.
Выбирайте ключ партиционирования так, чтобы он совпадал с условиями в самых частых запросах (здесь — action_time).
Рассмотрите интервал партиционирования (день, неделя, месяц) в зависимости от частоты запросов и скорости роста.
Не забывайте про локальные vs глобальные индексы: в большинстве СУБД (PostgreSQL, Oracle, MySQL) локальные индексы автоматически партицируются вместе с таблицей и обслуживаются независимо.
Учитывайте операции обслуживания: партиционирование позволяет делать VACUUM, ANALYZE, REINDEX на отдельных партициях без простоя всей системы.
Для ещё большей производительности можно комбинировать партиционирование с кластеризацией (физической сортировкой данных внутри партиции по user_id), но это требует дополнительного планирования.
7. Итог
Партиционирование по времени с локальными индексами — единственный вариант, который одновременно:
сокращает объём сканируемых данных (partition pruning),
обеспечивает быстрый доступ по пользователю (локальный индекс),
упрощает обслуживание и удаление старых данных (DROP PARTITION),
масштабируется горизонтально без усложнения инфраструктуры.
Это промышленный стандарт для систем с временными рядами (логи, события, метрики). Системный аналитик, закладывающий такое решение на этапе проектирования, предотвращает будущие проблемы с производительностью и стоимостью владения системой. 🎯
5. Почему шардинг по user_id (вариант D) неэффективен для этого запроса
Шардинг (горизонтальное масштабирование) распределяет данные по физическим узлам на основе ключа (например, user_id).
Запрос «действия за последние 30 дней» не ограничен по ключу шардирования — он требует данные за определённый период, но у разных пользователей эти данные лежат на разных узлах.
Придётся выполнить запрос ко всем шардам (fan-out), собрать результаты и отсортировать. Это создаёт огромную сетевую нагрузку и задержки.
Шардинг полезен, когда все запросы содержат ключ шардирования (например, «показать действия пользователя X»). Но в нашем случае запрос включает ещё и период, поэтому шардинг по user_id не даст partition pruning по времени.
Шардинг требует дополнительной инфраструктуры (маршрутизаторы, балансировка) и усложняет операции JOIN и транзакции.
Когда шардинг оправдан: если данные не имеют естественного временного разреза и запросы всегда содержат ключ шардирования (например, социальная сеть: все посты пользователя).
6. Дополнительные рекомендации для аналитика
При проектировании таблиц с временными метками всегда задавайте вопрос: «Какой объём данных будет накоплен через год? Через три года?»
Партиционирование должно быть заложено в требования к физической модели данных с самого начала. Это не оптимизация постфактум — добавить партиционирование на таблицу с миллиардами строк очень трудоёмко.
Выбирайте ключ партиционирования так, чтобы он совпадал с условиями в самых частых запросах (здесь — action_time).
Рассмотрите интервал партиционирования (день, неделя, месяц) в зависимости от частоты запросов и скорости роста.
Не забывайте про локальные vs глобальные индексы: в большинстве СУБД (PostgreSQL, Oracle, MySQL) локальные индексы автоматически партицируются вместе с таблицей и обслуживаются независимо.
Учитывайте операции обслуживания: партиционирование позволяет делать VACUUM, ANALYZE, REINDEX на отдельных партициях без простоя всей системы.
Для ещё большей производительности можно комбинировать партиционирование с кластеризацией (физической сортировкой данных внутри партиции по user_id), но это требует дополнительного планирования.
7. Итог
Партиционирование по времени с локальными индексами — единственный вариант, который одновременно:
сокращает объём сканируемых данных (partition pruning),
обеспечивает быстрый доступ по пользователю (локальный индекс),
упрощает обслуживание и удаление старых данных (DROP PARTITION),
масштабируется горизонтально без усложнения инфраструктуры.
Это промышленный стандарт для систем с временными рядами (логи, события, метрики). Системный аналитик, закладывающий такое решение на этапе проектирования, предотвращает будущие проблемы с производительностью и стоимостью владения системой. 🎯
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1