Проектируем полезные логи для бэкендеров
Большинство девов логируют реактивно: добавили
1. Начинай со структурированного логирования: вместо обычного текста пиши JSON с едиными, предсказуемыми полями, по которым можно нормально искать.
Тогда твой мониторинг сможет алертить, фильтровать и строить графики, а логи можно будет “запрашивать” почти как базу данных.
2. Используй уровни логов по делу.
• INFO = ожидаемые бизнес-события (например, пользователь залогинился)
• WARN = обработанные аномалии (например, ретраим упавший вызов)
• ERROR = реальные фейлы, которые требуют внимания
• DEBUG = детали для разработки, в проде выключать
Большинство команд ставят ERROR вообще на все. Не надо так.
3. Всегда держи “чеклист контекста”:
Само сообщение лога почти бесполезно. Ценность дают контекстные поля, которые к нему приклеены, и именно они делают лог пригодным для действий.
• Идентификаторы:
• Значения:
• Состояние:
• Детали ошибки:
• Время:
Идентификаторы отвечают на “кто и что”, значения на “сколько”, состояние на “где мы в процессе”, детали ошибки на “что пошло не так”, а время на “как долго это длилось”.
4. Используй correlation ID:
Генерируй уникальный
Без этого дебажить параллельные запросы становится проблемнее.
5. Никогда не логируй чувствительные данные:
Не логируй пароли, токены, номера карт, сырой SQL с пользовательскими значениями, данные, возвращаемые запросами, и т.п.
Цель такая: любой инженер во время инцидента должен открыть логи и сразу понять, что произошло, даже не лазя в исходники.
👉 @BackendPortal
Большинство девов логируют реактивно: добавили
console.log, когда что-то сломалось, починили и пошли дальше. В итоге получаются в основном шумные логи, которые вообще ничего не говорят, когда прод полыхает.1. Начинай со структурированного логирования: вместо обычного текста пиши JSON с едиными, предсказуемыми полями, по которым можно нормально искать.
Тогда твой мониторинг сможет алертить, фильтровать и строить графики, а логи можно будет “запрашивать” почти как базу данных.
2. Используй уровни логов по делу.
• INFO = ожидаемые бизнес-события (например, пользователь залогинился)
• WARN = обработанные аномалии (например, ретраим упавший вызов)
• ERROR = реальные фейлы, которые требуют внимания
• DEBUG = детали для разработки, в проде выключать
Большинство команд ставят ERROR вообще на все. Не надо так.
3. Всегда держи “чеклист контекста”:
Само сообщение лога почти бесполезно. Ценность дают контекстные поля, которые к нему приклеены, и именно они делают лог пригодным для действий.
• Идентификаторы:
user_id, order_id, request_id• Значения:
amount, count, size• Состояние:
status, current_step• Детали ошибки:
error_code, exception• Время:
duration_msИдентификаторы отвечают на “кто и что”, значения на “сколько”, состояние на “где мы в процессе”, детали ошибки на “что пошло не так”, а время на “как долго это длилось”.
4. Используй correlation ID:
Генерируй уникальный
request_id на каждом входе в систему. Протаскивай его через каждую функцию, сервис и лог.Без этого дебажить параллельные запросы становится проблемнее.
5. Никогда не логируй чувствительные данные:
Не логируй пароли, токены, номера карт, сырой SQL с пользовательскими значениями, данные, возвращаемые запросами, и т.п.
Цель такая: любой инженер во время инцидента должен открыть логи и сразу понять, что произошло, даже не лазя в исходники.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤2
This media is not supported in your browser
VIEW IN TELEGRAM
JWT за 60 секунд
Что такое JWT?
JWT = JSON Web Token
Компактный, URL-safe токен, который используют для:
- аутентификации
- авторизации
- безопасного общения между API
- шаринга идентичности между сервисами
Он цифрово подписан, поэтому его можно проверять и ему можно доверять.
🟢 Зачем вообще нужен JWT
Типичный флоу без JWT:
Пользователь → Приложение → База данных (хранилище сессий)
-сервер хранит сессии
-нужна память/хранилище
-сложно масштабировать в микросервисах
-больше инфраструктурной сложности
-за балансировщиком нужны sticky sessions
-в распределенных системах это плохо масштабируется
🟢 Тут и появляется JWT
- JWT это stateless-аутентификация.
Новый флоу:
Пользователь → Приложение → JWT → Клиент → API
-на сервере не хранится сессия
-токен несет идентичность пользователя и claims
-сервер только проверяет подпись
-отлично подходит для масштабируемых систем
🟢 Полный флоу JWT-запроса
1️⃣ пользователь логинится с кредами
2️⃣ сервер валидирует пользователя
3️⃣ сервер генерирует JWT (Header + Payload + Signature)
4️⃣ клиент сохраняет JWT (обычно в браузере/приложении)
5️⃣ клиент шлет JWT в заголовке Authorization
6️⃣ сервер проверяет подпись
7️⃣ если валидно → доступ выдан
Никакой поход в базу за сессией не нужен.
🟢 Где JWT используют в реальных системах?
-REST API
-аутентификация микросервисов
-OAuth2 / SSO
-API Gateway
-Kubernetes dashboards
-CI/CD инструменты
-мобильные приложения и SPA
-почти каждый современный cloud-native апп использует JWT
🟢 JWT в DevOps и System Design
DevOps-инженеру JWT нужен для:
-проектирования stateless-приложений
-масштабирования за Load Balancer’ами
-внедрения API security
-работы с IAM и OAuth-провайдерами
-защиты коммуникации микросервисов
-уменьшения зависимости от session storage
Stateless auth = лучше масштабируемость + проще инфраструктура
Спасибо, что дочитал.
👉 @BackendPortal
Что такое JWT?
JWT = JSON Web Token
Компактный, URL-safe токен, который используют для:
- аутентификации
- авторизации
- безопасного общения между API
- шаринга идентичности между сервисами
Он цифрово подписан, поэтому его можно проверять и ему можно доверять.
Типичный флоу без JWT:
Пользователь → Приложение → База данных (хранилище сессий)
-сервер хранит сессии
-нужна память/хранилище
-сложно масштабировать в микросервисах
-больше инфраструктурной сложности
-за балансировщиком нужны sticky sessions
-в распределенных системах это плохо масштабируется
- JWT это stateless-аутентификация.
Новый флоу:
Пользователь → Приложение → JWT → Клиент → API
-на сервере не хранится сессия
-токен несет идентичность пользователя и claims
-сервер только проверяет подпись
-отлично подходит для масштабируемых систем
Никакой поход в базу за сессией не нужен.
-REST API
-аутентификация микросервисов
-OAuth2 / SSO
-API Gateway
-Kubernetes dashboards
-CI/CD инструменты
-мобильные приложения и SPA
-почти каждый современный cloud-native апп использует JWT
DevOps-инженеру JWT нужен для:
-проектирования stateless-приложений
-масштабирования за Load Balancer’ами
-внедрения API security
-работы с IAM и OAuth-провайдерами
-защиты коммуникации микросервисов
-уменьшения зависимости от session storage
Stateless auth = лучше масштабируемость + проще инфраструктура
Спасибо, что дочитал.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍4
У тебя SaaS, где есть фича: загрузить и обработать большой файл.
Без брокера:
1. Юзер жмёт Upload → POST /upload
2. Сервер принимает файл и тут же начинает обработку (2 минуты)
3. И только потом отвечает
Если так делают многие, сервер забивается обработкой и перестаёт нормально обслуживать остальные запросы. Логин тормозит. Дашборд грузится долго. Всё начинает ехать.
С брокером (например RabbitMQ):
1. Юзер жмёт Upload
2. Сервер принимает файл и кладёт его в storage
3. Отправляет в RabbitMQ сообщение: “обработать файл ID 1”
(этот сервер здесь Producer)
4. RabbitMQ кладёт сообщение в queue (очередь задач)
5. Сервер отвечает почти сразу
6. RabbitMQ отдаёт это сообщение другому процессу, который забирает файл из storage и обрабатывает (Consumer)
Пайплайн:
Users → Producer → Broker → Consumer
RabbitMQ сам файл не обрабатывает. Он просто хранит задачу и выдаёт её воркеру, когда приходит очередь.
Что ты выигрываешь?
Быстрый ответ на запрос, сервер свободен для других действий (логин, загрузка дашборда), обработку можно масштабировать отдельно, и если что-то упало, задачу можно ретраить.
Но: теперь поток асинхронный, значит нужно делать обработку идемпотентной и нормально продумать ретраи.
Брокер не упрощает систему. Он делает её устойчивее, когда проект начинает расти.
👉 @BackendPortal
Без брокера:
1. Юзер жмёт Upload → POST /upload
2. Сервер принимает файл и тут же начинает обработку (2 минуты)
3. И только потом отвечает
Если так делают многие, сервер забивается обработкой и перестаёт нормально обслуживать остальные запросы. Логин тормозит. Дашборд грузится долго. Всё начинает ехать.
С брокером (например RabbitMQ):
1. Юзер жмёт Upload
2. Сервер принимает файл и кладёт его в storage
3. Отправляет в RabbitMQ сообщение: “обработать файл ID 1”
(этот сервер здесь Producer)
4. RabbitMQ кладёт сообщение в queue (очередь задач)
5. Сервер отвечает почти сразу
6. RabbitMQ отдаёт это сообщение другому процессу, который забирает файл из storage и обрабатывает (Consumer)
Пайплайн:
Users → Producer → Broker → Consumer
RabbitMQ сам файл не обрабатывает. Он просто хранит задачу и выдаёт её воркеру, когда приходит очередь.
Что ты выигрываешь?
Быстрый ответ на запрос, сервер свободен для других действий (логин, загрузка дашборда), обработку можно масштабировать отдельно, и если что-то упало, задачу можно ретраить.
Но: теперь поток асинхронный, значит нужно делать обработку идемпотентной и нормально продумать ретраи.
Брокер не упрощает систему. Он делает её устойчивее, когда проект начинает расти.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍3
Представь, что ты делаешь новостной агрегатор (типа Google News). Одна из самых больших проблем, с которой ты столкнешься, это дедупликация статей среди миллионов документов. Наивные сравнения O(n^2) тебя просто раздавят на масштабе. А реальное решение это MinHash + LSH.
MinHash превращает большое множество в маленькую, фиксированного размера сигнатуру так, что похожесть двух сигнатур приближенно соответствует жаккаровскому сходству (Jaccard similarity) исходных множеств. Jaccard similarity это просто пересечение множеств, деленное на их объединение; мера того, насколько сильно множества перекрываются.
Это быстрый вероятностный способ оценить “насколько эти два документа похожи”, не сравнивая их слово в слово.
Первый шаг это шинглинг: ты разбиваешь каждый документ на перекрывающиеся n-граммы (например, последовательности из 3 слов), а потом запускаешь MinHash на множестве этих шинглов. На выходе MinHash дает компактную сигнатуру, обычно 100-200 хеш-значений.
Ключевое свойство такое: вероятность того, что две сигнатуры разделят одно и то же минимальное хеш-значение, равна Jaccard similarity их исходных множеств шинглов. Так ты оцениваешь сходство, вообще не трогая сырой текст.
Но проблема сравнения все равно остается. Даже с компактными сигнатурами сравнивать каждую пару дорого. Тут и появляется LSH
Ты делишь каждую сигнатуру на b полос по r строк в каждой, и хешируешь каждую полосу в бакет. Два документа, которые достаточно похожи, с высокой вероятностью попадут в один и тот же бакет хотя бы в одной полосе, и уже только эти кандидатные пары ты реально сравниваешь.
Такой подход схлопывает миллиарды сравнений до миллионов, и именно так системы вроде Google News и ранние веб-краулеры дедупили контент на больших объемах. В нескольких гугловых статьях и инженерных блогах начала 2000-х прямо упоминается этот подход. Довольно просто и аккуратно.
И как почти всегда на масштабе: тебе не нужна идеальная система детекта похожести. Нужна быстрая и достаточно хорошая, потому что стоимость в итоге и диктует правила игры.
👉 @BackendPortal
MinHash превращает большое множество в маленькую, фиксированного размера сигнатуру так, что похожесть двух сигнатур приближенно соответствует жаккаровскому сходству (Jaccard similarity) исходных множеств. Jaccard similarity это просто пересечение множеств, деленное на их объединение; мера того, насколько сильно множества перекрываются.
Это быстрый вероятностный способ оценить “насколько эти два документа похожи”, не сравнивая их слово в слово.
Первый шаг это шинглинг: ты разбиваешь каждый документ на перекрывающиеся n-граммы (например, последовательности из 3 слов), а потом запускаешь MinHash на множестве этих шинглов. На выходе MinHash дает компактную сигнатуру, обычно 100-200 хеш-значений.
Ключевое свойство такое: вероятность того, что две сигнатуры разделят одно и то же минимальное хеш-значение, равна Jaccard similarity их исходных множеств шинглов. Так ты оцениваешь сходство, вообще не трогая сырой текст.
Но проблема сравнения все равно остается. Даже с компактными сигнатурами сравнивать каждую пару дорого. Тут и появляется LSH
Ты делишь каждую сигнатуру на b полос по r строк в каждой, и хешируешь каждую полосу в бакет. Два документа, которые достаточно похожи, с высокой вероятностью попадут в один и тот же бакет хотя бы в одной полосе, и уже только эти кандидатные пары ты реально сравниваешь.
Такой подход схлопывает миллиарды сравнений до миллионов, и именно так системы вроде Google News и ранние веб-краулеры дедупили контент на больших объемах. В нескольких гугловых статьях и инженерных блогах начала 2000-х прямо упоминается этот подход. Довольно просто и аккуратно.
И как почти всегда на масштабе: тебе не нужна идеальная система детекта похожести. Нужна быстрая и достаточно хорошая, потому что стоимость в итоге и диктует правила игры.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2😁2🔥1
Ускорь JSON-ответы FastAPI в 2 раза, а то и больше
1. Обновись до вышедшего FastAPI 0.131.0
2. Объявляй response model (тип возврата)
После этого🦀
https://fastapi.tiangolo.com/advanced/custom-response/#json-performance
👉 @BackendPortal
1. Обновись до вышедшего FastAPI 0.131.0
2. Объявляй response model (тип возврата)
После этого
@pydantic будет заниматься JSON-сериализацией на стороне Rust https://fastapi.tiangolo.com/advanced/custom-response/#json-performance
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍5
Сколько бы блогов, книг или статей ты ни прочитал, прототипирование все равно остается самым быстрым способом что-то понять. Вот мой воркфлоу:
- держу GitHub-репозиторий с названием
- каждая папка это один эксперимент
- если что-то кажется интересным, иду и реализую
- четко формулирую, что именно хочу понять
- нахожу абсолютный минимум того, что нужно
- пишу код, запускаю, итерируюсь
Ключевой момент тут это найти минимально необходимое, самый-самый минимум кода, который поможет тебе собрать нужное понимание. Сначала ты почти всегда будешь писать больше, чем нужно, но со временем начнешь подставлять данные, добавлять
И да, я понимаю, есть соблазн заопенсорсить прототип или превратить его в проект или стартап. Не надо. Помни, цель тут понять. Как только понял, все, закончил, двигаешься дальше.
Вчера, например, я собрал и реализовал разные типы
👉 @BackendPortal
- держу GitHub-репозиторий с названием
prototypes- каждая папка это один эксперимент
- если что-то кажется интересным, иду и реализую
- четко формулирую, что именно хочу понять
- нахожу абсолютный минимум того, что нужно
- пишу код, запускаю, итерируюсь
Ключевой момент тут это найти минимально необходимое, самый-самый минимум кода, который поможет тебе собрать нужное понимание. Сначала ты почти всегда будешь писать больше, чем нужно, но со временем начнешь подставлять данные, добавлять
sleep, мокать и делать правильные допущения.И да, я понимаю, есть соблазн заопенсорсить прототип или превратить его в проект или стартап. Не надо. Помни, цель тут понять. Как только понял, все, закончил, двигаешься дальше.
Вчера, например, я собрал и реализовал разные типы
join-ов и прогнал бенчмарки, чтобы посмотреть, как они реально себя ведут по производительности. Теорию я и так знал, но прототип дал мне реальные, пусть и грубые, цифры.Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🔥4
Большинство думают, что каждый
Это не так.
Разные языки, разные алгоритмы:
> C++ → Introsort
>
> Python → Timsort
>
> Java → Зависит от типа
>
>
> JavaScript → Зависит от движка
> Chrome V8 = гибрид в стиле Timsort
> Firefox = гибрид Merge Sort
> C → Нет фиксированной гарантии
>
> Go → PDQSort + Insertion
> Rust → Выбираешь сам
>
>
> Swift → Интроспективный гибрид
> Ближе к PDQSort + Insertion
Один и тот же
👉 @BackendPortal
.sort() работает одинаково во всех языках.Это не так.
Разные языки, разные алгоритмы:
> C++ → Introsort
>
std::sort() = Quicksort + Heapsort + Insertion> Python → Timsort
>
list.sort() = Stable (стабильная сортировка)> Java → Зависит от типа
>
Arrays.sort(Object[]) = Timsort (стабильная)>
Arrays.sort(int[]) = Dual-Pivot Quicksort> JavaScript → Зависит от движка
> Chrome V8 = гибрид в стиле Timsort
> Firefox = гибрид Merge Sort
> C → Нет фиксированной гарантии
>
qsort() обычно Quicksort> Go → PDQSort + Insertion
> Rust → Выбираешь сам
>
.sort() = стабильная Merge Sort>
.sort_unstable() = PDQSort> Swift → Интроспективный гибрид
> Ближе к PDQSort + Insertion
Один и тот же
.sort(), но в каждом языке за ним могут стоять разные алгоритмы.Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3
Если ты учишь backend в 2026, прокачай вот эти 8 тем:
1️⃣ Дизайн REST API
↓
2️⃣ Аутентификация (JWT / OAuth)
↓
3️⃣ Индексы в базе данных
↓
4️⃣ Кэширование (Redis)
↓
5️⃣ Rate limiting
↓
6️⃣ Логирование и мониторинг
↓
7️⃣ База по Docker
↓
8️⃣ Основы system design
👉 @BackendPortal
↓
↓
↓
↓
↓
↓
↓
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍3
Что такое LGTM?
L – Loki
структурированные, поисковые, коррелируемые логи.
G – Grafana
визуализация всего, что происходит в системе.
T – Tempo
распределенный трейсинг между сервисами.
M – Mimir
истина про поведение системы в виде таймсерий.
Вместе они отвечают на три ключевых вопроса:
что произошло? → логи
насколько все плохо? → метрики
где конкретно тормозит? → трейсы
Без LGTM дебаг распределенных систем это гадание на кофейной гуще.❤️
Плейлист-курс по LGTM (Loki + Grafana + Tempo + Mimir)
👉 @BackendPortal
L – Loki
структурированные, поисковые, коррелируемые логи.
G – Grafana
визуализация всего, что происходит в системе.
T – Tempo
распределенный трейсинг между сервисами.
M – Mimir
истина про поведение системы в виде таймсерий.
Вместе они отвечают на три ключевых вопроса:
что произошло? → логи
насколько все плохо? → метрики
где конкретно тормозит? → трейсы
Без LGTM дебаг распределенных систем это гадание на кофейной гуще.
Плейлист-курс по LGTM (Loki + Grafana + Tempo + Mimir)
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤2💊1
Ваша база PostgreSQL уперлась в потолок.
Текущие метрики:
- База 2 ТБ на
- 85% read, 15% write
- Лаг read-replica: в среднем 200 мс, пики до 2 секунд
- Количество подключений в пик упирается в максимум (500)
- Самые медленные запросы: сложные JOIN’ы по 4 таблицам с 100M+ строк
- VACUUM не успевает, растет число мертвых кортежей (dead tuples)
- Хранилище растет на 50 ГБ/месяц
Команда спорит, что делать:
- Добавить PgBouncer для пуллинга соединений
- Внедрить read-replica + роутинг запросов
- Шардировать базу
- Вынести горячие таблицы в DynamoDB
- Залить деньгами (апгрейд до
В этом квартале можно выбрать только 2.
Какие 2 и в каком порядке?
👉 @BackendPortal
Текущие метрики:
- База 2 ТБ на
db.r5.4xlarge- 85% read, 15% write
- Лаг read-replica: в среднем 200 мс, пики до 2 секунд
- Количество подключений в пик упирается в максимум (500)
- Самые медленные запросы: сложные JOIN’ы по 4 таблицам с 100M+ строк
- VACUUM не успевает, растет число мертвых кортежей (dead tuples)
- Хранилище растет на 50 ГБ/месяц
Команда спорит, что делать:
- Добавить PgBouncer для пуллинга соединений
- Внедрить read-replica + роутинг запросов
- Шардировать базу
- Вынести горячие таблицы в DynamoDB
- Залить деньгами (апгрейд до
db.r5.12xlarge)В этом квартале можно выбрать только 2.
Какие 2 и в каком порядке?
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5
Нашел эту статью о транзакциях в базах данных и реализации ACID в реляционных базах данных действительно интересной, рекомендую прочитать.
https://www.datacamp.com/blog/acid-transactions
👉 @BackendPortal
https://www.datacamp.com/blog/acid-transactions
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥2
Паттерны сложности по времени, которые надо знать, чтобы не тупить на собесах
1️⃣
2️⃣ Два вложенных цикла
3️⃣
(потому что суммарно
4️⃣
5️⃣
6️⃣ Два раздельных цикла по
(в Big-O константы отбрасываем:
7️⃣
8️⃣
9️⃣
(типичный merge sort)
1️⃣ 0️⃣
(наивная рекурсия Фибоначчи, без мемоизации)
Если умеешь сходу маппить “паттерн → сложность”, вопросы про time complexity на интервью перестают быть проблемой.
👉 @BackendPortal
for (i = 0; i < n; i++) → O(n)(n × n) → O(n²)for (j = 0; j < i; j++) внутри внешнего цикла → O(n²)(потому что суммарно
1 + 2 + ... + (n-1))i = i * 2 пока i < n → O(log n)i = i / 2 пока i > 1 → O(log n)n → O(n)(в Big-O константы отбрасываем:
O(n) + O(n) = O(n))while (n > 0) n /= 2 → O(log n)T(n) = T(n-1) + O(1) → O(n)T(n) = 2T(n/2) + O(n) → O(n log n)(типичный merge sort)
T(n) = T(n-1) + T(n-2) → O(2ⁿ)(наивная рекурсия Фибоначчи, без мемоизации)
Если умеешь сходу маппить “паттерн → сложность”, вопросы про time complexity на интервью перестают быть проблемой.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7
p50, p95 и p99.
Сколько тут этих p и что они значат?
Коротко: p = percentile (процентиль).
Он показывает распределение производительности, а не среднее значение.
Сколько вообще бывает этих p?
Процентили есть от p0 до p100, но на практике обычно смотрят на:
• p50 -> медиана
• p90 -> первые “подтупливания”
• p95 -> базовая линия для SLA
• p99 -> tail latency (хвостовая задержка)
• p99.9 -> системы с ультра-надежностью (Google и Amazon следят за p99.9)
Представь 100 пользователей, которые бьют в твой API.
p50 = 120ms -> 50 пользователей уложились в 120ms или быстрее, 50 были медленнее.
p95 = 800ms -> 95 пользователей уложились в 800ms или быстрее, 5 были медленнее.
p99 = 2.5s -> 99 пользователей уложились в 2.5s или быстрее, 1 был медленнее (вот тут и живет фрустрация).
Почему трекать процентили, а не среднее?
Потому что средняя задержка умеет прятать боль.
Пример:
100ms
110ms
120ms
130ms
5000ms
Среднее ≈ 1,092ms❌
p50 = 120ms
p95 = 5000ms✅
Зная p50, p95 и p99, ты можешь:
• заметить всплески задержек до того, как все упадет
• понять реальный UX пользователей
• найти проблемы со скейлингом и контеншеном
• безопаснее настроить ретраи, таймауты и backpressure
• задать адекватные SLA
• не допустить системы “быстро, но ненадежно”
👉 @BackendPortal
Сколько тут этих p и что они значат?
Коротко: p = percentile (процентиль).
Он показывает распределение производительности, а не среднее значение.
Сколько вообще бывает этих p?
Процентили есть от p0 до p100, но на практике обычно смотрят на:
• p50 -> медиана
• p90 -> первые “подтупливания”
• p95 -> базовая линия для SLA
• p99 -> tail latency (хвостовая задержка)
• p99.9 -> системы с ультра-надежностью (Google и Amazon следят за p99.9)
Представь 100 пользователей, которые бьют в твой API.
p50 = 120ms -> 50 пользователей уложились в 120ms или быстрее, 50 были медленнее.
p95 = 800ms -> 95 пользователей уложились в 800ms или быстрее, 5 были медленнее.
p99 = 2.5s -> 99 пользователей уложились в 2.5s или быстрее, 1 был медленнее (вот тут и живет фрустрация).
Почему трекать процентили, а не среднее?
Потому что средняя задержка умеет прятать боль.
Пример:
100ms
110ms
120ms
130ms
5000ms
Среднее ≈ 1,092ms
p50 = 120ms
p95 = 5000ms
Зная p50, p95 и p99, ты можешь:
• заметить всплески задержек до того, как все упадет
• понять реальный UX пользователей
• найти проблемы со скейлингом и контеншеном
• безопаснее настроить ретраи, таймауты и backpressure
• задать адекватные SLA
• не допустить системы “быстро, но ненадежно”
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥2
Есть очень интересный подход к хешированию, называется Robin Hood Hashing. Это как раз из тех идей, которые одновременно простые и элегантные. Смотри.
В обычных хеш-таблицах с open addressing, когда случается коллизия, ты просто идёшь дальше по слотам, пока не найдёшь пустой. Проблема в том, что одни ключи оказываются почти рядом со своей “идеальной” позицией, а другим не везёт и их уносит далеко.
Из-за этого у неудачливых ключей получаются длинные probe-sequence, и поиск начинает заметно проседать.
Robin Hood Hashing чинит это минимальным изменением стратегии вставки. Когда вставляешь новый ключ, ты сравниваешь, насколько он далеко от своей “домашней” позиции, с тем ключом, который сейчас занимает слот. Если новый ключ “беднее” (то есть он уже дальше от своего идеала), он забирает место, а вытесненный ключ продолжает пробинг дальше.
По сути, ты “крадёшь у богатых” (у ключей, которые уютно сидят рядом с домом) и “отдаёшь бедным” (тем, кого утащило далеко). Отсюда и название Robin Hood.
В итоге дисперсия длины пробинга по всем ключам остаётся очень маленькой. Никого не оставляют сильно “позади”.
Этот алгоритм можно встретить в стандартной реализации HashMap в Rust, в нескольких высокопроизводительных индексах баз данных и в системах управления памятью, где важна cache efficiency. Он ещё популярен в хеш-таблицах, где нужна предсказуемая скорость поиска, а не просто хороший средний случай.
На бумаге худший случай не меняется, но на практике распределение ключей получается ближе к идеальным слотам, и поиск становится ощутимо быстрее, особенно когда таблица плотная.
Забавно, что одно маленькое правило на этапе вставки так сильно влияет на общую производительность хеш-таблицы.
👉 @BackendPortal
В обычных хеш-таблицах с open addressing, когда случается коллизия, ты просто идёшь дальше по слотам, пока не найдёшь пустой. Проблема в том, что одни ключи оказываются почти рядом со своей “идеальной” позицией, а другим не везёт и их уносит далеко.
Из-за этого у неудачливых ключей получаются длинные probe-sequence, и поиск начинает заметно проседать.
Robin Hood Hashing чинит это минимальным изменением стратегии вставки. Когда вставляешь новый ключ, ты сравниваешь, насколько он далеко от своей “домашней” позиции, с тем ключом, который сейчас занимает слот. Если новый ключ “беднее” (то есть он уже дальше от своего идеала), он забирает место, а вытесненный ключ продолжает пробинг дальше.
По сути, ты “крадёшь у богатых” (у ключей, которые уютно сидят рядом с домом) и “отдаёшь бедным” (тем, кого утащило далеко). Отсюда и название Robin Hood.
В итоге дисперсия длины пробинга по всем ключам остаётся очень маленькой. Никого не оставляют сильно “позади”.
Этот алгоритм можно встретить в стандартной реализации HashMap в Rust, в нескольких высокопроизводительных индексах баз данных и в системах управления памятью, где важна cache efficiency. Он ещё популярен в хеш-таблицах, где нужна предсказуемая скорость поиска, а не просто хороший средний случай.
На бумаге худший случай не меняется, но на практике распределение ключей получается ближе к идеальным слотам, и поиск становится ощутимо быстрее, особенно когда таблица плотная.
Забавно, что одно маленькое правило на этапе вставки так сильно влияет на общую производительность хеш-таблицы.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🔥2🤔1
Похоже, компании реально переосмысляют весь SDLC вокруг агентов и пытаются прийти к миру, где инженеры, возможно, вообще не будут писать ни одной строчки кода руками.
Я тут общался с одним инженерным лидом, и по ощущениям многие уже готовятся к полностью agentic SDLC. В репозиториях начинают появляться файлы под Claude и прочие agent-штуки, под внутренние инструменты пилят MCP, и в целом многое меняется. Код-ревью тоже автоматизируют, и это только один из пунктов.
Вот такие времена :)
👉 @BackendPortal
Я тут общался с одним инженерным лидом, и по ощущениям многие уже готовятся к полностью agentic SDLC. В репозиториях начинают появляться файлы под Claude и прочие agent-штуки, под внутренние инструменты пилят MCP, и в целом многое меняется. Код-ревью тоже автоматизируют, и это только один из пунктов.
Вот такие времена :)
Please open Telegram to view this post
VIEW IN TELEGRAM
💊10🔥6🤔1
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13
Любая база данных делает компромиссы по производительности.
Это не случайность. Это осознанные решения в дизайне, чтобы закрывать нужды конкретных приложений.
Самые частые:
Time / Space
Делаем базу быстрее, расплачиваясь памятью или диском. Храним одни и те же данные в нескольких форматах, заранее считаем агрегаты, используем сжатие и т.д. Классический CS tradeoff.
Durability
Улучшаем latency, ослабляя долговечность. Вы делаете
Read / Write
Типичный пример: B-tree обычно лучше для чтения, LSM лучше для записи. Можно ухудшить чтения ради более высокой пропускной способности на запись.
Startup time
Меняем время ответа на запросы на время старта. Например, делаем индексы непостоянными (non-persistent): при каждом рестарте их нужно пересобирать, зато записи не обязаны явно обновлять индексы на диске.
Consistency
Если eventual consistency ок, это упрощает и ускоряет выполнение запросов. Strong consistency требует больше локов и блокировок.
Нужно заранее понимать свои требования по всем пунктам и выбирать DBMS под них.
👉 @BackendPortal
Это не случайность. Это осознанные решения в дизайне, чтобы закрывать нужды конкретных приложений.
Самые частые:
Time / Space
Делаем базу быстрее, расплачиваясь памятью или диском. Храним одни и те же данные в нескольких форматах, заранее считаем агрегаты, используем сжатие и т.д. Классический CS tradeoff.
Durability
Улучшаем latency, ослабляя долговечность. Вы делаете
fsync сразу или позже? Что для вас durability: 1, 2 или N копий на диске? Нужны копии между несколькими DC?Read / Write
Типичный пример: B-tree обычно лучше для чтения, LSM лучше для записи. Можно ухудшить чтения ради более высокой пропускной способности на запись.
Startup time
Меняем время ответа на запросы на время старта. Например, делаем индексы непостоянными (non-persistent): при каждом рестарте их нужно пересобирать, зато записи не обязаны явно обновлять индексы на диске.
Consistency
Если eventual consistency ок, это упрощает и ускоряет выполнение запросов. Strong consistency требует больше локов и блокировок.
Нужно заранее понимать свои требования по всем пунктам и выбирать DBMS под них.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4
Видел интересное видео от CTO Zerodha про то, как они масштабировали Postgres с 7+ млн таблиц.
Технология просто выносит мозг, а если копнуть глубже, становится еще веселее:
- синхронщина? не масштабируется, значит выкидываем.
- все в async. тяжелую генерацию отчетов ставим в очередь.
- независимый middleware, которому все равно на базу и на приложение.
- собрали “DungBeetle” на Go: обобщенные, независимые от СУБД HTTP API, чтобы тянуть отчеты из любой базы.
- результаты сливаем в отдельную Results DB, а приложение читает уже оттуда.
Вот так выглядит настоящий масштаб.
Смотреть видео
👉 @BackendPortal
Технология просто выносит мозг, а если копнуть глубже, становится еще веселее:
- синхронщина? не масштабируется, значит выкидываем.
- все в async. тяжелую генерацию отчетов ставим в очередь.
- независимый middleware, которому все равно на базу и на приложение.
- собрали “DungBeetle” на Go: обобщенные, независимые от СУБД HTTP API, чтобы тянуть отчеты из любой базы.
- результаты сливаем в отдельную Results DB, а приложение читает уже оттуда.
Вот так выглядит настоящий масштаб.
Смотреть видео
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5
Хочешь посмотреть, как Java эволюционировала, фича за фичей?
brunoborges сделал офигенный сайт, где изменения языка Java показаны рядом, в формате side-by-side. Идеально для студентов и для тех, кто модернизирует легаси-приложения.
Зацени:
https://javaevolved.github.io
👉 @BackendPortal
brunoborges сделал офигенный сайт, где изменения языка Java показаны рядом, в формате side-by-side. Идеально для студентов и для тех, кто модернизирует легаси-приложения.
Зацени:
https://javaevolved.github.io
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4🤔4