Структура данных B-Tree реально крутая штука. Базы данных используют B-деревья для хранения данных на диске, разбивая всё на страницы , обычно по 4–8 КБ. Все операции ввода-вывода происходят именно с этими страницами как единицами.
Как устроена страница
Один из популярных способов организовать данные внутри страницы, называется slotted page structure.
Она начинается с заголовка, за ним идут указатели на ячейки, которые ссылаются на ячейки данных в конце страницы.
Пока ты добавляешь данные:
указатели растут слева направо,
а сами ячейки растут справа налево,
встречаясь где-то посередине.
Эта середина — формально "свободное место", но прикол в том, что оно может содержать что угодно. Страница и всё дерево вообще не заботятся, что там лежит.
Если на эти байты нет указателей, они просто не существуют для дерева. Там может быть мусор из прошлых операций, но он не читается и не используется.
Буферный пул и «грязные» страницы
Базы данных ещё поддерживают buffer pool, это кэш страниц, загруженных с диска. Эти страницы часто переиспользуются.
И вот тут появляется интересная оптимизация:
вызов memset (который обнуляет память) занимает время, поэтому базы часто вообще не затирают старые данные.
Удаление записи это просто удаление указателя, но данные на диске остаются.
В итоге, старый мусор из памяти может снова попасть на диск, хотя уже не используется. Ни одна другая структура данных, кажется, так не делает.
Зарезервированное место в конце страницы
Помнишь, я говорил, что ячейки растут справа налево?
Если ты специально оставишь немного свободного места в конце страницы до начала области ячеек, B-дерево продолжит работать как ни в чём не бывало.
Эта область не используется самим деревом, и в неё можно записывать что угодно.
SQLite использует этот трюк для фичи reserved space.
Расширения могут сохранять свои данные прямо в каждой странице, не ломая работу дерева.
Например, туда пишут метаданные для шифрования или контрольных сумм.
Кстати, у SQLite есть настройка PRAGMA secure_delete, которая при удалении затирает содержимое нулями, чтобы мусор не утёк никуда , это уже для безопасности.
Если коротко: B-Tree не просто эффективная структура, она ещё и невероятно продумана для работы с реальным диском, где каждая операция чтения и записи стоит дорого.
👉 @BackendPortal
Как устроена страница
Один из популярных способов организовать данные внутри страницы, называется slotted page structure.
Она начинается с заголовка, за ним идут указатели на ячейки, которые ссылаются на ячейки данных в конце страницы.
Пока ты добавляешь данные:
указатели растут слева направо,
а сами ячейки растут справа налево,
встречаясь где-то посередине.
Эта середина — формально "свободное место", но прикол в том, что оно может содержать что угодно. Страница и всё дерево вообще не заботятся, что там лежит.
Если на эти байты нет указателей, они просто не существуют для дерева. Там может быть мусор из прошлых операций, но он не читается и не используется.
Буферный пул и «грязные» страницы
Базы данных ещё поддерживают buffer pool, это кэш страниц, загруженных с диска. Эти страницы часто переиспользуются.
И вот тут появляется интересная оптимизация:
вызов memset (который обнуляет память) занимает время, поэтому базы часто вообще не затирают старые данные.
Удаление записи это просто удаление указателя, но данные на диске остаются.
В итоге, старый мусор из памяти может снова попасть на диск, хотя уже не используется. Ни одна другая структура данных, кажется, так не делает.
Зарезервированное место в конце страницы
Помнишь, я говорил, что ячейки растут справа налево?
Если ты специально оставишь немного свободного места в конце страницы до начала области ячеек, B-дерево продолжит работать как ни в чём не бывало.
Эта область не используется самим деревом, и в неё можно записывать что угодно.
SQLite использует этот трюк для фичи reserved space.
Расширения могут сохранять свои данные прямо в каждой странице, не ломая работу дерева.
Например, туда пишут метаданные для шифрования или контрольных сумм.
Кстати, у SQLite есть настройка PRAGMA secure_delete, которая при удалении затирает содержимое нулями, чтобы мусор не утёк никуда , это уже для безопасности.
Если коротко: B-Tree не просто эффективная структура, она ещё и невероятно продумана для работы с реальным диском, где каждая операция чтения и записи стоит дорого.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
😁2❤1
Платежи не дают второго шанса.
Вот решение, которое стояло перед Марко
Большинство инженеров в первую очередь выбирают скорость.
А потом месяцами пилят возвраты, джобы для сверки и устраняют последствия.
Но платежам плевать на «низкую задержку».
Им важно одно и это не списывать деньги дважды.
Перед ним были такие варианты:
A → Строгая консистентность
Хранить сгенерированный клиентом idempotency key и финальный ответ в надежной БД.
Уникальное ограничение отсекает дубликаты.
Аудируемо. Правильно. Чуть медленнее.
B → Хак с Redis и TTL
SETNX-лок → обработка → кеширование результата.
Очень быстро… пока Redis не перезапустится, TTL не сгорит раньше времени или не случится разделение сети.
И вот ты уже списал деньги дважды и несколько дней «разбираешься».
C → Вера в магию Kafka
Публикуешь событие и надеешься на “exactly-once” в стриме.
Но в платежах корректность нужна до того, как ты возвращаешь успех.
Это решает масштаб, но не ответственность.
D → Починим потом
Позволяешь двойным списаниям происходить.
Потом проводишь аудит, делаешь возвраты, извиняешься и тд
Дешево в реализации. Дорого в доверии.
Марко выбрал A.
Потому что «быстрые» варианты становятся самыми дорогими, когда в игру вступают реальные деньги.
В финтехе побеждает корректность. Всегда.
Задержка раздражает, но двойное списание уничтожает доверие.
👉 @BackendPortal
Вот решение, которое стояло перед Марко
Большинство инженеров в первую очередь выбирают скорость.
А потом месяцами пилят возвраты, джобы для сверки и устраняют последствия.
Но платежам плевать на «низкую задержку».
Им важно одно и это не списывать деньги дважды.
Перед ним были такие варианты:
A → Строгая консистентность
Хранить сгенерированный клиентом idempotency key и финальный ответ в надежной БД.
Уникальное ограничение отсекает дубликаты.
Аудируемо. Правильно. Чуть медленнее.
B → Хак с Redis и TTL
SETNX-лок → обработка → кеширование результата.
Очень быстро… пока Redis не перезапустится, TTL не сгорит раньше времени или не случится разделение сети.
И вот ты уже списал деньги дважды и несколько дней «разбираешься».
C → Вера в магию Kafka
Публикуешь событие и надеешься на “exactly-once” в стриме.
Но в платежах корректность нужна до того, как ты возвращаешь успех.
Это решает масштаб, но не ответственность.
D → Починим потом
Позволяешь двойным списаниям происходить.
Потом проводишь аудит, делаешь возвраты, извиняешься и тд
Дешево в реализации. Дорого в доверии.
Марко выбрал A.
Потому что «быстрые» варианты становятся самыми дорогими, когда в игру вступают реальные деньги.
В финтехе побеждает корректность. Всегда.
Задержка раздражает, но двойное списание уничтожает доверие.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤4
Векторные базы данных простыми словами
Представь, у тебя есть 10 000 описаний товаров. Пользователь ищет "удобная уличная мебель".
Обычная база данных:
Ищет точные совпадения слов
Находит товары с "удобная" ИЛИ "уличная" ИЛИ "мебель"
Пропускает "уютные кресла для террасы", хотя это то же самое
Ключевые слова — тупой способ поиска
Векторная база данных:
Превращает запрос в набор чисел, отражающих смысл: [0.2, 0.8, 0.1, 0.9, ...]
Каждое описание товара тоже превращается в такие числа
Сравнивает эти "векторы" и ищет похожие по смыслу
Находит "уютные кресла для террасы", потому что векторы похожи
Смысловой поиск — умный поиск
Как это работает
Шаг 1. Превращаем текст в векторы (массивы чисел)
"comfortable chair" → [0.2, 0.7, 0.1, 0.4, ...]
"cozy seat" → [0.3, 0.8, 0.2, 0.5, ...]
Похожие фразы → похожие числа
Это делает AI-модель, например OpenAI embeddings
Шаг 2. Храним векторы
В обычной БД хранят текст
В векторной — массив чисел для каждого объекта
Индексируют их, чтобы быстро искать по схожести
Оптимизировано под "найди похожие", а не "найди точное"
Шаг 3. Ищем по схожести
Запрос: "outdoor furniture"
Превращается в [0.3, 0.6, 0.2, 0.8, ...]
Система ищет ближайшие векторы (через cosine similarity)
Результаты сортируются по степени похожести
Где это используется:
- Поиск товаров, который понимает смысл, а не только слова
- Поиск по документации, который находит релевантные ответы
- Рекомендательные системы
- Чатботы, ищущие похожие вопросы
- Обнаружение аномалий
Популярные векторные базы
Pinecone = управляемая, простая, но дорогая
Weaviate = опенсорс, с кучей функций
Milvus = быстрая и масштабируемая, но сложная
pgvector = расширение для Postgres, простое и удобное
Qdrant = быстрая, написана на Rust
Спорная, но практичная мысль
В большинстве проектов тебе не нужна отдельная векторная база.
Начни с Postgres + pgvector = этого хватает, пока у тебя меньше 1 миллиона векторов.
Когда масштаб вырастет, тогда уже смотри в сторону специализированных решений.
👉 @BackendPortal
Представь, у тебя есть 10 000 описаний товаров. Пользователь ищет "удобная уличная мебель".
Обычная база данных:
Ищет точные совпадения слов
Находит товары с "удобная" ИЛИ "уличная" ИЛИ "мебель"
Пропускает "уютные кресла для террасы", хотя это то же самое
Ключевые слова — тупой способ поиска
Векторная база данных:
Превращает запрос в набор чисел, отражающих смысл: [0.2, 0.8, 0.1, 0.9, ...]
Каждое описание товара тоже превращается в такие числа
Сравнивает эти "векторы" и ищет похожие по смыслу
Находит "уютные кресла для террасы", потому что векторы похожи
Смысловой поиск — умный поиск
Как это работает
Шаг 1. Превращаем текст в векторы (массивы чисел)
"comfortable chair" → [0.2, 0.7, 0.1, 0.4, ...]
"cozy seat" → [0.3, 0.8, 0.2, 0.5, ...]
Похожие фразы → похожие числа
Это делает AI-модель, например OpenAI embeddings
Шаг 2. Храним векторы
В обычной БД хранят текст
В векторной — массив чисел для каждого объекта
Индексируют их, чтобы быстро искать по схожести
Оптимизировано под "найди похожие", а не "найди точное"
Шаг 3. Ищем по схожести
Запрос: "outdoor furniture"
Превращается в [0.3, 0.6, 0.2, 0.8, ...]
Система ищет ближайшие векторы (через cosine similarity)
Результаты сортируются по степени похожести
Где это используется:
- Поиск товаров, который понимает смысл, а не только слова
- Поиск по документации, который находит релевантные ответы
- Рекомендательные системы
- Чатботы, ищущие похожие вопросы
- Обнаружение аномалий
Популярные векторные базы
Pinecone = управляемая, простая, но дорогая
Weaviate = опенсорс, с кучей функций
Milvus = быстрая и масштабируемая, но сложная
pgvector = расширение для Postgres, простое и удобное
Qdrant = быстрая, написана на Rust
Спорная, но практичная мысль
В большинстве проектов тебе не нужна отдельная векторная база.
Начни с Postgres + pgvector = этого хватает, пока у тебя меньше 1 миллиона векторов.
Когда масштаб вырастет, тогда уже смотри в сторону специализированных решений.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥3❤1
Совет по Docker
Как понять, что раздувает образ
Любой Docker-образ состоит из слоев.
Каждая строка в Dockerfile добавляет новый слой.
По этим слоям можно понять, почему образ получается большим, долго собирается или плохо кешируется.
Вот как посмотреть слои и узнать, что именно менялось в каждом из них.
Используй утилиту dive.
Она показывает наглядно:
- какие слои созданы
- какие файлы добавлены или изменены
- сколько места занимает каждый слой
Когда начинаешь изучать слои образа, можно быстро выяснить:
• какая команда в Dockerfile добавляет лишний вес
• как оптимизировать сборку, чтобы образ был меньше и собирался быстрее
Dive также дает оценку «эффективности» образа. Она показывает, насколько много данных дублируется или просто впустую занимает место в слоях.
👉 @BackendPortal
Как понять, что раздувает образ
Любой Docker-образ состоит из слоев.
Каждая строка в Dockerfile добавляет новый слой.
По этим слоям можно понять, почему образ получается большим, долго собирается или плохо кешируется.
Вот как посмотреть слои и узнать, что именно менялось в каждом из них.
Используй утилиту dive.
Она показывает наглядно:
- какие слои созданы
- какие файлы добавлены или изменены
- сколько места занимает каждый слой
Когда начинаешь изучать слои образа, можно быстро выяснить:
• какая команда в Dockerfile добавляет лишний вес
• как оптимизировать сборку, чтобы образ был меньше и собирался быстрее
Dive также дает оценку «эффективности» образа. Она показывает, насколько много данных дублируется или просто впустую занимает место в слоях.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤3🔥2
Это моя любимая статья про внутренности B-деревьев.
Джереми Коул работал с MySQL в Twitter, Google и Shopify. Один из самых прошаренных MySQL-инженеров в мире, а его блог просто кладезь полезной инфы.
👉 @BackendPortal
Джереми Коул работал с MySQL в Twitter, Google и Shopify. Один из самых прошаренных MySQL-инженеров в мире, а его блог просто кладезь полезной инфы.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Реализовали ограничение API по IP. 1000 запросов в час на один IP. Идеальная защита от злоупотреблений.
Неделя спустя: легитимный клиент уперся в лимит. Пожаловался, что у них сервис сломался.
Они сидели за корпоративным NAT. 500 сотрудников делили один IP. Лимит кончился за 20 минут.
Переключили ограничение на API-ключ. Проблема решена.
Еще через неделю: бот-атака. 50 000 запросов в час. Разные API-ключи. Абьюз бесплатного тарифа.
Сделали ограничение и по IP, и по API-ключу. Проблема решена.
Еще через неделю: DDoS. 10 000 разных IP и 10 000 разных бесплатных API-ключей.
Rate limiting оказался бесполезен против распределенной атаки.
Настоящее решение:
Ограничение по IP для неавторизованных эндпоинтов
Ограничение по API-ключу для авторизованных эндпоинтов
Анализ поведения для поиска подозрительных паттернов
CAPTCHA для подозрительного трафика
CloudFlare спереди для защиты от DDoS
Лимиты по стоимости на бесплатный тариф
Нет одной универсальной стратегии rate limiting. Нужны слои. IP, пользователь, аккаунт, поведение и внешняя DDoS-защита.
👉 @BackendPortal
Неделя спустя: легитимный клиент уперся в лимит. Пожаловался, что у них сервис сломался.
Они сидели за корпоративным NAT. 500 сотрудников делили один IP. Лимит кончился за 20 минут.
Переключили ограничение на API-ключ. Проблема решена.
Еще через неделю: бот-атака. 50 000 запросов в час. Разные API-ключи. Абьюз бесплатного тарифа.
Сделали ограничение и по IP, и по API-ключу. Проблема решена.
Еще через неделю: DDoS. 10 000 разных IP и 10 000 разных бесплатных API-ключей.
Rate limiting оказался бесполезен против распределенной атаки.
Настоящее решение:
Ограничение по IP для неавторизованных эндпоинтов
Ограничение по API-ключу для авторизованных эндпоинтов
Анализ поведения для поиска подозрительных паттернов
CAPTCHA для подозрительного трафика
CloudFlare спереди для защиты от DDoS
Лимиты по стоимости на бесплатный тариф
Нет одной универсальной стратегии rate limiting. Нужны слои. IP, пользователь, аккаунт, поведение и внешняя DDoS-защита.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤17👍5
Большинство проблем с производительностью возникают не из-за плохих запросов.
А из-за кривых таблиц.
Каждый CREATE TABLE это решение, которое потом аукнется:
• типы данных
• индексы
• ограничения
• партиционирование
Мой друг Рауль, собрал то, чему его научили годы проектирования и перепроектирования баз.
Почему реальная производительность начинается именно со схемы.
Читать тут → ссылка
👉 @BackendPortal
А из-за кривых таблиц.
Каждый CREATE TABLE это решение, которое потом аукнется:
• типы данных
• индексы
• ограничения
• партиционирование
Мой друг Рауль, собрал то, чему его научили годы проектирования и перепроектирования баз.
Почему реальная производительность начинается именно со схемы.
Читать тут → ссылка
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤2
Foreign keys и constraints. Многие путают эти понятия.
Foreign key: столбец, который устанавливает связь между двумя таблицами. Обычно это столбец в одной таблице, который хранит значения первичного ключа из другой таблицы , чтобы можно было соединить их в запросе.
Foreign key constraint: механизм, который заставляет СУБД контролировать целостность внешних ключей. Он гарантирует, что значения во внешнем ключе (post -> user_id) ссылаются на реальную запись в другой таблице (user -> id).
Constraints это полезная фича (помогает с той самой буквой C в ACID), но за удобство приходится платить. Вставки, обновления и удаления на таблицах с constraints требуют дополнительных вычислений и IO для проверки.
Если их убрать, можно ускорить систему, но тогда ответственность за поддержание связей перекладывается на сторону приложения. Приложение должно гарантировать, что после транзакций отношения между данными остаются валидными.
Базы данных, как мир бесконечных компромиссов.🚬
👉 @BackendPortal
Foreign key: столбец, который устанавливает связь между двумя таблицами. Обычно это столбец в одной таблице, который хранит значения первичного ключа из другой таблицы , чтобы можно было соединить их в запросе.
Foreign key constraint: механизм, который заставляет СУБД контролировать целостность внешних ключей. Он гарантирует, что значения во внешнем ключе (post -> user_id) ссылаются на реальную запись в другой таблице (user -> id).
Constraints это полезная фича (помогает с той самой буквой C в ACID), но за удобство приходится платить. Вставки, обновления и удаления на таблицах с constraints требуют дополнительных вычислений и IO для проверки.
Если их убрать, можно ускорить систему, но тогда ответственность за поддержание связей перекладывается на сторону приложения. Приложение должно гарантировать, что после транзакций отношения между данными остаются валидными.
Базы данных, как мир бесконечных компромиссов.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5
API ускорили, трафик похудел
В продакшн завезли gzip-сжатие. Размер ответов упал примерно на 85%, а задержка почти на 60%.
До:
Средний ответ 240 KB
Передача ~400 мс
Трафик обходился в ~220 $ в месяц
После:
Средний ответ 36 KB
Передача ~48 мс
Трафик снизился до ~70 $ в месяц
Добавили compression middleware и сразу получили:
Загрузка быстрее на 88%
Минус 68% по расходам на пропускную способность
Маленькое изменение. Большая разница.
👉 @BackendPortal
В продакшн завезли gzip-сжатие. Размер ответов упал примерно на 85%, а задержка почти на 60%.
До:
Средний ответ 240 KB
Передача ~400 мс
Трафик обходился в ~220 $ в месяц
После:
Средний ответ 36 KB
Передача ~48 мс
Трафик снизился до ~70 $ в месяц
Добавили compression middleware и сразу получили:
Загрузка быстрее на 88%
Минус 68% по расходам на пропускную способность
Маленькое изменение. Большая разница.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤6
Наш основной файл состояния Terraform весил 15 МБ. В нём была описана вся наша AWS-организация.
terraform plan занимал 20 минут и часто падал по таймауту.
Никто не хотел к нему прикасаться. Это был наш самый большой технический долг.
Рефакторинг занял 3 месяца:
- Инфраструктуру не меняли.
- Через
- Terragrunt использовали для оркестрации зависимостей между новыми маленькими state-ами.
Теперь план для одного приложения занимает 30 секунд. Наконец-то можно безопасно вносить изменения.
👉 @BackendPortal
terraform plan занимал 20 минут и часто падал по таймауту.
Никто не хотел к нему прикасаться. Это был наш самый большой технический долг.
Рефакторинг занял 3 месяца:
- Инфраструктуру не меняли.
- Через
terraform state mv переносили ресурсы из огромного state-файла в более мелкие логические (например, networking.tfstate, security.tfstate, app-foo.tfstate).- Terragrunt использовали для оркестрации зависимостей между новыми маленькими state-ами.
Теперь план для одного приложения занимает 30 секунд. Наконец-то можно безопасно вносить изменения.
Please open Telegram to view this post
VIEW IN TELEGRAM
Пентестер получил root-доступ к нашему Kubernetes-кластера за 15 минут. Вот что он эксплуатировал.
Цепочка атаки:
- Нашёл открытый Kubernetes Dashboard (это наш косяк)
- На Dashboard был service account только с view-доступом (мы считали это безопасным)
- Service account мог читать Secrets во всех namespaces
- В одном из Secrets были AWS-креды
- С этими кредами получил доступ к EC2 instance profile
- Instance profile имел полный Kubernetes admin через IAM
- Через kubectl поднял привилегированный pod
- Сделал escape на node
- Root-доступ ко всему кластеру
Что мы думали сделали правильно:
- Dashboard был read-only
- Secrets были зашифрованы at rest
- NetworkPolicy настроены
- Регулярные безопасности обновления
Что мы упустили:
- Dashboard вообще не должен быть доступен снаружи
- Service accounts должны работать по принципу наименьших привилегий
- Secrets не должны хранить AWS-креды (нужен IRSA)
- Pod Security Policies не были включены
- Узлы не были достаточно защищены
Исправление заняло 2 недели:
- Полностью убрали Kubernetes Dashboard
- Внедрили IRSA для всех pod'ов, которым нужен доступ к AWS
- Включили строгие PSP / Pod Security Standards
- Провели аудит всех RBAC-разрешений
- Регулярные пентесты
Стоимость: 24K $ за пентест
Ценность: предотвратили потенциально катастрофический взлом
👉 @BackendPortal
Цепочка атаки:
- Нашёл открытый Kubernetes Dashboard (это наш косяк)
- На Dashboard был service account только с view-доступом (мы считали это безопасным)
- Service account мог читать Secrets во всех namespaces
- В одном из Secrets были AWS-креды
- С этими кредами получил доступ к EC2 instance profile
- Instance profile имел полный Kubernetes admin через IAM
- Через kubectl поднял привилегированный pod
- Сделал escape на node
- Root-доступ ко всему кластеру
Что мы думали сделали правильно:
- Dashboard был read-only
- Secrets были зашифрованы at rest
- NetworkPolicy настроены
- Регулярные безопасности обновления
Что мы упустили:
- Dashboard вообще не должен быть доступен снаружи
- Service accounts должны работать по принципу наименьших привилегий
- Secrets не должны хранить AWS-креды (нужен IRSA)
- Pod Security Policies не были включены
- Узлы не были достаточно защищены
Исправление заняло 2 недели:
- Полностью убрали Kubernetes Dashboard
- Внедрили IRSA для всех pod'ов, которым нужен доступ к AWS
- Включили строгие PSP / Pod Security Standards
- Провели аудит всех RBAC-разрешений
- Регулярные пентесты
Стоимость: 24K $ за пентест
Ценность: предотвратили потенциально катастрофический взлом
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯9🔥5❤4
Нужен был API-gateway для нашего кластера Kubernetes.
Вариант 1: AWS API Gateway + ALB Ingress
Плюсы: полностью управляется AWS, масштабируется как угодно, есть авторизация и прочие нужные штуки
Минусы: дорого. Плюс это два хопа (API-GW -> ALB -> Pod)
Вариант 2: просто использовать AWS ALB Ingress Controller
Плюсы: проще, дешевле, трафик сразу в под
Минусы: нет авторизации, нет rate limiting, никаких фич уровня gateway
В итоге выбрали вариант 1. Авторизация, WAF и rate limiting для нас обязательны, и лучше заплатить AWS за готовое решение, чем лепить своё. По сути мы платим за возможности gateway, а не за ingress.
👉 @BackendPortal
Вариант 1: AWS API Gateway + ALB Ingress
Плюсы: полностью управляется AWS, масштабируется как угодно, есть авторизация и прочие нужные штуки
Минусы: дорого. Плюс это два хопа (API-GW -> ALB -> Pod)
Вариант 2: просто использовать AWS ALB Ingress Controller
Плюсы: проще, дешевле, трафик сразу в под
Минусы: нет авторизации, нет rate limiting, никаких фич уровня gateway
В итоге выбрали вариант 1. Авторизация, WAF и rate limiting для нас обязательны, и лучше заплатить AWS за готовое решение, чем лепить своё. По сути мы платим за возможности gateway, а не за ingress.
Please open Telegram to view this post
VIEW IN TELEGRAM
Если можно, делай работу на записи.
Низкая задержка любит предвычисление.
Ситуация у Майи:
В таблице профилей были опциональные поля: display_name, timezone и bio.
Половина строк с NULL.
Чтения должны укладываться в 20 мс при 10k QPS.
Нужно было гарантировать, что все сервисы видят согласованные эффективные значения. Никаких NULL ни при каких обстоятельствах.
Варианты:
A → COALESCE на чтении
Оставить колонки nullable.
В каждом запросе писать COALESCE(column, default).
Просто, пока не понимаешь, что каждое чтение теперь считает на горячем пути.
Разный хардкод логики по сервисам.
Не индексируется. Медленно.
B → Материализовать effective_ колонки на записи*
Считать один раз при записи или через CDC.
Хранить effective_display_name, effective_timezone и т. п.
Чтения быстрые. Дефолты единые.
Чуть больше работы на запись, зато предсказуемо, кэшируемо, наблюдаемо.
C → DB defaults + миграция на NOT NULL
Выглядит чисто и декларативно.
Но менять живую таблицу на миллионах строк — минное поле.
Дефолты фиксят только новые строки, не легаси.
Сложные правила дефолтов не место тащить в SQL.
D → Пускай каждый потребитель сам подставляет дефолты
Схему трогать не надо.
Но каждый сервис заново изобретает, что такое default.
Через месяц получаешь пять версий правды, ни одна не совпадает.
Майя выбрала B.
Потому что на масштабе чтения доминируют записи.
Каждая миллисекунда, снятая с запроса, накапливается.
Посчитать один раз на записи лучше, чем пересчитывать 10 000 раз в секунду.
Компромиссы, на которые она пошла:
Гоняться за перфом умеют многие.
Сделать это надежным — единицы.
Помни: латентность любит предвычисление.
Пара вариантов E, которые можно рассмотреть:
E1 → Сгенерированные колонки в БД (persisted/generated)
Если база поддерживает stored/generated columns, вынеси вычисление эффективных значений в схему.
Плюсы: считает на записи, индексируется, одна точка истины, одинаково для всех клиентов. Можно поставить NOT NULL и уникальные индексы на effective_* поля.
Минусы: логика дефолтов уезжает в DDL, сложнее версионировать и тестировать, зависит от конкретного движка.
E2 → Write-through кэш эффективного профиля
При записи формируешь компактный документ с effective_* и кладешь в быструю KV-базу или Redis. Чтения идут в кэш, БД остается источником истины и синхронизируется через outbox/CDC.
Плюсы: легко удержать <20 мс при 10k QPS, дешево масштабируется, можно добавить TTL и метрики попаданий.
Минусы: инвалидация и согласованность, прогрев кэша, нужно строго держать идемпотентность и порядок событий.
Небольшая ремарка к варианту A: если все же тянуть COALESCE на чтении, можно частично смягчить минусы выражениями-индексами и оберткой-VIEW/DB-функцией, чтобы стандартизировать логику. Это не отменяет вычислений на горячем пути, но делает их индексируемыми и едиными. Однако при 10k QPS B/E1/E2 обычно выигрывают.
👉 @BackendPortal
Низкая задержка любит предвычисление.
Ситуация у Майи:
В таблице профилей были опциональные поля: display_name, timezone и bio.
Половина строк с NULL.
Чтения должны укладываться в 20 мс при 10k QPS.
Нужно было гарантировать, что все сервисы видят согласованные эффективные значения. Никаких NULL ни при каких обстоятельствах.
Варианты:
A → COALESCE на чтении
Оставить колонки nullable.
В каждом запросе писать COALESCE(column, default).
Просто, пока не понимаешь, что каждое чтение теперь считает на горячем пути.
Разный хардкод логики по сервисам.
Не индексируется. Медленно.
B → Материализовать effective_ колонки на записи*
Считать один раз при записи или через CDC.
Хранить effective_display_name, effective_timezone и т. п.
Чтения быстрые. Дефолты единые.
Чуть больше работы на запись, зато предсказуемо, кэшируемо, наблюдаемо.
C → DB defaults + миграция на NOT NULL
Выглядит чисто и декларативно.
Но менять живую таблицу на миллионах строк — минное поле.
Дефолты фиксят только новые строки, не легаси.
Сложные правила дефолтов не место тащить в SQL.
D → Пускай каждый потребитель сам подставляет дефолты
Схему трогать не надо.
Но каждый сервис заново изобретает, что такое default.
Через месяц получаешь пять версий правды, ни одна не совпадает.
Майя выбрала B.
Потому что на масштабе чтения доминируют записи.
Каждая миллисекунда, снятая с запроса, накапливается.
Посчитать один раз на записи лучше, чем пересчитывать 10 000 раз в секунду.
Компромиссы, на которые она пошла:
Чуть выше латентность записи.
Нужна идемпотентность и контроль конкуренции.
Лаг CDC, если апдейты асинхронные.
Бэкфилл, чтобы поправить существующие данные.
Гоняться за перфом умеют многие.
Сделать это надежным — единицы.
Помни: латентность любит предвычисление.
Пара вариантов E, которые можно рассмотреть:
E1 → Сгенерированные колонки в БД (persisted/generated)
Если база поддерживает stored/generated columns, вынеси вычисление эффективных значений в схему.
Плюсы: считает на записи, индексируется, одна точка истины, одинаково для всех клиентов. Можно поставить NOT NULL и уникальные индексы на effective_* поля.
Минусы: логика дефолтов уезжает в DDL, сложнее версионировать и тестировать, зависит от конкретного движка.
E2 → Write-through кэш эффективного профиля
При записи формируешь компактный документ с effective_* и кладешь в быструю KV-базу или Redis. Чтения идут в кэш, БД остается источником истины и синхронизируется через outbox/CDC.
Плюсы: легко удержать <20 мс при 10k QPS, дешево масштабируется, можно добавить TTL и метрики попаданий.
Минусы: инвалидация и согласованность, прогрев кэша, нужно строго держать идемпотентность и порядок событий.
Небольшая ремарка к варианту A: если все же тянуть COALESCE на чтении, можно частично смягчить минусы выражениями-индексами и оберткой-VIEW/DB-функцией, чтобы стандартизировать логику. Это не отменяет вычислений на горячем пути, но делает их индексируемыми и едиными. Однако при 10k QPS B/E1/E2 обычно выигрывают.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Пул соединений помогает выдерживать большое количество запросов. Но стоит учитывать, что дефолтные настройки могут быть неудачными.
Когда фронтенд подключается к реверс-прокси (load balancer, API-gateway, CDN и т.д.) и отправляет запрос, прокси устанавливает соединение с исходным бэкендом и пробрасывает запрос по этому соединению.
Проблема в том, как обрабатывать большой поток запросов со всех фронтендов. Это легко может перегрузить бэкенд или привести к ошибкам connection reset, если очередь accept у бэкенда забивается.
Обычно используют пул соединений — по сути очередь с ограничением на максимальное число соединений. Запросы попадают в пул с таймаутом и обрабатываются по мере освобождения соединений.
Но даже с пулом, если дефолтные лимиты слишком большие, проблемы могут возникать незаметно. Правильные параметры — это баланс, который зависит от нагрузки и используемых протоколов.
👉 @BackendPortal
Когда фронтенд подключается к реверс-прокси (load balancer, API-gateway, CDN и т.д.) и отправляет запрос, прокси устанавливает соединение с исходным бэкендом и пробрасывает запрос по этому соединению.
Проблема в том, как обрабатывать большой поток запросов со всех фронтендов. Это легко может перегрузить бэкенд или привести к ошибкам connection reset, если очередь accept у бэкенда забивается.
Обычно используют пул соединений — по сути очередь с ограничением на максимальное число соединений. Запросы попадают в пул с таймаутом и обрабатываются по мере освобождения соединений.
Но даже с пулом, если дефолтные лимиты слишком большие, проблемы могут возникать незаметно. Правильные параметры — это баланс, который зависит от нагрузки и используемых протоколов.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Мы прогнали проверку безопасности. У 90% наших K8s pod'ов оказался примонтирован Service Account token в /var/run/secrets/...
Мы спросили у разработчиков: вы вообще используете это для обращения к K8s API?
Ответ: нет.
Проблема в том, что automountServiceAccountToken по умолчанию true. Токен получает каждый pod, даже если он ему вообще не нужен.
Риск огромный. Если pod взломали (например, через SSRF), атакер сразу получает токен для запросов к API.
Фикс: теперь по умолчанию ставим automountServiceAccountToken: false на всех Deployment'ах
👉 @BackendPortal
Мы спросили у разработчиков: вы вообще используете это для обращения к K8s API?
Ответ: нет.
Проблема в том, что automountServiceAccountToken по умолчанию true. Токен получает каждый pod, даже если он ему вообще не нужен.
Риск огромный. Если pod взломали (например, через SSRF), атакер сразу получает токен для запросов к API.
Фикс: теперь по умолчанию ставим automountServiceAccountToken: false на всех Deployment'ах
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Хочешь быструю базу? Выбирай диск с головой.
EBS подходит для нагрузок с небольшим количеством операций ввода-вывода. Но если нагрузка серьезная, он быстро превращается либо в узкое место, либо в черную дыру бюджета.
Самые частые варианты EBS: gp3 и io2. Оба — сетевые SSD, но по скорости заметно отличаются.
gp3 дешевле, но сильно ограничен по IOPS (максимум 16k) и по скорости ощущается слабее, чем io2.
io2 может выдавать до 64k IOPS (256k c Block Express) и обычно работает очень бодро. Но стоит БОЛЬШИХ денег. Переход с gp3 на io2 легко умножает стоимость хранилища раза в пять.
Лучший вариант? Локальные NVMe диски. Они самые быстрые и часто самые выгодные по деньгам. Если позаботиться о репликации и безопасности данных, это просто победа для производительности базы.
👉 @BackendPortal
EBS подходит для нагрузок с небольшим количеством операций ввода-вывода. Но если нагрузка серьезная, он быстро превращается либо в узкое место, либо в черную дыру бюджета.
Самые частые варианты EBS: gp3 и io2. Оба — сетевые SSD, но по скорости заметно отличаются.
gp3 дешевле, но сильно ограничен по IOPS (максимум 16k) и по скорости ощущается слабее, чем io2.
io2 может выдавать до 64k IOPS (256k c Block Express) и обычно работает очень бодро. Но стоит БОЛЬШИХ денег. Переход с gp3 на io2 легко умножает стоимость хранилища раза в пять.
Лучший вариант? Локальные NVMe диски. Они самые быстрые и часто самые выгодные по деньгам. Если позаботиться о репликации и безопасности данных, это просто победа для производительности базы.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤2
В распределенных системах обычно используют два подхода к хешированию. Выбор зависит от компромиссов.
Consistent hashing делает упор на скорость поиска и масштабируемость.
Rendezvous hashing ставит на первое место равномерное распределение нагрузки.
Суть простая:
• для каждого ключа считаем рейтинг всех серверов
• ключ уходит на сервер с максимальным рейтингом
• при добавлении или удалении серверов нужно сохранить эту схему
Рейтинг считается так: берем ключ, по очереди хешируем вместе с каждым сервером. Сервер с наибольшим значением хеша назначается для этого ключа.
Если сервер S удаляется, переназначаются только те ключи, для которых S был лучшим. Они переходят на сервер со вторым по величине рейтингом. Если сервер S добавляется, только те ключи, у которых S окажется выше всех в рейтинге, переедут на него.
Обычный rendezvous hashing — O(N) по времени на расчет хешей для каждого ключа. Есть вариант (skeleton-based), который сокращает это до O(log N). Дополнительная память не нужна, потому что рейтинг для ключа считается динамически.
👉 @BackendPortal
Consistent hashing делает упор на скорость поиска и масштабируемость.
Rendezvous hashing ставит на первое место равномерное распределение нагрузки.
Суть простая:
• для каждого ключа считаем рейтинг всех серверов
• ключ уходит на сервер с максимальным рейтингом
• при добавлении или удалении серверов нужно сохранить эту схему
Рейтинг считается так: берем ключ, по очереди хешируем вместе с каждым сервером. Сервер с наибольшим значением хеша назначается для этого ключа.
Если сервер S удаляется, переназначаются только те ключи, для которых S был лучшим. Они переходят на сервер со вторым по величине рейтингом. Если сервер S добавляется, только те ключи, у которых S окажется выше всех в рейтинге, переедут на него.
Обычный rendezvous hashing — O(N) по времени на расчет хешей для каждого ключа. Есть вариант (skeleton-based), который сокращает это до O(log N). Дополнительная память не нужна, потому что рейтинг для ключа считается динамически.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1
Большинство инженеров действительно начинают не с той стороны.
Сначала накидывают Kubernetes, Terraform, сервис-меш, мониторинг, а потом удивляются, почему всё тормозит и разваливается под нагрузкой.
Хороший порядок обучения выглядит примерно так:
- Сети
Разобраться что такое TCP, что внутри HTTP, зачем нужен DNS, как работает routing и чем задержка отличается от пропускной способности.
Без этого все последующие знания, как дом на песке.
- Базы данных
Индексирование, транзакции, репликация, шардинг.
Если не понимаешь как хранить и доставать данные = не понимаешь как масштабировать систему.
- Кэширование
Redis, мемкеш, CDN.
Правильно закэшировал = снял 70 процентов нагрузки.
- Очереди и стримы
Kafka, RabbitMQ и прочие.
Это подушка безопасности, когда трафик внезапно летит очень сильно
- Балансировка нагрузки
Разные стратегии, sticky-сессии, health-checks.
Если не умеешь равномерно раскладывать запросы, то горизонтальное масштабирование превращается в клоунаду🤡
- Практика на классических задачах
Сократи ссылки.
Ограничь скорость.
Сделай чат, ленту новостей, уведомления.
Боевое обучение без огромных рисков.
- Пост-мортемы
Лучший учитель — чужой факап.
Читаешь разбор инцидентов и видишь закономерности.
В итоге ты начинаешь думать не о «микросервисной архитектуре ради микросервисов», а о базовых законах:
задержка, надежность, пропускная способность, доступность, деньги.
Вот когда у тебя это в голове, то любые диаграммы становятся просто формой записи мыслей, а не магией бога-DevOps.
👉 @BackendPortal
Сначала накидывают Kubernetes, Terraform, сервис-меш, мониторинг, а потом удивляются, почему всё тормозит и разваливается под нагрузкой.
Хороший порядок обучения выглядит примерно так:
- Сети
Разобраться что такое TCP, что внутри HTTP, зачем нужен DNS, как работает routing и чем задержка отличается от пропускной способности.
Без этого все последующие знания, как дом на песке.
- Базы данных
Индексирование, транзакции, репликация, шардинг.
Если не понимаешь как хранить и доставать данные = не понимаешь как масштабировать систему.
- Кэширование
Redis, мемкеш, CDN.
Правильно закэшировал = снял 70 процентов нагрузки.
- Очереди и стримы
Kafka, RabbitMQ и прочие.
Это подушка безопасности, когда трафик внезапно летит очень сильно
- Балансировка нагрузки
Разные стратегии, sticky-сессии, health-checks.
Если не умеешь равномерно раскладывать запросы, то горизонтальное масштабирование превращается в клоунаду
- Практика на классических задачах
Сократи ссылки.
Ограничь скорость.
Сделай чат, ленту новостей, уведомления.
Боевое обучение без огромных рисков.
- Пост-мортемы
Лучший учитель — чужой факап.
Читаешь разбор инцидентов и видишь закономерности.
В итоге ты начинаешь думать не о «микросервисной архитектуре ради микросервисов», а о базовых законах:
задержка, надежность, пропускная способность, доступность, деньги.
Вот когда у тебя это в голове, то любые диаграммы становятся просто формой записи мыслей, а не магией бога-DevOps.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🔥1
Учебник по Java от Андрея Иванцова теперь доступен онлайн на GitBook. В нём собрано всё, что нужно новичку: от простых типов данных и строк до исключений и коллекций. Материал подан по делу, с примерами кода и понятными объяснениями.
Ссылка для тех, кто хочет прокачаться: andrey-ivantsov.gitbook.io/java
👉 @BackendPortal
Ссылка для тех, кто хочет прокачаться: andrey-ivantsov.gitbook.io/java
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4