⚡️Про bloat в pg и как с ним бороться
При обновлениях и удалениях строк postgres физически не удаляет/изменяет старую версию строки, а просто создает новую. У каждой такой версии есть поля:
1. xmin — номер транзакции, который создал версию строки
2. xmax — номер транзакции, который удалил версию строки
Чтобы проникнуться идеей xmin/xmax, можно почитать пост, как это позволяет обеспечить snapshot isolation
---
Окей, мы пообновляли строку, у нас появилось несколько версий строки со своими xmin/xmax. Интуитивно кажется, что нам не нужны все эти версии, ведь мы хотим видить только последнее, актуальное состояние строки. Так и есть — если xmax != 0 (версия строки кем-то удалена) и xmax < минимальный xid, среди активных транзакций (версия строки не видна ни для одной живой транзакции), то эта версия больше не нужна и ее можно удалить. Такие версии строк называются dead tuples
"Удалением" dead tuples занимается autovacuum: он помечает, что фрагменты страниц, где раньше лежали dead tuples, можно переиспользовать для записи новых данных. Важно отметить, что автовакуум никак не "двигает" живые данные и не освобождает физическое место. Он просто говорит, что в текущих страницах есть вот такие дырки, куда теперь можно что-то записать
К слову, минимальный xid, среди активных транзакций называется горизонтом базы. То есть автовакуум может удалять только те версии строк, которые "старше" горизонта базы. Это еще один аргумент, почему долгие транзакции — зло: из-за них автовакуум встает, так как такие транзакции долго держат горизонт
---
Итого, у нас есть набор страниц, куда записываются версии строк, потом они "удаляются" автовакуумом, и на эти места записываются новые данные. Казалось бы, если размер датасета не растет, то и физическое занимаемое место не должно расти. Но это не совсем правда
Несмотря на то, что у нас есть "дырки" в страницах, куда можно записать новые данные, этого не всегда хватает. Например, "дырка" может быть слишком маленькой, чтобы туда записать версию строки. Либо таких дырок в моменте может быть недостаточно (например, при массовых апдейтах/удалениях) — все это приводит к тому, что постгрес вынужден аллоцировать новые страницы => растет физический размер таблицы
---
Суммаризируя, table bloating — это ситуация, когда физический размер таблицы существенно превосходит размер датасета. Это происходит из-за:
1. Накопления dead tuples
2. Фрагментации таблицы, когда текущих "дырок" не хватает для записи новых данных и приходится выделять новые страницы
Для борьбы с фрагментацией у постгреса есть vacuum full — он берет эксклюзивную блокировку на таблицу и полностью ее перезаписывает в новый файл "без дырок". Однако на практике он редко применим, поскольку он буквально вызывает даунтайм сервиса (возможно на несколько часов, если таблица большая)
Для борьбы с фрагментацией без даунтайма есть утилита pg_repack
👍 — если нужен пост про принцип работы pg_repack
При обновлениях и удалениях строк postgres физически не удаляет/изменяет старую версию строки, а просто создает новую. У каждой такой версии есть поля:
1. xmin — номер транзакции, который создал версию строки
2. xmax — номер транзакции, который удалил версию строки
Чтобы проникнуться идеей xmin/xmax, можно почитать пост, как это позволяет обеспечить snapshot isolation
---
Окей, мы пообновляли строку, у нас появилось несколько версий строки со своими xmin/xmax. Интуитивно кажется, что нам не нужны все эти версии, ведь мы хотим видить только последнее, актуальное состояние строки. Так и есть — если xmax != 0 (версия строки кем-то удалена) и xmax < минимальный xid, среди активных транзакций (версия строки не видна ни для одной живой транзакции), то эта версия больше не нужна и ее можно удалить. Такие версии строк называются dead tuples
"Удалением" dead tuples занимается autovacuum: он помечает, что фрагменты страниц, где раньше лежали dead tuples, можно переиспользовать для записи новых данных. Важно отметить, что автовакуум никак не "двигает" живые данные и не освобождает физическое место. Он просто говорит, что в текущих страницах есть вот такие дырки, куда теперь можно что-то записать
К слову, минимальный xid, среди активных транзакций называется горизонтом базы. То есть автовакуум может удалять только те версии строк, которые "старше" горизонта базы. Это еще один аргумент, почему долгие транзакции — зло: из-за них автовакуум встает, так как такие транзакции долго держат горизонт
---
Итого, у нас есть набор страниц, куда записываются версии строк, потом они "удаляются" автовакуумом, и на эти места записываются новые данные. Казалось бы, если размер датасета не растет, то и физическое занимаемое место не должно расти. Но это не совсем правда
Несмотря на то, что у нас есть "дырки" в страницах, куда можно записать новые данные, этого не всегда хватает. Например, "дырка" может быть слишком маленькой, чтобы туда записать версию строки. Либо таких дырок в моменте может быть недостаточно (например, при массовых апдейтах/удалениях) — все это приводит к тому, что постгрес вынужден аллоцировать новые страницы => растет физический размер таблицы
---
Суммаризируя, table bloating — это ситуация, когда физический размер таблицы существенно превосходит размер датасета. Это происходит из-за:
1. Накопления dead tuples
2. Фрагментации таблицы, когда текущих "дырок" не хватает для записи новых данных и приходится выделять новые страницы
Для борьбы с фрагментацией у постгреса есть vacuum full — он берет эксклюзивную блокировку на таблицу и полностью ее перезаписывает в новый файл "без дырок". Однако на практике он редко применим, поскольку он буквально вызывает даунтайм сервиса (возможно на несколько часов, если таблица большая)
Для борьбы с фрагментацией без даунтайма есть утилита pg_repack
👍 — если нужен пост про принцип работы pg_repack
Telegram
Microservices Thoughts
⚡️Принцип работы snapshot isolation (aka repeatable read) в postgres
Изоляция repeatable read избавляет от неповторяющегося чтения — ситуации, когда одна и та же строка запрашивается дважды в рамках транзакции, но результаты чтения получаются разными
begin;…
Изоляция repeatable read избавляет от неповторяющегося чтения — ситуации, когда одна и та же строка запрашивается дважды в рамках транзакции, но результаты чтения получаются разными
begin;…
👍232🔥6✍1 1
⚡️Про bloat в pg и как с ним бороться (pt. 2)
Перфоманс ревью почти закончилось, поэтому я снова в строю
В предыдущем посте посте обсудили, что таблица может блоатиться из-за фрагментации, и один из способов борьбы — это пересбор таблицы с помощью vacuum full. Однако он берет эксклюзивную блокировку и на больших таблицах может ее держать несколько часов. Поэтому такой способ не подходит, если даунтаймы недопустимы
Одна из альтернатив — расширение pg_repack, которое позволяет пересобрать таблицы и индексы без долгих блокировок
---
Можно отдельно рассмотреть два режима
1. Пересбор только индексов (--only-indexes)
Это простой случай — репак просто подменяет индекс:
1) Создает копию индекса через create index concurrently
2) Удаляет старый индекс, а новый переименовывает в старый
В целом такое спокойно делается и без репака
2. Пересбор таблиц
Тут уже сложнее — нужно создать копию таблицы, при этом по дороге не просыпать данные, и не провоцировать долгие блокировки
Конкретные DDL/DML можно почитать тут, если в общих чертах, то шаги такие:
1) Под access exclusive блокировкой: создаем лог-таблицу для трекинга изменений и триггер, который будет "реплицировать" изменения из основной таблицы в лог-таблицу
2) Создаем новую таблицу-копию (буквально через insert into new select * from old)
3) Создаем индексы на копию
4) Накатываем на копию "лаг", который за это время образовался в лог-таблице
5) Под access exclusive блокировкой: атомарно подменяем старую и новую таблицы, старую удаляем
Итого имеем полностью пересобранную таблицу и индексы без bloat-а
---
Что важно знать и что может пойти не так
1. Во write-intensive базах может быть проблематично взять access exclusive блокировки
2. Репак создает копию таблицы через insert .. select *, что может генерить резкую IO нагрузку на диск. Встроенной функциональности ее ограничить в репаке нет, но можно придумать workaround-ы наподобие такого через cgroup
2.1. — надо иметь на диске свободного пространства ~x2 от размера таблицы
2.2. — резко накапливается WAL, что приводит ко всем сопутствующим проблемам
2.3. — такое действие провоцирует долгую транзакцию, которая держит горизонт => встает автовакуум => вновь копится bloat
---
Overall, если у вас нагруженная система, и при этом вы можете выделить окно обслуживания, в котором нагрузка будет сильно меньше — pg_repack скорее всего вам подойдет
Если же система нагруженная, но окошек с низкой нагрузкой нет, то pg_repack вероятно вам просто положит базу. Поэтому стоит рассмотреть более "онлайновые" инструменты — pg_squeeze и pgcompacttable
По традиции, 👍 — если нужен пост про них
Перфоманс ревью почти закончилось, поэтому я снова в строю
В предыдущем посте посте обсудили, что таблица может блоатиться из-за фрагментации, и один из способов борьбы — это пересбор таблицы с помощью vacuum full. Однако он берет эксклюзивную блокировку и на больших таблицах может ее держать несколько часов. Поэтому такой способ не подходит, если даунтаймы недопустимы
Одна из альтернатив — расширение pg_repack, которое позволяет пересобрать таблицы и индексы без долгих блокировок
---
Можно отдельно рассмотреть два режима
1. Пересбор только индексов (--only-indexes)
Это простой случай — репак просто подменяет индекс:
1) Создает копию индекса через create index concurrently
2) Удаляет старый индекс, а новый переименовывает в старый
В целом такое спокойно делается и без репака
2. Пересбор таблиц
Тут уже сложнее — нужно создать копию таблицы, при этом по дороге не просыпать данные, и не провоцировать долгие блокировки
Конкретные DDL/DML можно почитать тут, если в общих чертах, то шаги такие:
1) Под access exclusive блокировкой: создаем лог-таблицу для трекинга изменений и триггер, который будет "реплицировать" изменения из основной таблицы в лог-таблицу
2) Создаем новую таблицу-копию (буквально через insert into new select * from old)
3) Создаем индексы на копию
4) Накатываем на копию "лаг", который за это время образовался в лог-таблице
5) Под access exclusive блокировкой: атомарно подменяем старую и новую таблицы, старую удаляем
Итого имеем полностью пересобранную таблицу и индексы без bloat-а
---
Что важно знать и что может пойти не так
1. Во write-intensive базах может быть проблематично взять access exclusive блокировки
2. Репак создает копию таблицы через insert .. select *, что может генерить резкую IO нагрузку на диск. Встроенной функциональности ее ограничить в репаке нет, но можно придумать workaround-ы наподобие такого через cgroup
2.1. — надо иметь на диске свободного пространства ~x2 от размера таблицы
2.2. — резко накапливается WAL, что приводит ко всем сопутствующим проблемам
2.3. — такое действие провоцирует долгую транзакцию, которая держит горизонт => встает автовакуум => вновь копится bloat
---
Overall, если у вас нагруженная система, и при этом вы можете выделить окно обслуживания, в котором нагрузка будет сильно меньше — pg_repack скорее всего вам подойдет
Если же система нагруженная, но окошек с низкой нагрузкой нет, то pg_repack вероятно вам просто положит базу. Поэтому стоит рассмотреть более "онлайновые" инструменты — pg_squeeze и pgcompacttable
По традиции, 👍 — если нужен пост про них
Telegram
Microservices Thoughts
⚡️Про bloat в pg и как с ним бороться
При обновлениях и удалениях строк postgres физически не удаляет/изменяет старую версию строки, а просто создает новую. У каждой такой версии есть поля:
1. xmin — номер транзакции, который создал версию строки
2. xmax…
При обновлениях и удалениях строк postgres физически не удаляет/изменяет старую версию строки, а просто создает новую. У каждой такой версии есть поля:
1. xmin — номер транзакции, который создал версию строки
2. xmax…
👍172🔥3 1
⚡️Про неопределенности в продуктовой разработке
При разработке продуктовых фичей не всегда на старте есть четкие ФТ, НФТ — иногда есть просто пользовательский юзкейс, который хочется реализовать. А дальше смотрим, насколько он вписывается в систему, насколько долго / сложно такое делать, какие дает перспективы на будущее и т.д.
Побочный эффект — на старте можно предложить примерно бесконечное множество решений, что в свою очередь может приводить к бесконечным встречам и обсуждениям одного и того же по кругу
---
И чтобы это минимизировать, я люблю такой метод:
1. Берем любое (обычно самое простое) решение, которое реализует юзкейс
2. Собираем возражения от продакта / разработчиков
3. Проводим gap-анализ:
Желаемое решение = Текущее решение + Отклонение. Если отклонение небольшое, то докручиваем текущее решение. Если же отклонение сильное, придумываем новое решение, которое сразу закроет это отклонение
*Повторяем, пока не дойдем до решения, с которым согласен некий кворум участников встречи
---
Это работает по одной простой причине — убивает страх чистого листа. Вместо того чтобы с нуля придумывать решение, вы уже работаете с некоторой "базой", которую докручиваете
При разработке продуктовых фичей не всегда на старте есть четкие ФТ, НФТ — иногда есть просто пользовательский юзкейс, который хочется реализовать. А дальше смотрим, насколько он вписывается в систему, насколько долго / сложно такое делать, какие дает перспективы на будущее и т.д.
Побочный эффект — на старте можно предложить примерно бесконечное множество решений, что в свою очередь может приводить к бесконечным встречам и обсуждениям одного и того же по кругу
---
И чтобы это минимизировать, я люблю такой метод:
1. Берем любое (обычно самое простое) решение, которое реализует юзкейс
2. Собираем возражения от продакта / разработчиков
3. Проводим gap-анализ:
Желаемое решение = Текущее решение + Отклонение. Если отклонение небольшое, то докручиваем текущее решение. Если же отклонение сильное, придумываем новое решение, которое сразу закроет это отклонение
*Повторяем, пока не дойдем до решения, с которым согласен некий кворум участников встречи
---
Это работает по одной простой причине — убивает страх чистого листа. Вместо того чтобы с нуля придумывать решение, вы уже работаете с некоторой "базой", которую докручиваете
👍52🤔21 2
⚡️Немного про векторные базы данных
Учитывая что аббревиатуры LLM, AI и подобные я стал слышать мучительно часто, внутренний голос заставил что-то почитать на эту тему. Поэтому ловите нетипичный пост
tl;dr зачем нужны векторные базы данных
1. Добавлять в базу данных числовые вектора
2. По заданному вектору находить похожие
---
Если чуть подробнее, то сложные объекты (текст, картинки, ...) можно закодировать в виде числовых векторов (эмбеддингов). От эмбеддингов ожидается, что они в себе сохраняют какую-то семантическую информацию об объекте, и как следствие, ожидается, что если эмбеддинги двух объектов похожи, то скорее всего сами объекты похожи
В целом, это позволяет осуществлять поиск похожих объектов, даже если сами объекты "сложные" — объект -> эмбеддинг -> похожие эмбеддинги -> похожие объекты:
- Поиск схожих картинок
- Рекомендательные системы
- RAG — где мы по запросу пользователя ищем подходящие знания в базе знаний
... и так далее
И встает вопрос, как по числовому вектору находить ближайшие
---
Конечно, интересен случай, когда данных много. В таких ситуациях зачастую обменивают точный поиск на приближенный в пользу скорости
Один из частоиспользуемых приближенных алгоритмов — HNSW: можно почитать прекрасную статью с картинками вот тут
Если вкратце:
Есть более простая версия — NSW, где определенным образом строится граф (каждый вектор — вершина). И чтобы найти ближайший вектор выполняется такой жадный алгоритм:
1. Берется некоторая начальная вершина
2. Выбираем вершину соседа, которая ближе всего к целевой
3. Повторяем пока не дойдем до "локального минимума"
HNSW — это развитие этой идеи, где появляется иерархия. Есть основной граф, это самый нижний слой. Каждый следующий слой является все меньшим подмножеством графа, при этом содержит все более "длинные" ребра. Концептуально очень похоже на skiplist
Принцип работы примерно такой, что
1. Делаем NSW на верхнем слое, пока не упремся в локальный минимум
2. Из этой вершины уходим на предыдущий слой
3. Повторяем, пока не дойдем до самого нижнего слоя
Утверждается, что такая эвристика позволяет сильно лучше обходить локальные минимумы => показывает лучше качество
Например, этот алгоритм используется по дефолту в одной из самых популярных векторных БД — Qdrant
---
Тема весьма специфичная для привычного бэкенда, поэтому если кому-то вдруг доводилось такое применять, обязательно пишите!
Учитывая что аббревиатуры LLM, AI и подобные я стал слышать мучительно часто, внутренний голос заставил что-то почитать на эту тему. Поэтому ловите нетипичный пост
tl;dr зачем нужны векторные базы данных
1. Добавлять в базу данных числовые вектора
2. По заданному вектору находить похожие
---
Если чуть подробнее, то сложные объекты (текст, картинки, ...) можно закодировать в виде числовых векторов (эмбеддингов). От эмбеддингов ожидается, что они в себе сохраняют какую-то семантическую информацию об объекте, и как следствие, ожидается, что если эмбеддинги двух объектов похожи, то скорее всего сами объекты похожи
В целом, это позволяет осуществлять поиск похожих объектов, даже если сами объекты "сложные" — объект -> эмбеддинг -> похожие эмбеддинги -> похожие объекты:
- Поиск схожих картинок
- Рекомендательные системы
- RAG — где мы по запросу пользователя ищем подходящие знания в базе знаний
... и так далее
И встает вопрос, как по числовому вектору находить ближайшие
---
Конечно, интересен случай, когда данных много. В таких ситуациях зачастую обменивают точный поиск на приближенный в пользу скорости
Один из частоиспользуемых приближенных алгоритмов — HNSW: можно почитать прекрасную статью с картинками вот тут
Если вкратце:
Есть более простая версия — NSW, где определенным образом строится граф (каждый вектор — вершина). И чтобы найти ближайший вектор выполняется такой жадный алгоритм:
1. Берется некоторая начальная вершина
2. Выбираем вершину соседа, которая ближе всего к целевой
3. Повторяем пока не дойдем до "локального минимума"
HNSW — это развитие этой идеи, где появляется иерархия. Есть основной граф, это самый нижний слой. Каждый следующий слой является все меньшим подмножеством графа, при этом содержит все более "длинные" ребра. Концептуально очень похоже на skiplist
Принцип работы примерно такой, что
1. Делаем NSW на верхнем слое, пока не упремся в локальный минимум
2. Из этой вершины уходим на предыдущий слой
3. Повторяем, пока не дойдем до самого нижнего слоя
Утверждается, что такая эвристика позволяет сильно лучше обходить локальные минимумы => показывает лучше качество
Например, этот алгоритм используется по дефолту в одной из самых популярных векторных БД — Qdrant
---
Тема весьма специфичная для привычного бэкенда, поэтому если кому-то вдруг доводилось такое применять, обязательно пишите!
www.pinecone.io
Hierarchical Navigable Small Worlds (HNSW)
Hierarchical Navigable Small World (HNSW) graphs are among the top-performing indexes for vector similarity search. HNSW is a hugely popular technology that time and time again produces state-of-the-art performance with super fast search speeds and fantastic…
👍45🔥9 2💅1
Учитывая, что подавлющая часть аудитории — действующие разработчики и руководители, стало интересно а что по деньгам
Число в опросе — усредненный total (с премиями и бонусами), NET, в тыс. рублей
Число в опросе — усредненный total (с премиями и бонусами), NET, в тыс. рублей
Anonymous Poll
8%
<100
12%
100-200
21%
200-300
26%
300-400
17%
400-500
7%
500-600
3%
600-700
6%
>700
⚡️Про петли положительной обратной связи
На мой взгляд, одни из самых интересных проблем — это ситуации, когда результат действий начинает усиливать сам себя. Причем такое случается во всех сферах:
В технике: "сервер долго отвечает => клиенты падают по таймауту => клиенты ретраят => сервер еще хуже отвечает"
В процессах: "низкая скорость разработки => забьем на кодревью => качество кода ниже => скорость разработки еще ниже"
В обычной жизни: "плохой сон => выше тревожность => еще хуже сон"
И так далее
---
В простых случаях влияния довольны очевидны, и их можно представить буквально в голове. Но самое интересное, когда переменных не 2, 3, 4, а несколько десятков
Как такое дебажить? В общем-то простого способа нет — нужно по честному взять (а сначала найти) все переменные и далее
1. Построить граф, где ребро (X, Y) ~ переменная X влияет на Y. Влияние может быть как положительным, так и отрицательным. Такая диаграмма называется Causal loop diagram
2. Циклы с четным числом "отрицательных влияний" — это и будут feedback loop-ы
---
Вообще, этот подход охватывается умной вещью, называемой системной динамикой, а неплохую статью по теме можно почитать тут
На мой взгляд, одни из самых интересных проблем — это ситуации, когда результат действий начинает усиливать сам себя. Причем такое случается во всех сферах:
В технике: "сервер долго отвечает => клиенты падают по таймауту => клиенты ретраят => сервер еще хуже отвечает"
В процессах: "низкая скорость разработки => забьем на кодревью => качество кода ниже => скорость разработки еще ниже"
В обычной жизни: "плохой сон => выше тревожность => еще хуже сон"
И так далее
---
В простых случаях влияния довольны очевидны, и их можно представить буквально в голове. Но самое интересное, когда переменных не 2, 3, 4, а несколько десятков
Как такое дебажить? В общем-то простого способа нет — нужно по честному взять (а сначала найти) все переменные и далее
1. Построить граф, где ребро (X, Y) ~ переменная X влияет на Y. Влияние может быть как положительным, так и отрицательным. Такая диаграмма называется Causal loop diagram
2. Циклы с четным числом "отрицательных влияний" — это и будут feedback loop-ы
---
Вообще, этот подход охватывается умной вещью, называемой системной динамикой, а неплохую статью по теме можно почитать тут
👍47🔥6💅2 2✍1
⚡️Почему алгосы — это полезно
Сразу отбросим вариант, когда под алгоритмами понимается "я наизусть выучил решение 50 литкод задач перед собесом"))
Решение алгоритмических задач с условного кодфорсеса развивает два важных навыка:
1. Отделять суть от формы
Из абстрактного "на Машу падали яблоки" на "были такие-то данные, которые так-то будут меняться, нужно научиться быстро отвечать на такие запросы"
2. Проводить аналогии
Это примерно про следующее: понять, что текущая задача (или ее часть) концептуально похожа на ту, что ты уже решал, либо на готовый алгоритм / структуру данных / etc. И из этого синтезировать новое решение
---
Как это помогает в обычной разработке?
Отделение сути от формы — в переводе бизнесовых хотелок на технический язык
Проведение аналогий — в придумывании решения технической задачи и видения, а как вообще концептуально должна быть устроена архитектура
p.s.: все вышесказанное относится не только к алгоритмам, а примерно к любой сложной математике/информатике/etc
Решение алгоритмических задач с условного кодфорсеса развивает два важных навыка:
1. Отделять суть от формы
Из абстрактного "на Машу падали яблоки" на "были такие-то данные, которые так-то будут меняться, нужно научиться быстро отвечать на такие запросы"
2. Проводить аналогии
Это примерно про следующее: понять, что текущая задача (или ее часть) концептуально похожа на ту, что ты уже решал, либо на готовый алгоритм / структуру данных / etc. И из этого синтезировать новое решение
---
Как это помогает в обычной разработке?
Отделение сути от формы — в переводе бизнесовых хотелок на технический язык
Проведение аналогий — в придумывании решения технической задачи и видения, а как вообще концептуально должна быть устроена архитектура
p.s.: все вышесказанное относится не только к алгоритмам, а примерно к любой сложной математике/информатике/etc
🤔54👍41🔥12😁5 4💅2
⚡️Флапающие алерты
Представьте, что у вас есть алерт на кол-во ошибок — если 5хх ответов больше 1%, то алерт "загорается"
Обычно это выглядит так:
1. Берется окошко, скажем 3 мин
2. Считается доля 5хх: sum(requests[status = 5xx]) / sum(requests)
3. Алармим, если доля > 0.01
Так мы по сути считаем усредненную долю ошибок за последние 3 минуты
---
Что может поломать такую схему? Короткие всплески, когда процент 5хх пару секунд очень высокий. Это может происходить по разным причинам: случайные/локальные сбои, переключения мастера, накатка миграции etc
Поскольку мы считаем avg по окошку, такие короткие всплески скорее всего будут триггерить алерт, пока наблюдаемое окошко "не перескачет" этот всплеск
Пример: 100 rps; окошко 3 минуты; пятисотили 2 секунды
доля 5хх = (100rps * 2 сек) / (100rps * 180сек) = 1/90 > 0.01 — алерт загорелся
Хотите ли вы, чтобы алерт загорался в такой ситуации? depends
Далее рассмотрим случай, когда не хотим
---
Из банальных советов:
1. Увеличить окошко (3min -> 5min)
Шире окно, выше шанс, что пик размажется. Но тут идет трейдофф с риском появлений false-negative, когда пропустим реальную проблему
2. Увеличить трешхолд (0.01 -> 0.03)
Примерно та же история, что с окошком
Из менее банальных:
3. Пересмотреть алерт (avg -> percentile)
Например, так
1) Берем такую метрику requests[status = 5xx] / requests — это график доли пятисоток
2) Берем p75 от нее — получаем число. Это число означает, что 25% времени окошка доля пятисоток была выше, чем это число
3) Поджигаем если p75 > threshold
То есть мы ушли от "среднее кол-во пятисоток в окне нарушает трешхолд" до ">25% времени нарушался трешхолд пятисоток"
Частные случаи:
p0 (минимум) — трешхолд нарушался 100% времени (в каждой точке окна)
p50 (медиана) — трешхолд нарушался половину времени
p100 (максимум) — трешхолд нарушился буквально в одной точке
4. Добавить сглаживание
Вместо того, чтобы смотреть на исходную метрику requests[status = 5xx] / sum(requests), можно добавить сглаживание: например, каждая точка будет показывать не текущее значение, а усредненное за предыдущую минуту (moving avg). Либо какой-то перцентиль за предыдущую минуту
Де-факто этот способ часто взаимозаменяем с предыдущим
---
Зачастую эмпирический тюнинг алерта приводит к использованию комбинации этих способов
Представьте, что у вас есть алерт на кол-во ошибок — если 5хх ответов больше 1%, то алерт "загорается"
Обычно это выглядит так:
1. Берется окошко, скажем 3 мин
2. Считается доля 5хх: sum(requests[status = 5xx]) / sum(requests)
3. Алармим, если доля > 0.01
Так мы по сути считаем усредненную долю ошибок за последние 3 минуты
---
Что может поломать такую схему? Короткие всплески, когда процент 5хх пару секунд очень высокий. Это может происходить по разным причинам: случайные/локальные сбои, переключения мастера, накатка миграции etc
Поскольку мы считаем avg по окошку, такие короткие всплески скорее всего будут триггерить алерт, пока наблюдаемое окошко "не перескачет" этот всплеск
Пример: 100 rps; окошко 3 минуты; пятисотили 2 секунды
доля 5хх = (100rps * 2 сек) / (100rps * 180сек) = 1/90 > 0.01 — алерт загорелся
Хотите ли вы, чтобы алерт загорался в такой ситуации? depends
Далее рассмотрим случай, когда не хотим
---
Из банальных советов:
1. Увеличить окошко (3min -> 5min)
Шире окно, выше шанс, что пик размажется. Но тут идет трейдофф с риском появлений false-negative, когда пропустим реальную проблему
2. Увеличить трешхолд (0.01 -> 0.03)
Примерно та же история, что с окошком
Из менее банальных:
3. Пересмотреть алерт (avg -> percentile)
Например, так
1) Берем такую метрику requests[status = 5xx] / requests — это график доли пятисоток
2) Берем p75 от нее — получаем число. Это число означает, что 25% времени окошка доля пятисоток была выше, чем это число
3) Поджигаем если p75 > threshold
То есть мы ушли от "среднее кол-во пятисоток в окне нарушает трешхолд" до ">25% времени нарушался трешхолд пятисоток"
Частные случаи:
p0 (минимум) — трешхолд нарушался 100% времени (в каждой точке окна)
p50 (медиана) — трешхолд нарушался половину времени
p100 (максимум) — трешхолд нарушился буквально в одной точке
4. Добавить сглаживание
Вместо того, чтобы смотреть на исходную метрику requests[status = 5xx] / sum(requests), можно добавить сглаживание: например, каждая точка будет показывать не текущее значение, а усредненное за предыдущую минуту (moving avg). Либо какой-то перцентиль за предыдущую минуту
Де-факто этот способ часто взаимозаменяем с предыдущим
---
Зачастую эмпирический тюнинг алерта приводит к использованию комбинации этих способов
👍54🔥8💅1 1
⚡️По какой сущности шардировать
Недавно у коллег возникла необходимость шардировать большую pg базу, и для этого нужно определиться с сущностью, по которой надо шардировать. И задумался, можно ли придумать базовый универсальный алгоритм, как отправную точку, от которой можно рассуждать
Для простоты представим, что модель данных системы представлена в виде одного дерева
Исходное состояние: всё лежит в одном шарде, никаких кроссшардовых операций. Все прекрасно, кроме момента, что объем данных бесконечно растет
---
И далее начинаем спускаться по дереву сущностей, начиная от корня, примеряя каждую сущность как кандидата на шардирование
Корень — корневые сущности максимально независимы => будет минимальное количество кроссшардовых операций. При этом общий объем сущности (корневая сущность + все ее дочерние) скорее всего может бесконечно расти
Берем сущность на уровень ниже — получаем сущность, экземпляры который более зависимы друг от друга, при этом вероятность разрастания до бесконечности становится ниже
И с каждым понижением уровня мы получаем все более зависимые сущности, но которые все сильнее ограничены в объеме
И задача заключается в том, чтобы поймать баланс, когда сущности настолько независимы, чтобы не требовали (или почти не требовали) кросс-шардовых операций, при этом были достаточно ограничены в объеме
---
Один из успешных примеров с работы:
queue — определенная очередь обработки обращений в поддержку
ticket — конкретное обращение
article — сообщение в рамках обращения
queue — две очереди максимально независимы друг от друга, но могут расти бесконечно и неравномерно — тикетов может появится сколько угодно, и где-то их много, где-то мало
ticket — два тикета все еще достаточно независимы (это два отдельных обращения в поддержку), при этом два тикета могут участвовать в какой-то общей выборке (например, выборка открытых тикетов, на которых нет исполнителя). А вот максимальный объем тикета уже становится ограничен
article — два сообщения уже сильно зависимы (часто выбираются вместе), а объем сильно ограничен, и он гарантированно небольшой
Overall, что мы видим:
queue vs ticket — чуть чуть теряем в независимости, при этом сильно выигрываем, что сущность становится ограничена в объеме
ticket vs article — сильно проигрываем в независимости, при этом чуть чуть выигрываем в максимальном размере сущности
Такими рассуждениями, ticket выглядит максимально привлекательным кандидатом для шардирования
Недавно у коллег возникла необходимость шардировать большую pg базу, и для этого нужно определиться с сущностью, по которой надо шардировать. И задумался, можно ли придумать базовый универсальный алгоритм, как отправную точку, от которой можно рассуждать
Для простоты представим, что модель данных системы представлена в виде одного дерева
Исходное состояние: всё лежит в одном шарде, никаких кроссшардовых операций. Все прекрасно, кроме момента, что объем данных бесконечно растет
---
И далее начинаем спускаться по дереву сущностей, начиная от корня, примеряя каждую сущность как кандидата на шардирование
Корень — корневые сущности максимально независимы => будет минимальное количество кроссшардовых операций. При этом общий объем сущности (корневая сущность + все ее дочерние) скорее всего может бесконечно расти
Берем сущность на уровень ниже — получаем сущность, экземпляры который более зависимы друг от друга, при этом вероятность разрастания до бесконечности становится ниже
И с каждым понижением уровня мы получаем все более зависимые сущности, но которые все сильнее ограничены в объеме
И задача заключается в том, чтобы поймать баланс, когда сущности настолько независимы, чтобы не требовали (или почти не требовали) кросс-шардовых операций, при этом были достаточно ограничены в объеме
---
Один из успешных примеров с работы:
queue — определенная очередь обработки обращений в поддержку
ticket — конкретное обращение
article — сообщение в рамках обращения
queue — две очереди максимально независимы друг от друга, но могут расти бесконечно и неравномерно — тикетов может появится сколько угодно, и где-то их много, где-то мало
ticket — два тикета все еще достаточно независимы (это два отдельных обращения в поддержку), при этом два тикета могут участвовать в какой-то общей выборке (например, выборка открытых тикетов, на которых нет исполнителя). А вот максимальный объем тикета уже становится ограничен
article — два сообщения уже сильно зависимы (часто выбираются вместе), а объем сильно ограничен, и он гарантированно небольшой
Overall, что мы видим:
queue vs ticket — чуть чуть теряем в независимости, при этом сильно выигрываем, что сущность становится ограничена в объеме
ticket vs article — сильно проигрываем в независимости, при этом чуть чуть выигрываем в максимальном размере сущности
Такими рассуждениями, ticket выглядит максимально привлекательным кандидатом для шардирования
👍37🤔9💅2🔥1 1
⚡️Что под капотом у Cursor?
Хорошая статья, рассказывающая базовые принципы работы AI идеешек (и в целом агентных систем)
---
tl;dr:
Базовый минимум для агента, который будет уметь писать код:
1. Хорошая моделька (claude 3.7, gpt 4.1, ...)
2. Функции для работы с файлами (read_file, write_file) — чтобы моделька могла взаимодействовать с внешним (относительно нее) миром, т.е. нашей кодовой базой
3.1. Эмбеддинги кода в векторной базе — чтобы был семантический поиск по коду (найди места, где происходит вызов апи сервиса X).
3.2. Альтернатива: научить агента итеративно искать обычным поиском по коду — примерно как действуют люди, погружаясь в новую кодовую базу (так сделано в Claude Code)
4. Качественные промты — например, внутри Cursor используется такой промт
И на картинке показано, как эти компоненты друг с другом взаимодействуют
---
И несколько полезных моментов про написание правил для код-агентов (rules):
1. Не нужно писать что-то в стиле "ты опытный бэкенд разработчик на Java" — это будет странно для агента, тк у него уже есть built-in промт, рассказывающий, кто он такой
2. Правила лучше писать в формате энциклопедии (а не в формате четкого алгоритма) и с ссылками на код. Это позволяет агенту проще находить контекст, который нужен для исполнения пользовательского запроса, и переиспользовать одно и то же правило в разных ситуациях
3. Стоит вложиться в качественные описания правил. По описанию моделька лучше сматчит, применимо ли конкретное правило в конкретной ситуации
---
Следующий наворот — это подключение различных MCP-шек, которые позволяют агенту ходить во внешние системы: таск-трекер, внутреннюю вики или просто что-то гуглить. Ставьте классы, если был бы интересен пост про такое
Хорошая статья, рассказывающая базовые принципы работы AI идеешек (и в целом агентных систем)
---
tl;dr:
Базовый минимум для агента, который будет уметь писать код:
1. Хорошая моделька (claude 3.7, gpt 4.1, ...)
2. Функции для работы с файлами (read_file, write_file) — чтобы моделька могла взаимодействовать с внешним (относительно нее) миром, т.е. нашей кодовой базой
3.1. Эмбеддинги кода в векторной базе — чтобы был семантический поиск по коду (найди места, где происходит вызов апи сервиса X).
3.2. Альтернатива: научить агента итеративно искать обычным поиском по коду — примерно как действуют люди, погружаясь в новую кодовую базу (так сделано в Claude Code)
4. Качественные промты — например, внутри Cursor используется такой промт
И на картинке показано, как эти компоненты друг с другом взаимодействуют
---
И несколько полезных моментов про написание правил для код-агентов (rules):
1. Не нужно писать что-то в стиле "ты опытный бэкенд разработчик на Java" — это будет странно для агента, тк у него уже есть built-in промт, рассказывающий, кто он такой
2. Правила лучше писать в формате энциклопедии (а не в формате четкого алгоритма) и с ссылками на код. Это позволяет агенту проще находить контекст, который нужен для исполнения пользовательского запроса, и переиспользовать одно и то же правило в разных ситуациях
3. Стоит вложиться в качественные описания правил. По описанию моделька лучше сматчит, применимо ли конкретное правило в конкретной ситуации
---
Следующий наворот — это подключение различных MCP-шек, которые позволяют агенту ходить во внешние системы: таск-трекер, внутреннюю вики или просто что-то гуглить. Ставьте классы, если был бы интересен пост про такое
👍175😁9🔥5✍4💅2
Microservices Thoughts
⚡️Что под капотом у Cursor? Хорошая статья, рассказывающая базовые принципы работы AI идеешек (и в целом агентных систем) --- tl;dr: Базовый минимум для агента, который будет уметь писать код: 1. Хорошая моделька (claude 3.7, gpt 4.1, ...) 2. Функции…
На посте >150 шейров, поэтому интересно - куда вы репостите?)
Anonymous Poll
46%
Себе в saved messages
9%
Знакомым или друзьям
49%
тык
🤯3
Наткнулся на статью https://blog.wilsonl.in/search-engine/, где чел весьма подробно рассказывает, как с нуля зафигачил семантический поиск (включая сбор данных) по 3млрд документов
Понравилось, что описывается не просто "я сделал так-то", а "попробовал так-то, не получилось, поэтому сделал вот так"
Рекомендую почитать + погулять по референсам — там много интересного как про ML, так и про инфру и бэкенд
Понравилось, что описывается не просто "я сделал так-то", а "попробовал так-то, не получилось, поэтому сделал вот так"
Рекомендую почитать + погулять по референсам — там много интересного как про ML, так и про инфру и бэкенд
Wilson Lin
Building a web search engine from scratch in two months with 3 billion neural embeddings
End-to-end deep dive of the project, spanning a large GPU cluster, distributed RocksDB, and terabytes of sharded HNSW.
4👍63🔥4
⚡️TTFB/FCP vs response time
TTFB (Time To First Byte) — это время от отправки запроса до получения первого байта данных от сервера
FCP (First Contentful Paint) — это время от открытия страницы до того как на экране появится первый контент
Почему я рассматриваю эти метрики одновременно? Потому что зачастую они оптимизируются с одной целью — как можно раньше что-то показать пользователю, т.е. сделать интерфейс отзывчивее
---
В зависимости от контекста response time может иметь два значения:
1. Конкретный запрос к бэкенду
Это время от начала запроса до получения последнего байта по конкретному запросу к апишке бэкенда
response time = TTFB + время загрузки всех данных по этому запросу
Пример: пользователь тыкнул "воспроизвести" на плеере и у него сразу начало играться видео, без ожидания скачивания всего видео-файла
В основном, для конечного пользователя здесь будет разница, когда есть какой-то стриминг: видео/аудио/...
2. Загрузка всей страницы
Более неформальный вариант — это время от момента открытия страницы до загрузки всех необходимых данных
response time = FCP + время загрузки всех данных для этой страницы
Пример: при открытии ленты в соц сети сначала подгрузится разметка, затем текст постов, затем фото в постах
Конечный пользователь ощутит разницу, когда фронтенд не дожидается всех данных от сервера и потом их отрисовывает, а делает несколько запросов до бэкенда и рисует страницу по частям
---
Вообще, один из актуальных примеров это диалог с чатгпт
Технически модели не генерят весь текст ответа за раз, а делают это по одному токену. Сюда приплетаем тот факт, что люди читают текст последовательно, и получаем, что здесь идеально ложится стриминг: когда мы отдаем пользователю новый токен сразу, как он готов. Это не вынуждает пользователя ждать несколько секунд перед ответом, а позволяет сразу читать то, что сгенерилось
Overall, оптимизировать TTFB/FCP имеет смысл когда одновременно
1) полный response time долгий
2) неполный контент уже имеет смысл и будет полезен пользователю
p.s.: поправил несколько неточностей и решил перепостнуть
TTFB (Time To First Byte) — это время от отправки запроса до получения первого байта данных от сервера
FCP (First Contentful Paint) — это время от открытия страницы до того как на экране появится первый контент
Почему я рассматриваю эти метрики одновременно? Потому что зачастую они оптимизируются с одной целью — как можно раньше что-то показать пользователю, т.е. сделать интерфейс отзывчивее
---
В зависимости от контекста response time может иметь два значения:
1. Конкретный запрос к бэкенду
Это время от начала запроса до получения последнего байта по конкретному запросу к апишке бэкенда
response time = TTFB + время загрузки всех данных по этому запросу
Пример: пользователь тыкнул "воспроизвести" на плеере и у него сразу начало играться видео, без ожидания скачивания всего видео-файла
В основном, для конечного пользователя здесь будет разница, когда есть какой-то стриминг: видео/аудио/...
2. Загрузка всей страницы
Более неформальный вариант — это время от момента открытия страницы до загрузки всех необходимых данных
response time = FCP + время загрузки всех данных для этой страницы
Пример: при открытии ленты в соц сети сначала подгрузится разметка, затем текст постов, затем фото в постах
Конечный пользователь ощутит разницу, когда фронтенд не дожидается всех данных от сервера и потом их отрисовывает, а делает несколько запросов до бэкенда и рисует страницу по частям
---
Вообще, один из актуальных примеров это диалог с чатгпт
Технически модели не генерят весь текст ответа за раз, а делают это по одному токену. Сюда приплетаем тот факт, что люди читают текст последовательно, и получаем, что здесь идеально ложится стриминг: когда мы отдаем пользователю новый токен сразу, как он готов. Это не вынуждает пользователя ждать несколько секунд перед ответом, а позволяет сразу читать то, что сгенерилось
Overall, оптимизировать TTFB/FCP имеет смысл когда одновременно
1) полный response time долгий
2) неполный контент уже имеет смысл и будет полезен пользователю
p.s.: поправил несколько неточностей и решил перепостнуть
1👍31💅1 1
Забавно, что статья, как положить постгрес, воспринимается гораздо проще статей про оптимизации
Домашка от Тайлера — прочитать и внедрить в своем сервисе https://byteofdev.com/posts/making-postgres-slow/#the-parameters
Домашка от Тайлера — прочитать и внедрить в своем сервисе https://byteofdev.com/posts/making-postgres-slow/#the-parameters
ByteofDev
Making Postgres 42,000x slower because I am unemployed
As an respectable unemployed person must do, I tried to make Postgres as slow as possible
8😁47👍9💅5 2
На мой взгляд, один из признаков мастерства в каком-то деле — это наличие структуры мышления, которая позволяет принимать решения осознанно, а не интуитивно
Пару месяцев назад я вступил в новую должность тимлида тимлидов и как раз ощутил, что при решении некоторых новых проблем еще нет той самой структуры
И тут есть два пути:
- Ходить по граблям и итеративно выстраивать свою структуру
- Поучиться на опыте других
Ходить по граблям я не очень люблю (хотя иногда полезно), поэтому чтобы ускорить этот процесс, я пошел на курс для M2+ руководителей к ребятам из Стратоплана
Это значит, что в канале теперь иногда будет менеджерский контент, тк буду публиковать всякие кулстори и интересные моменты с курса
---
Пару слов про поступление
Оно проходит в два этапа
1. Эссе про опыт + решение менеджерского кейса
2. Собеседование, где обсуждаете опыт и кейс
Собеседование здесь выступает скорее в роли проверки, что программа курса действительно релевантна для твоего опыта и целей
Обучение стартует в конце сентября, примерно в это же время начнутся первые посты на эту тему
stay tuned!
Пару месяцев назад я вступил в новую должность тимлида тимлидов и как раз ощутил, что при решении некоторых новых проблем еще нет той самой структуры
И тут есть два пути:
- Ходить по граблям и итеративно выстраивать свою структуру
- Поучиться на опыте других
Ходить по граблям я не очень люблю (хотя иногда полезно), поэтому чтобы ускорить этот процесс, я пошел на курс для M2+ руководителей к ребятам из Стратоплана
Это значит, что в канале теперь иногда будет менеджерский контент, тк буду публиковать всякие кулстори и интересные моменты с курса
---
Пару слов про поступление
Оно проходит в два этапа
1. Эссе про опыт + решение менеджерского кейса
2. Собеседование, где обсуждаете опыт и кейс
Собеседование здесь выступает скорее в роли проверки, что программа курса действительно релевантна для твоего опыта и целей
Обучение стартует в конце сентября, примерно в это же время начнутся первые посты на эту тему
stay tuned!
1👍57🔥23🤔11😁7💅6 3
⚡️Что может пойти не так с очередью на postgres
Очереди задач на БД — вещь объективно удобная. Тут и транзакционность, и возможность сделать очередь с приоритетами, и возможность настроить кастомные политики ретраев. И что важно — нет необходимости тянуть еще одну зависимость в виде внешней очереди
---
С точки зрения БД это write-intensive таблица, в которую летят примерно такие запросы:
1. Поллинг запланированных задач
2. Обновления задач по id (смена статусов, кол-ва ретраев и т.д.)
Конкретные реализации могут использовать
Энивей, логично сделать как минимум два индекса
1. Для первого запроса — на (scheduled_ts) where status = 'scheduled'
2. Для второго запроса — pk на (id)
---
Далее представим, что в нашей базе начинается долгая транзакция
Из поста про bloat мы знаем, что долгие транзакции держат горизонт базы и не позволяют autovacuum-у чистить dead tuples, которые "старше" этого горизонта
Посмотрим, как это заафектит наш индекс на (scheduled_ts). На самом нижнем уровне индекса есть leaf pages, которые между собой образуют двусвязный список
В ходе выполнения первого запроса для выборки задач у нас есть сортировка по scheduled_ts, поэтому мы
1. Спускаемся по btree в самую левую часть
2. Идем в отсортированном порядке по двусвязному списку
Затем мы проставляем этим задачам другой статус, например, 'running'. Эти апдейты создают новые версии строк, а старые помечают удаленными
Но если autovacuum не работает, то удаленные версии строк не будут чиститься, и в левой части индекса начнут копиться dead tuples
Изначальное состояние индекса по scheduled_ts:
Сделали выборку, апдейтнули статусы
Сделали выборку, апдейтнули статусы
И так далее
Если подождать часок, то может образоваться "полоска" из нескольких сотен тысяч dead tuples. И чтобы сделать очередную выборку, нам сначала нужно будет вручную пробежаться по этой "полоске" из тысяч dead tuples и только после этого взять живые записи
Это может привести к тому, что скорость выборки деградирует на несколько порядков, например, с 1мс до 200мс
А если таких выборок в секунду происходит штук 50, то чисто на выборки нам придется тратить 50 * 0.2 = 10 cpu cores
Если подождать еще часок, то кластер развалится...
---
Мораль сей басни такова: сочетание долгих транзакций и queue-like ворклоада на постгресе — вещь крайне опасная
Но иногда долгих транзакций просто не избежать. Один из самых частых примеров: создание индекса на большой таблице. Да, даже create index concurrently под собой удерживает долгую транзакцию
Суммаризируя, рекомендации здесь весьма банальные:
- Стараться избегать долгих транзакций
- Если все-таки надо, то снижать нагрузку на время обслуживания
- Не допускать, чтобы таблицы сильно разрастались. Этого можно добиться с помощью партицирования, шардирования и введения retention-а (например, хранить данные только за последнюю неделю) — это позволит выполнять создание индекса на таблице сильно быстрее
Очереди задач на БД — вещь объективно удобная. Тут и транзакционность, и возможность сделать очередь с приоритетами, и возможность настроить кастомные политики ретраев. И что важно — нет необходимости тянуть еще одну зависимость в виде внешней очереди
---
С точки зрения БД это write-intensive таблица, в которую летят примерно такие запросы:
1. Поллинг запланированных задач
select * from tasks
where status = 'scheduled'
and scheduled_ts < now()
order by scheduled_ts
limit 100
2. Обновления задач по id (смена статусов, кол-ва ретраев и т.д.)
update tasks
set ...
where id = 123
Конкретные реализации могут использовать
for update skip locked либо heartbeat pattern, но сейчас это не суть вопросаЭнивей, логично сделать как минимум два индекса
1. Для первого запроса — на (scheduled_ts) where status = 'scheduled'
2. Для второго запроса — pk на (id)
---
Далее представим, что в нашей базе начинается долгая транзакция
Из поста про bloat мы знаем, что долгие транзакции держат горизонт базы и не позволяют autovacuum-у чистить dead tuples, которые "старше" этого горизонта
Посмотрим, как это заафектит наш индекс на (scheduled_ts). На самом нижнем уровне индекса есть leaf pages, которые между собой образуют двусвязный список
[row1, row2, row3] <-> [row4, row5, row6] <-> [...]
В ходе выполнения первого запроса для выборки задач у нас есть сортировка по scheduled_ts, поэтому мы
1. Спускаемся по btree в самую левую часть
2. Идем в отсортированном порядке по двусвязному списку
Затем мы проставляем этим задачам другой статус, например, 'running'. Эти апдейты создают новые версии строк, а старые помечают удаленными
Но если autovacuum не работает, то удаленные версии строк не будут чиститься, и в левой части индекса начнут копиться dead tuples
Изначальное состояние индекса по scheduled_ts:
[row1, row2, row3] <-> [row4, row5, row6] <-> [...]
Сделали выборку, апдейтнули статусы
[dead, dead, dead] <-> [row4, row5, row6] <-> [...]
Сделали выборку, апдейтнули статусы
[dead, dead, dead] <-> [dead, dead, dead] <-> [...]
И так далее
Если подождать часок, то может образоваться "полоска" из нескольких сотен тысяч dead tuples. И чтобы сделать очередную выборку, нам сначала нужно будет вручную пробежаться по этой "полоске" из тысяч dead tuples и только после этого взять живые записи
Это может привести к тому, что скорость выборки деградирует на несколько порядков, например, с 1мс до 200мс
А если таких выборок в секунду происходит штук 50, то чисто на выборки нам придется тратить 50 * 0.2 = 10 cpu cores
Если подождать еще часок, то кластер развалится...
---
Мораль сей басни такова: сочетание долгих транзакций и queue-like ворклоада на постгресе — вещь крайне опасная
Но иногда долгих транзакций просто не избежать. Один из самых частых примеров: создание индекса на большой таблице. Да, даже create index concurrently под собой удерживает долгую транзакцию
Суммаризируя, рекомендации здесь весьма банальные:
- Стараться избегать долгих транзакций
- Если все-таки надо, то снижать нагрузку на время обслуживания
- Не допускать, чтобы таблицы сильно разрастались. Этого можно добиться с помощью партицирования, шардирования и введения retention-а (например, хранить данные только за последнюю неделю) — это позволит выполнять создание индекса на таблице сильно быстрее
3👍55🔥9💅4✍1 1
Не всякая неидеальность — техдолг
"Некрасивая" архитектура — еще не техдолг
"Некрасивая" архитектура, из-за которой разработка простой фичи требует изменений в 20 сервисах — уже техдолг
Потому что техдолг — это набор технических решений, которые снижают скорость и/или снижают качество и/или повышают стоимость разработки, что приводит к негативным последствиям для целей бизнеса
Из этого определения следуют два простых и важных умозаключения:
1. Бизнес и разработка должны быть "на одной стороне"
Часто может возникать ситуация, что бизнес отказывается давать ресурсы на технические задачи, так как всегда есть более приоритетные фичи. Здесь важно объяснить, что техдолг делается именно для них в долгосрочной перспективе:
- запутанная архитектура, в которую сложно добавлять новые фичи => высокий TTM
- много багов => расходы на поддержку
- сервис нестабилен и регулярно падает => пользователь может просто уйти к конкурентам
2. Продуктовые и технические задачи должны приоритезироваться вместе
Это следствие из первого пункта. Если продуктовые фичи скорее про "как заработать денег", то техдолг про "как не потерять деньги". И эти вещи должны скориться вместе друг с другом, чтобы можно было сопоставить, что важнее, и какие риски мы можем принять
---
Важный момент (про который часто забывают), чтобы такая схема работала — исправление критичного техдолга должно оцениваться по важности на уровне с продуктовыми фичами на perfomance review. Без этого логично, что у разработчиков не будет никакой мотивации заниматься техдолгом
"Некрасивая" архитектура — еще не техдолг
"Некрасивая" архитектура, из-за которой разработка простой фичи требует изменений в 20 сервисах — уже техдолг
Потому что техдолг — это набор технических решений, которые снижают скорость и/или снижают качество и/или повышают стоимость разработки, что приводит к негативным последствиям для целей бизнеса
Из этого определения следуют два простых и важных умозаключения:
1. Бизнес и разработка должны быть "на одной стороне"
Часто может возникать ситуация, что бизнес отказывается давать ресурсы на технические задачи, так как всегда есть более приоритетные фичи. Здесь важно объяснить, что техдолг делается именно для них в долгосрочной перспективе:
- запутанная архитектура, в которую сложно добавлять новые фичи => высокий TTM
- много багов => расходы на поддержку
- сервис нестабилен и регулярно падает => пользователь может просто уйти к конкурентам
2. Продуктовые и технические задачи должны приоритезироваться вместе
Это следствие из первого пункта. Если продуктовые фичи скорее про "как заработать денег", то техдолг про "как не потерять деньги". И эти вещи должны скориться вместе друг с другом, чтобы можно было сопоставить, что важнее, и какие риски мы можем принять
---
Важный момент (про который часто забывают), чтобы такая схема работала — исправление критичного техдолга должно оцениваться по важности на уровне с продуктовыми фичами на perfomance review. Без этого логично, что у разработчиков не будет никакой мотивации заниматься техдолгом
5👍51🔥9
Раз в несколько месяцев я рекомендую другие каналы по бэкенду/управлению, которые сам читаю и что-то оттуда черпаю (из последнего раз и два)
Сегодня — про канал @thisnotes, автором которого является Ваня Ходор — руководитель одной из команд в Яндекс Лавке
Ваня пишет как на генерик бэкендовые темы, так и про всякие забористые штуки типа CRDT и полнотекстового поиска. Также иногда можно увидеть контент по C++
С чего можно начать:
- Про shared databases
- Что под капотом у поиска Яндекс Лавки
- Почему балансировка трафика это не всегда просто
- Пост, который хотел сам написать, но не дошли руки — про устройство codesearch
Если вам нравится контент в моем канале, но подобного контента хочется побольше, то канал выше — мастхев:)
Сегодня — про канал @thisnotes, автором которого является Ваня Ходор — руководитель одной из команд в Яндекс Лавке
Ваня пишет как на генерик бэкендовые темы, так и про всякие забористые штуки типа CRDT и полнотекстового поиска. Также иногда можно увидеть контент по C++
С чего можно начать:
- Про shared databases
- Что под капотом у поиска Яндекс Лавки
- Почему балансировка трафика это не всегда просто
- Пост, который хотел сам написать, но не дошли руки — про устройство codesearch
Если вам нравится контент в моем канале, но подобного контента хочется побольше, то канал выше — мастхев:)
2👍27🔥7
Про PACELС
"На практике" CAP говорит, что при разделении сети в системе:
- либо каждое чтение возвращает последнюю запись, но может быть недоступность (CP)
- либо доступность, но можем не видеть записанные данные (AP)
Если же сеть в порядке, то система может быть доступной и консистентной одновременно
Однако CAP не раскрывает еще один трейдофф: даже когда с сетью все в порядке, соблюдение Consistency может требовать дополнительных задержек (например, на синхронную репликацию)
То есть, если с сетью все в порядке, то:
- либо чтение возвращает последнюю запись, но может увеличиваться latency
- либо latency низкое, но можем не видеть записанные данные
Именно это описывается принципом PACELC, который расширяет CAP на случай, когда с сетью все в порядке:
Это позволяет классифицировать системы как PA/EL, PA/EC, PC/EL, PC/EC. Например, DynamoDB — PA/EL: выбирает доступность вместо согласованности при разделении сети, а когда с сетью все в порядке — минимальную задержку вместо согласованности
p.s.: такие классификации — это не формальные описания систем, а скорее ментальные модели, которыми можно удобно оперировать. На эту тему есть хорошая статья
"На практике" CAP говорит, что при разделении сети в системе:
- либо каждое чтение возвращает последнюю запись, но может быть недоступность (CP)
- либо доступность, но можем не видеть записанные данные (AP)
Если же сеть в порядке, то система может быть доступной и консистентной одновременно
Однако CAP не раскрывает еще один трейдофф: даже когда с сетью все в порядке, соблюдение Consistency может требовать дополнительных задержек (например, на синхронную репликацию)
То есть, если с сетью все в порядке, то:
- либо чтение возвращает последнюю запись, но может увеличиваться latency
- либо latency низкое, но можем не видеть записанные данные
Именно это описывается принципом PACELC, который расширяет CAP на случай, когда с сетью все в порядке:
if (Partition) {
Availability OR Consistency
} else {
Latency OR Consistency
}
Это позволяет классифицировать системы как PA/EL, PA/EC, PC/EL, PC/EC. Например, DynamoDB — PA/EL: выбирает доступность вместо согласованности при разделении сети, а когда с сетью все в порядке — минимальную задержку вместо согласованности
p.s.: такие классификации — это не формальные описания систем, а скорее ментальные модели, которыми можно удобно оперировать. На эту тему есть хорошая статья
1👍53🔥6
Немножко про LLM системы
Работа вынуждает иногда что-то читать на эту тему, поэтому ловите пару полезных статеек
Workflows and Agents
Статья описывает самые популярные типы систем на базе LLM
tl;dr (картинка оттуда):
1. Workflow
Все пути исполнения флоу заранее известны. Делится на два подвида:
1.1. Путь исполнения единственен. В некоторых местах флоу вызывается модель
1.2. Путь исполнения может меняться в зависимости от ответов модели. Например, отправили вопрос пользователя в классификатор, который определил тематику, и в зависимости от тематики исполнение идет в одну из веток
2. Agent
Путь исполнения заранее не известен. Модель сама рассуждает и решает, что ей делать, вызывает внешние тулы (например, с помощью MCP), обрабатывает результаты, думает что делать дальше и т.д.
12 правил разработки проектов с LLM
Статья от @vikulin_ai (подписывайтесь, канал классный, и это даже не реклама!), в которой как ни парадоксально описываются 12 правил разработки проектов с LLM. Читается легко, много полезных референсов
Building effective AI agents
Статья от Anthropic: описываются некоторые подробности про типы LLM систем, описанные выше
Работа вынуждает иногда что-то читать на эту тему, поэтому ловите пару полезных статеек
Workflows and Agents
Статья описывает самые популярные типы систем на базе LLM
tl;dr (картинка оттуда):
1. Workflow
Все пути исполнения флоу заранее известны. Делится на два подвида:
1.1. Путь исполнения единственен. В некоторых местах флоу вызывается модель
1.2. Путь исполнения может меняться в зависимости от ответов модели. Например, отправили вопрос пользователя в классификатор, который определил тематику, и в зависимости от тематики исполнение идет в одну из веток
2. Agent
Путь исполнения заранее не известен. Модель сама рассуждает и решает, что ей делать, вызывает внешние тулы (например, с помощью MCP), обрабатывает результаты, думает что делать дальше и т.д.
12 правил разработки проектов с LLM
Статья от @vikulin_ai (подписывайтесь, канал классный, и это даже не реклама!), в которой как ни парадоксально описываются 12 правил разработки проектов с LLM. Читается легко, много полезных референсов
Building effective AI agents
Статья от Anthropic: описываются некоторые подробности про типы LLM систем, описанные выше
1👍26🔥7🤔1