🔌 "Too many connections" - и почему база падает под нагрузкой
Под нагрузкой прилетает
Первая мысль - "надо поднять лимит соединений в базе"
Обычно это лечение симптома, а не причины
А теперь, откуда берётся упор в соединения и почему пул решает это правильно
💰 Соединение к базе - дорогое удовольствие
Каждое подключение к базе - это не бесплатная абстракция
В том же Postgres на каждое соединение заводится отдельный процесс со своей памятью
Их число жёстко ограничено (
Поэтому "просто поднять лимит до 5000" - плохая идея
Ты не уберёшь проблему, а перенесёшь базу из состояния "отказывает" в состояние "еле дышит"
💥 Как упираешься в потолок
Приложение открывает новое соединение на каждый запрос и закрывает после
На малой нагрузке норм
Но под потоком - сто параллельных запросов открывают сто соединений одновременно, тысяча запросов - тысячу Лимит выбивается мгновенно, и новые запросы получают отказ
При этом само соединение ещё и открывается не моментально - на установку уходит время, так что ты платишь дважды
🏊 Пул соединений
Идея простая - не открывать соединение под каждый запрос, а держать наготове небольшой набор уже открытых и переиспользовать их
Запросу нужна база - он берёт свободное соединение из пула, отработал - вернул обратно, не закрывая
Соединений мало, они постоянно в деле, и на установку время не тратится
Пул бывает двух видов:
встроенный в приложение (HikariCP в Java, пулеры в других языках) и внешний - отдельный процесс перед базой (для Postgres это PgBouncer)
Внешний особенно важен, когда инстансов приложения много
⚠️ Ловушка масштабирования
У тебя двадцать инстансов приложения, у каждого свой пул на 20 соединений
Двадцать раз по двадцать — это уже 400 соединений к базе, даже если сейчас тихо
Раскатал автоскейлингом полсотни подов - и снова упёрся в лимит, хотя пулы вроде есть
Вот тут и ставят один общий внешний пулер (PgBouncer) перед базой: приложения ходят в него, а он держит небольшое число реальных соединений к базе
📉 И размер пула - не "чем больше, тем лучше"
Ещё контринтуитивная штука - огромный пул часто медленнее маленького
Если соединений больше, чем база способна реально обрабатывать параллельно (а это упирается в число ядер и диски), они начинают толкаться и мешать друг другу
Небольшой пул, где запросы аккуратно ждут своей очереди, нередко даёт больший throughput, чем раздутый
Так что размер подбирают под возможности базы, а не "на глаз побольше"
А вы на чём ловили «too many connections» — забыли пул, разъехались по инстансам, или крутанули autoscaling? 🤔
#backend #database #postgres #performance #devops #dev
Под нагрузкой прилетает
FATAL: too many connections, база отказывается принимать запросы, всё стоитПервая мысль - "надо поднять лимит соединений в базе"
Обычно это лечение симптома, а не причины
А теперь, откуда берётся упор в соединения и почему пул решает это правильно
💰 Соединение к базе - дорогое удовольствие
Каждое подключение к базе - это не бесплатная абстракция
В том же Postgres на каждое соединение заводится отдельный процесс со своей памятью
Их число жёстко ограничено (
max_connections), и не просто так: тысяча соединений - это тысяча процессов, которые сжирают память и заставляют базу тратить силы на переключение между ними, а не на работуПоэтому "просто поднять лимит до 5000" - плохая идея
Ты не уберёшь проблему, а перенесёшь базу из состояния "отказывает" в состояние "еле дышит"
💥 Как упираешься в потолок
Приложение открывает новое соединение на каждый запрос и закрывает после
На малой нагрузке норм
Но под потоком - сто параллельных запросов открывают сто соединений одновременно, тысяча запросов - тысячу Лимит выбивается мгновенно, и новые запросы получают отказ
При этом само соединение ещё и открывается не моментально - на установку уходит время, так что ты платишь дважды
🏊 Пул соединений
Идея простая - не открывать соединение под каждый запрос, а держать наготове небольшой набор уже открытых и переиспользовать их
Запросу нужна база - он берёт свободное соединение из пула, отработал - вернул обратно, не закрывая
Соединений мало, они постоянно в деле, и на установку время не тратится
Пул бывает двух видов:
встроенный в приложение (HikariCP в Java, пулеры в других языках) и внешний - отдельный процесс перед базой (для Postgres это PgBouncer)
Внешний особенно важен, когда инстансов приложения много
⚠️ Ловушка масштабирования
У тебя двадцать инстансов приложения, у каждого свой пул на 20 соединений
Двадцать раз по двадцать — это уже 400 соединений к базе, даже если сейчас тихо
Раскатал автоскейлингом полсотни подов - и снова упёрся в лимит, хотя пулы вроде есть
Вот тут и ставят один общий внешний пулер (PgBouncer) перед базой: приложения ходят в него, а он держит небольшое число реальных соединений к базе
📉 И размер пула - не "чем больше, тем лучше"
Ещё контринтуитивная штука - огромный пул часто медленнее маленького
Если соединений больше, чем база способна реально обрабатывать параллельно (а это упирается в число ядер и диски), они начинают толкаться и мешать друг другу
Небольшой пул, где запросы аккуратно ждут своей очереди, нередко даёт больший throughput, чем раздутый
Так что размер подбирают под возможности базы, а не "на глаз побольше"
too many connections - это почти всегда поставь пул, а под много инстансов — общий внешний пулер, и потолок перестанет быть потолкомА вы на чём ловили «too many connections» — забыли пул, разъехались по инстансам, или крутанули autoscaling? 🤔
#backend #database #postgres #performance #devops #dev
👍1🤩1
🗃 Кэш поставили, а он отдаёт старьё
В программировании две сложные вещи - инвалидация кэша и придумывание имён
С кэшем так и вышло
Закэшировать - минута работы, а заставить вовремя отдавать свежее - вот где начинается веселье Разберём, почему кэш врёт и как с этим жить
🎯 Сложность не в том, чтобы закэшировать
Положить данные в кэш легко
Вся жара в другом: когда копия протухла и её пора выкинуть?
Данные в базе поменялись, а в кэше лежит старьё - и юзер видит вчерашний день
Вот это "когда чистить" и есть главная боль
⏳ Вариант 1: TTL, само протухает по времени
Кладёшь данные на N минут, дальше они испаряются, и следующий запрос тянет свежак из базы
Плюс - просто, ничего не забудешь
Минус - есть окно: данные уже поменялись, а TTL ещё не вышел, и юзер видит старое
Для ленты или счётчика лайков норм, минутная задержка никого не убьёт
Для баланса или прав доступа - уже стрёмно
🎯 Вариант 2: сбрасывать кэш при изменении
Поменял данные - сразу выкинул их из кэша (или перезаписал)
Точность идеальная, старья нет
Звучит красиво, но тут прячется главная подстава
Данные меняются не в одном месте
Есть основная ручка апдейта - её помнят
А ещё админка, фоновый джоб, миграция, импорт, соседний сервис - и если хоть один путь поменял данные, но забыл сбросить кэш, получаешь вечно устаревшую запись, которую потом фиг найдёшь
Чем больше мест правит данные, тем легче забыть про одно
💥 И ещё пара граблей
- Лавина (стампед). Популярный ключ протух - и в ту же секунду сотня запросов ломанулась в базу пересчитывать одно и то же
Кэш, который снимал нагрузку, наоборот устраивает базе микро-DDoS
Лечится так: пересчитывает кто-то один, остальные ждут результат
- Сброс из пушки по воробьям. Из страха отдать старьё некоторые на любое изменение чистят пол-кэша
Формально всё свежее, а толку ноль - кэш вечно пустой и не работает
🛠 Как выбирать
- Данные терпят лёгкое устаревание (лента, счётчики, справочники) - бери TTL, просто и без риска забыть
- Устаревание недопустимо (деньги, права, статусы) - сбрасывай явно при записи, но тогда честно найди ВСЕ места, где данные меняются, и сбрось в каждом
Пропустил одно - поймал баг
- Не уверен, что надёжно инвалидируешь - честнее не кэшировать, чем потом ловить призрачные баги со старьём
Прежде чем кэшировать, ответь себе, как будешь это оттуда выкидывать
Нет ответа - не кэшируй
А вас кэш подставлял? 🤔
#backend #performance #architecture #dev #programming #cache
В программировании две сложные вещи - инвалидация кэша и придумывание имён
С кэшем так и вышло
Закэшировать - минута работы, а заставить вовремя отдавать свежее - вот где начинается веселье Разберём, почему кэш врёт и как с этим жить
🎯 Сложность не в том, чтобы закэшировать
Положить данные в кэш легко
Вся жара в другом: когда копия протухла и её пора выкинуть?
Данные в базе поменялись, а в кэше лежит старьё - и юзер видит вчерашний день
Вот это "когда чистить" и есть главная боль
⏳ Вариант 1: TTL, само протухает по времени
Кладёшь данные на N минут, дальше они испаряются, и следующий запрос тянет свежак из базы
Плюс - просто, ничего не забудешь
Минус - есть окно: данные уже поменялись, а TTL ещё не вышел, и юзер видит старое
Для ленты или счётчика лайков норм, минутная задержка никого не убьёт
Для баланса или прав доступа - уже стрёмно
🎯 Вариант 2: сбрасывать кэш при изменении
Поменял данные - сразу выкинул их из кэша (или перезаписал)
Точность идеальная, старья нет
Звучит красиво, но тут прячется главная подстава
Данные меняются не в одном месте
Есть основная ручка апдейта - её помнят
А ещё админка, фоновый джоб, миграция, импорт, соседний сервис - и если хоть один путь поменял данные, но забыл сбросить кэш, получаешь вечно устаревшую запись, которую потом фиг найдёшь
Чем больше мест правит данные, тем легче забыть про одно
💥 И ещё пара граблей
- Лавина (стампед). Популярный ключ протух - и в ту же секунду сотня запросов ломанулась в базу пересчитывать одно и то же
Кэш, который снимал нагрузку, наоборот устраивает базе микро-DDoS
Лечится так: пересчитывает кто-то один, остальные ждут результат
- Сброс из пушки по воробьям. Из страха отдать старьё некоторые на любое изменение чистят пол-кэша
Формально всё свежее, а толку ноль - кэш вечно пустой и не работает
🛠 Как выбирать
- Данные терпят лёгкое устаревание (лента, счётчики, справочники) - бери TTL, просто и без риска забыть
- Устаревание недопустимо (деньги, права, статусы) - сбрасывай явно при записи, но тогда честно найди ВСЕ места, где данные меняются, и сбрось в каждом
Пропустил одно - поймал баг
- Не уверен, что надёжно инвалидируешь - честнее не кэшировать, чем потом ловить призрачные баги со старьём
Прежде чем кэшировать, ответь себе, как будешь это оттуда выкидывать
Нет ответа - не кэшируй
А вас кэш подставлял? 🤔
#backend #performance #architecture #dev #programming #cache
❤1
🚨 Как перевод денег уронил нам прод
⏰ 19:10
Задеплоили долгожданное - переводы между кошельками юзеров. Списываем с одного, зачисляем на другой, всё в одной транзакции, чтобы деньги не потерялись
На стейдже гоняли, всё зелёное
⏰ 19:40
Посыпались ошибки, часть переводов падает
В логах - "deadlock detected"
Не таймаут, не отвал базы, а взаимная блокировка
Вылезает только когда переводов много и идут они параллельно
⏰ 19:55
Картина складывается
Перевод от Ани к Боре берёт блокировку на строку Ани, потом тянется за строкой Бори
А в ту же секунду перевод от Бори к Ане блокирует строку Бори и тянется за строкой Ани
Оба ждут друг друга, никто не отпустит
База ловит это сама: видит цикл ожидания, убивает одну из транзакций с ошибкой дедлока
Юзер получает "перевод не прошёл", хотя по сути ничего не сломано
⏰ 20:20
Причина не в переводах, а в порядке
Мы блокировали строки как "сначала отправитель, потом получатель"
Направление у переводов разное, вот встречные пары и встают лицом к лицу
Лочим строки в одном и том же порядке, независимо от того, кто кому платит
Взяли сортировку по id: сначала строка с меньшим id, потом с большим
Теперь две встречные транзакции идут за строками одинаково, и вместо взаимной блокировки одна ждёт другую
🧠 Что забрали с собой
- Дедлок - это про порядок захвата ресурсов, а не про их количество
Двум транзакциям хватает взять два одинаковых замка в разной последовательности
- На стейдже такое не ловится, там нет параллельной нагрузки
Дедлок живёт только там, где реально пересекаются одновременные операции
- Трогаешь в транзакции несколько строк - бери их в детерминированном порядке
По id, по ключу, как угодно, лишь бы одинаково у всех
- И держи в голове: база всё равно иногда будет кидать дедлоки, это норма под нагрузкой
Ретрай на такую ошибку - не костыль, а штатный сценарий
Одна строчка сортировки, а стоила вечера и пачки упавших переводов
А вы ловили дедлоки? На чём - переводы, счётчики, обновление связанных таблиц? 🤔
#backend #database #postgres #concurrency #dev #incident
⏰ 19:10
Задеплоили долгожданное - переводы между кошельками юзеров. Списываем с одного, зачисляем на другой, всё в одной транзакции, чтобы деньги не потерялись
На стейдже гоняли, всё зелёное
⏰ 19:40
Посыпались ошибки, часть переводов падает
В логах - "deadlock detected"
Не таймаут, не отвал базы, а взаимная блокировка
Вылезает только когда переводов много и идут они параллельно
⏰ 19:55
Картина складывается
Перевод от Ани к Боре берёт блокировку на строку Ани, потом тянется за строкой Бори
А в ту же секунду перевод от Бори к Ане блокирует строку Бори и тянется за строкой Ани
Оба ждут друг друга, никто не отпустит
База ловит это сама: видит цикл ожидания, убивает одну из транзакций с ошибкой дедлока
Юзер получает "перевод не прошёл", хотя по сути ничего не сломано
⏰ 20:20
Причина не в переводах, а в порядке
Мы блокировали строки как "сначала отправитель, потом получатель"
Направление у переводов разное, вот встречные пары и встают лицом к лицу
Лочим строки в одном и том же порядке, независимо от того, кто кому платит
Взяли сортировку по id: сначала строка с меньшим id, потом с большим
Теперь две встречные транзакции идут за строками одинаково, и вместо взаимной блокировки одна ждёт другую
🧠 Что забрали с собой
- Дедлок - это про порядок захвата ресурсов, а не про их количество
Двум транзакциям хватает взять два одинаковых замка в разной последовательности
- На стейдже такое не ловится, там нет параллельной нагрузки
Дедлок живёт только там, где реально пересекаются одновременные операции
- Трогаешь в транзакции несколько строк - бери их в детерминированном порядке
По id, по ключу, как угодно, лишь бы одинаково у всех
- И держи в голове: база всё равно иногда будет кидать дедлоки, это норма под нагрузкой
Ретрай на такую ошибку - не костыль, а штатный сценарий
Одна строчка сортировки, а стоила вечера и пачки упавших переводов
А вы ловили дедлоки? На чём - переводы, счётчики, обновление связанных таблиц? 🤔
#backend #database #postgres #concurrency #dev #incident
🎭 Мифы про производительность, в которые верят даже опытные
Миф 1: "Меньше строк кода - быстрее работает"
На самом деле длина кода и скорость почти не связаны
Однострочник с хитрой магией может быть медленнее и в разы хуже читаться, чем пять понятных строк
Компилятор и рантайм оптимизируют лучше, когда видят простой код, а не эквилибристику
Пиши понятно, а не коротко
Миф 2: "Оптимизировать надо везде"
На самом деле почти весь код не на горячем пути - он выполняется редко, и его скорость никого не волнует
Реальные тормоза сидят в паре мест, и найти их можно только профайлером, а не на глаз
Оптимизировать всё подряд - значит усложнять код там, где это не даёт ничего, и раздувать сроки
Миф 3: "Кэш всегда ускоряет"
На самом деле кэш добавляет отдельный слой со своими багами - инвалидацией, устареванием, лавинами на протухших ключах
Иногда убрать лишний запрос к базе или повесить индекс даёт тот же выигрыш, но без нового источника проблем
Кэш - это размен скорости на сложность
Миф 4: "Больше потоков - быстрее"
На самом деле только до предела
Потоков больше, чем ядер и ресурсов, - и они начинают толкаться, драться за блокировки и тратить время на переключение
В какой-то момент добавление потоков не ускоряет, а замедляет
Параллелизм помогает ровно до того места, где упираешься в реальное железо
Миф 5: "База медленная, вынесу логику в код"
На самом деле база десятилетиями заточена под работу с данными - фильтрацию, сортировку, агрегацию
Вытащить всё в приложение и крутить руками обычно медленнее, чем один нормальный запрос
Тот же N+1 - это как раз попытка сделать в коде то, что база сделала бы одним движением
Общий смысл под всеми пятью один: производительность - это не про интуицию и красивые приёмы, а про измерения, и использование только там где надо
Не гадай, где медленно - померь профайлером, и часто окажется, что тормозит совсем не там, где ты собирался оптимизировать
А какие мифы вы слышали слышали и сами так думали до определенного момента? 🤔
#performance #backend #optimization #dev #programming #database
Миф 1: "Меньше строк кода - быстрее работает"
На самом деле длина кода и скорость почти не связаны
Однострочник с хитрой магией может быть медленнее и в разы хуже читаться, чем пять понятных строк
Компилятор и рантайм оптимизируют лучше, когда видят простой код, а не эквилибристику
Пиши понятно, а не коротко
Миф 2: "Оптимизировать надо везде"
На самом деле почти весь код не на горячем пути - он выполняется редко, и его скорость никого не волнует
Реальные тормоза сидят в паре мест, и найти их можно только профайлером, а не на глаз
Оптимизировать всё подряд - значит усложнять код там, где это не даёт ничего, и раздувать сроки
Миф 3: "Кэш всегда ускоряет"
На самом деле кэш добавляет отдельный слой со своими багами - инвалидацией, устареванием, лавинами на протухших ключах
Иногда убрать лишний запрос к базе или повесить индекс даёт тот же выигрыш, но без нового источника проблем
Кэш - это размен скорости на сложность
Миф 4: "Больше потоков - быстрее"
На самом деле только до предела
Потоков больше, чем ядер и ресурсов, - и они начинают толкаться, драться за блокировки и тратить время на переключение
В какой-то момент добавление потоков не ускоряет, а замедляет
Параллелизм помогает ровно до того места, где упираешься в реальное железо
Миф 5: "База медленная, вынесу логику в код"
На самом деле база десятилетиями заточена под работу с данными - фильтрацию, сортировку, агрегацию
Вытащить всё в приложение и крутить руками обычно медленнее, чем один нормальный запрос
Тот же N+1 - это как раз попытка сделать в коде то, что база сделала бы одним движением
Общий смысл под всеми пятью один: производительность - это не про интуицию и красивые приёмы, а про измерения, и использование только там где надо
Не гадай, где медленно - померь профайлером, и часто окажется, что тормозит совсем не там, где ты собирался оптимизировать
А какие мифы вы слышали слышали и сами так думали до определенного момента? 🤔
#performance #backend #optimization #dev #programming #database
🔥1
🧩 Что тут не так? Код-загадка
Формат новый - показываю код, ты угадываешь подвох, ниже разбор
Питон, но грабли универсальные
Функция кладёт товар в корзину
Если корзину не передали - создаёт пустую
Логично же?
Вопрос: что вернёт код, если вызвать функцию три раза подряд для разных юзеров, каждый раз без передачи корзины?
Интуиция говорит: каждый вызов - новая пустая корзина, значит вернётся ["яблоко"], потом ["хлеб"], потом ["молоко"]
. . .
А на деле:
["яблоко"]
["яблоко", "хлеб"]
["яблоко", "хлеб", "молоко"]
Корзина одна на всех, и товары в неё накапливаются
Три разных юзера сложили покупки в общую тележку
🔍 Почему так
Значение по умолчанию (этот самый []) создаётся один раз - в момент, когда питон читает определение функции, а не при каждом вызове
Дальше все вызовы, которые не передали свой аргумент, работают с одним и тем же списком
Он живёт между вызовами и копит всё, что в него положили
Особенно весело это выстреливает на проде: локально по одному запросу всё чисто, а под потоком данные разных юзеров начинают протекать друг к другу
Ловится потом такое тяжело - код же выглядит правильным
🛠 Как чинить
Дефолтом ставим не список, а None, и создаём свежий список внутри:
Теперь каждый вызов без корзины получает свою собственную, пустую
Изменяемый объект (список, словарь, множество) в значении по умолчанию - почти всегда мина
Ставь None и создавай внутри
Кто угадал подвох с первого взгляда - press f? 🤔
#python #dev #programming #backend #bugs #квиз
Формат новый - показываю код, ты угадываешь подвох, ниже разбор
Питон, но грабли универсальные
def add_item(item, cart=[]):
cart.append(item)
return cart
Функция кладёт товар в корзину
Если корзину не передали - создаёт пустую
Логично же?
Вопрос: что вернёт код, если вызвать функцию три раза подряд для разных юзеров, каждый раз без передачи корзины?
add_item("яблоко") # ?
add_item("хлеб") # ?
add_item("молоко") # ?Интуиция говорит: каждый вызов - новая пустая корзина, значит вернётся ["яблоко"], потом ["хлеб"], потом ["молоко"]
. . .
А на деле:
["яблоко"]
["яблоко", "хлеб"]
["яблоко", "хлеб", "молоко"]
Корзина одна на всех, и товары в неё накапливаются
Три разных юзера сложили покупки в общую тележку
🔍 Почему так
Значение по умолчанию (этот самый []) создаётся один раз - в момент, когда питон читает определение функции, а не при каждом вызове
Дальше все вызовы, которые не передали свой аргумент, работают с одним и тем же списком
Он живёт между вызовами и копит всё, что в него положили
Особенно весело это выстреливает на проде: локально по одному запросу всё чисто, а под потоком данные разных юзеров начинают протекать друг к другу
Ловится потом такое тяжело - код же выглядит правильным
🛠 Как чинить
Дефолтом ставим не список, а None, и создаём свежий список внутри:
def add_item(item, cart=None):
if cart is None:
cart = []
cart.append(item)
return cart
Теперь каждый вызов без корзины получает свою собственную, пустую
Изменяемый объект (список, словарь, множество) в значении по умолчанию - почти всегда мина
Ставь None и создавай внутри
Кто угадал подвох с первого взгляда - press f? 🤔
#python #dev #programming #backend #bugs #квиз
Хотите не теряться среди сотен откликов?
На hh сейчас бывает ощущение, что до HR просто не докричаться. И небольшое откровение HR: вас правда очень и очень много 😄
Поэтому в EvApps мы решили сделать по-другому и создали закрытый канал EvApps Talent | Projects, где новые проектные заявки будут появляться в первую очередь.
Попасть туда смогут только избранные. Шучу 😄 Но не совсем.
В канал мы приглашаем только тех, кто прошёл наше техническое собеседование, подтвердил навыки из резюме и подходит нам по soft skills.
На технички сейчас в первую очередь зовём Middle/Senior IT-специалистов: разработчиков, аналитиков, специалистов по данным, DevOps, 1С и другим техническим направлениям. Java, .NET и QA сейчас не в фокусе.
Смысл простой: когда появится подходящий проект, вам не придётся снова пробиваться через сотни откликов. Вы увидите заявку в канале, а мы уже будем знать ваш уровень и опыт.
Хотите попробовать попасть внутрь — напишите в личку HR (@olga_panova): «Хочу в закрытый канал» и прикрепите свое актуальное резюме
Посмотрим резюме, познакомимся и, если профиль подходит под направления, с которыми мы работаем, договоримся о техническом интервью.
Возможно, подходящего проекта для вас нет прямо сейчас.
Но когда он появится, вам уже не придётся снова конкурировать за внимание среди сотен откликов.💚
На hh сейчас бывает ощущение, что до HR просто не докричаться. И небольшое откровение HR: вас правда очень и очень много 😄
Поэтому в EvApps мы решили сделать по-другому и создали закрытый канал EvApps Talent | Projects, где новые проектные заявки будут появляться в первую очередь.
Попасть туда смогут только избранные. Шучу 😄 Но не совсем.
В канал мы приглашаем только тех, кто прошёл наше техническое собеседование, подтвердил навыки из резюме и подходит нам по soft skills.
На технички сейчас в первую очередь зовём Middle/Senior IT-специалистов: разработчиков, аналитиков, специалистов по данным, DevOps, 1С и другим техническим направлениям. Java, .NET и QA сейчас не в фокусе.
Смысл простой: когда появится подходящий проект, вам не придётся снова пробиваться через сотни откликов. Вы увидите заявку в канале, а мы уже будем знать ваш уровень и опыт.
Хотите попробовать попасть внутрь — напишите в личку HR (@olga_panova): «Хочу в закрытый канал» и прикрепите свое актуальное резюме
Посмотрим резюме, познакомимся и, если профиль подходит под направления, с которыми мы работаем, договоримся о техническом интервью.
Возможно, подходящего проекта для вас нет прямо сейчас.
Но когда он появится, вам уже не придётся снова конкурировать за внимание среди сотен откликов.💚
🔁 Ретраи, которые добивают лежачий сервис
Сервис-сосед начал тупить, отвечает через раз
Логично же - повторить запрос, ретрай спасёт
Так думает не только твой код, а все сто инстансов сразу
И вот тут ретрай из спасения превращается в оружие против самого сервиса
💥 Retry storm
База или соседний сервис просели под нагрузкой, отвечают с ошибками
Каждый клиент, поймав ошибку, тут же повторяет запрос
Был один поток запросов - стало два, потом три
Сервис, который и так на пределе, получает в разы больше нагрузки именно в тот момент, когда ему плохо
В итоге он ложится окончательно, и ретраи держат его на дне, не давая подняться
⏳ Экспоненциальная задержка
Первое лекарство - не долбить сразу
Не "ошибка - мгновенно повторил", а "подожди и повтори с растущей паузой": 1 секунда, потом 2, потом 4, потом 8
Так ты даёшь соседу передышку вместо того, чтобы добивать лежащего
Плюс всегда ставь: максимум попыток и максимальную задержку, иначе клиенты будут висеть вечно
🎲 Джиттер - без него всё зря
А вот про это забывают
Даже с экспоненциальной задержкой есть засада: если сотня клиентов упала в одну секунду, то и повторят они синхронно - через 1с все разом, через 2с все разом
Получаются волны, которые снова добивают сервис пачками
Лечится джиттером - случайной добавкой к задержке
Не ровно 2 секунды, а 2 плюс-минус случайные миллисекунды
Тогда клиенты размазываются во времени и не бьют залпом
Мелочь, а именно она превращает ретраи из волн в ровный аккуратный поток
🛡 Что ещё
- Ретрай только идемпотентных операций
Повторять "создать платёж" вслепую - привет, двойное списание (об этом был отдельный пост)
- Уважай Retry-After
Если сервис в ответе явно сказал "приходи через 5 секунд" - слушайся, а не долби по своему расписанию
- Circuit breaker
Если сосед стабильно падает, перестань ходить к нему вообще на какое-то время - дай ему встать, а сам отдай заглушку
Ретраи - штука нужная, но наивный "поймал ошибку - сразу повторил" под нагрузкой не лечит
Пауза с ростом, джиттер и потолок попыток - вот что превращает их из проблемы в защиту
Свой сервис заваливали ретраями? 🤔
#backend #reliability #distributed #dev #programming #devops
Сервис-сосед начал тупить, отвечает через раз
Логично же - повторить запрос, ретрай спасёт
Так думает не только твой код, а все сто инстансов сразу
И вот тут ретрай из спасения превращается в оружие против самого сервиса
💥 Retry storm
База или соседний сервис просели под нагрузкой, отвечают с ошибками
Каждый клиент, поймав ошибку, тут же повторяет запрос
Был один поток запросов - стало два, потом три
Сервис, который и так на пределе, получает в разы больше нагрузки именно в тот момент, когда ему плохо
В итоге он ложится окончательно, и ретраи держат его на дне, не давая подняться
⏳ Экспоненциальная задержка
Первое лекарство - не долбить сразу
Не "ошибка - мгновенно повторил", а "подожди и повтори с растущей паузой": 1 секунда, потом 2, потом 4, потом 8
Так ты даёшь соседу передышку вместо того, чтобы добивать лежащего
Плюс всегда ставь: максимум попыток и максимальную задержку, иначе клиенты будут висеть вечно
🎲 Джиттер - без него всё зря
А вот про это забывают
Даже с экспоненциальной задержкой есть засада: если сотня клиентов упала в одну секунду, то и повторят они синхронно - через 1с все разом, через 2с все разом
Получаются волны, которые снова добивают сервис пачками
Лечится джиттером - случайной добавкой к задержке
Не ровно 2 секунды, а 2 плюс-минус случайные миллисекунды
Тогда клиенты размазываются во времени и не бьют залпом
Мелочь, а именно она превращает ретраи из волн в ровный аккуратный поток
🛡 Что ещё
- Ретрай только идемпотентных операций
Повторять "создать платёж" вслепую - привет, двойное списание (об этом был отдельный пост)
- Уважай Retry-After
Если сервис в ответе явно сказал "приходи через 5 секунд" - слушайся, а не долби по своему расписанию
- Circuit breaker
Если сосед стабильно падает, перестань ходить к нему вообще на какое-то время - дай ему встать, а сам отдай заглушку
Ретраи - штука нужная, но наивный "поймал ошибку - сразу повторил" под нагрузкой не лечит
Пауза с ростом, джиттер и потолок попыток - вот что превращает их из проблемы в защиту
Свой сервис заваливали ретраями? 🤔
#backend #reliability #distributed #dev #programming #devops
🧩 Что тут не так?
База MySQL, таблица с юзерами, кодировка
Юзер обновляет имя:
И вместо успеха прилетает:
Обычные буквы и цифры сохранялись годами, а тут запрос падает на ровном месте
Вопрос: почему невинный цветочек ломает вставку?
Потому что кодировка
Историческая засада: их
А эмодзи (и часть редких иероглифов, старые символы) - это 4 байта
Символ не влезает в отведённое место, база отказывается его писать и кидает ошибку
То есть колонка принимает буквы, кириллицу, латиницу - всё, что укладывается в 3 байта
А как только прилетает 4-байтовый символ, всё ломается
И ловится это не сразу: на тесте с обычными именами чисто, а в проде первый же юзер с эмодзи в нике роняет запрос
🛠 Как чинить
Нужна кодировка
И проверь, что на
Если приложение коннектится в старой
В MySQL
Всегда бери
В Postgres, к слову, такой засады нет - там
Ловили это? Эмодзи в имени, в сообщении, в названии - что вам роняло базу? 🤔
#mysql #database #backend #dev #programming #bugs
База MySQL, таблица с юзерами, кодировка
utf8Юзер обновляет имя:
UPDATE users SET name = 'Аня 🌸' WHERE id = 42;
И вместо успеха прилетает:
Incorrect string value: '\xF0\x9F\x8C\xB8' for column 'name'
Обычные буквы и цифры сохранялись годами, а тут запрос падает на ровном месте
Вопрос: почему невинный цветочек ломает вставку?
Потому что кодировка
utf8 в MySQL - это не полноценный UTF-8Историческая засада: их
utf8 умеет хранить максимум 3 байта на символА эмодзи (и часть редких иероглифов, старые символы) - это 4 байта
Символ не влезает в отведённое место, база отказывается его писать и кидает ошибку
То есть колонка принимает буквы, кириллицу, латиницу - всё, что укладывается в 3 байта
А как только прилетает 4-байтовый символ, всё ломается
И ловится это не сразу: на тесте с обычными именами чисто, а в проде первый же юзер с эмодзи в нике роняет запрос
🛠 Как чинить
Нужна кодировка
utf8mb4 - вот она и есть настоящий полный UTF-8 с поддержкой 4 байт:ALTER TABLE users
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
И проверь, что на
utf8mb4 переведены все три уровня: сама база, таблицы и подключение приложения к базеЕсли приложение коннектится в старой
utf8, эмодзи всё равно потеряются по дороге, даже когда таблица уже правильнаяВ MySQL
utf8 - это минаВсегда бери
utf8mb4 с самого начала, тогда эмодзи, редкие языки и всякая экзотика не будут ронять тебе вставкиВ Postgres, к слову, такой засады нет - там
UTF8 сразу полныйЛовили это? Эмодзи в имени, в сообщении, в названии - что вам роняло базу? 🤔
#mysql #database #backend #dev #programming #bugs
🪵 Логи, в которые невозможно ничего понять
Прод сломался, открываешь логи - а там каша, по которой не понять, что случилось
Логи есть, а толку ноль
Как пишут обычно и как надо
❌ Плохо:
✅ Хорошо:
Логи читает не глаз, а поиск по ним
"ошибка" без контекста - мусор, по которому ничего не найдёшь
Пиши что случилось плюс ключевые поля: кто, что, почему
Такой лог фильтруется и собирается в статистику
❌ Плохо:
✅ Хорошо: уровни по делу
Когда всё подряд на уровне info, в потоке событий тонет важное
Раздели: debug для отладки, info для нормальных событий, warning для странного, но терпимого, error для сломанного
В проде отключаешь шум и оставляешь важное
❌ Плохо:
✅ Хорошо: без секретов
Пароли, номера карт, токены, персоналку в логи лить нельзя
Логи оседают в куче систем, их видят десятки людей, они утекают
Маскируй чувствительное или не пиши вовсе - это требование безопасности, а не просто аккуратность
❌ Плохо:
Один запрос размазан по логам без связи
✅ Хорошо: сквозной id запроса
Когда параллельно летят сотни запросов, их логи перемешаны в один поток
Без общего идентификатора не понять, какие строчки относятся к одному запросу
Протащи request_id через все логи запроса (а в микросервисах - через все сервисы), и вытащишь всю цепочку одним фильтром
🎯 Простой критерий
Лог ты пишешь не себе и не сейчас
Его будет читать дежурный в три ночи - возможно, не ты, а тот, кто твой код видит впервые
И момент инцидента уже не переиграть: воспроизвести на нём нельзя, лог - единственный след
Так что проверяй каждую строчку одним вопросом: хватит ли этого незнакомому человеку, чтобы понять, что сломалось, без похода в исходники?
Хватит - лог свою работу сделал
Расскажите в комментариях, самые смешные логи, которые вы встречали на проектах и продуктах, где работали? 🤔
#backend #logging #observability #dev #programming #devops
Прод сломался, открываешь логи - а там каша, по которой не понять, что случилось
Логи есть, а толку ноль
Как пишут обычно и как надо
❌ Плохо:
log.info("ошибка")✅ Хорошо:
log.error("платёж отклонён", user_id=42, order_id=1001, reason="insufficient_funds")Логи читает не глаз, а поиск по ним
"ошибка" без контекста - мусор, по которому ничего не найдёшь
Пиши что случилось плюс ключевые поля: кто, что, почему
Такой лог фильтруется и собирается в статистику
❌ Плохо:
log.info("готово")
log.info("всё ок")✅ Хорошо: уровни по делу
Когда всё подряд на уровне info, в потоке событий тонет важное
Раздели: debug для отладки, info для нормальных событий, warning для странного, но терпимого, error для сломанного
В проде отключаешь шум и оставляешь важное
❌ Плохо:
log.info(f"юзер {user} с картой {card_number} и токеном {token}")✅ Хорошо: без секретов
Пароли, номера карт, токены, персоналку в логи лить нельзя
Логи оседают в куче систем, их видят десятки людей, они утекают
Маскируй чувствительное или не пиши вовсе - это требование безопасности, а не просто аккуратность
❌ Плохо:
Один запрос размазан по логам без связи
✅ Хорошо: сквозной id запроса
Когда параллельно летят сотни запросов, их логи перемешаны в один поток
Без общего идентификатора не понять, какие строчки относятся к одному запросу
Протащи request_id через все логи запроса (а в микросервисах - через все сервисы), и вытащишь всю цепочку одним фильтром
🎯 Простой критерий
Лог ты пишешь не себе и не сейчас
Его будет читать дежурный в три ночи - возможно, не ты, а тот, кто твой код видит впервые
И момент инцидента уже не переиграть: воспроизвести на нём нельзя, лог - единственный след
Так что проверяй каждую строчку одним вопросом: хватит ли этого незнакомому человеку, чтобы понять, что сломалось, без похода в исходники?
Хватит - лог свою работу сделал
Расскажите в комментариях, самые смешные логи, которые вы встречали на проектах и продуктах, где работали? 🤔
#backend #logging #observability #dev #programming #devops
❗️Напоминаем, что уже завтра продет наш вебинар с «Белым кодом» о том, как выбрать шину данных и какая команда нужна для ее внедрения и проверки.
🔥 Регистрация еще идет здесь: https://evapps.timepad.ru/event/4161537/
🔥 Регистрация еще идет здесь: https://evapps.timepad.ru/event/4161537/