⚡Виды индексов в БД
❓Проблема:
Когда таблица увеличивается, длительность seq scan (последовательного чтения строк) растет линейно по отношению к размеру таблицы. Когда размер доходит до нескольких миллионов, простые запросы, например, поиск по идентификатору, начинают работать несколько секунд.
✅ Решение:
Использовать индексы - структуры данных, призванные ускорить поиск. Существует довольно много типов индексов, рассмотрим наиболее популярные:
1. B-Tree
B-Tree - наиболее распространенный и универсальный тип индекса. Представляет собой сбалансированное дерево поиска, которое позволяет быстро производить поиск по равенству, ренжу, префиксу строки.
2. Hash
Hash-индексы работают на основе хеш-таблицы. Позволяют производить быстрый поиск по равенству.
3. GIN
GIN (Generalized Inverted Index) представляет собой инвертированный индекс, который строится по столбцу с типом ts_vector для проведения эффективного полнотекстового поиска. Также (в Postgres) для GIN существуют operator classes, как, например, gin_trgm_ops, который позволяет эффективно искать по текстовому столбцу по запросам … where column like ‘%query%’ с помощью разбиения текста на триграммы.
❓Проблема:
Когда таблица увеличивается, длительность seq scan (последовательного чтения строк) растет линейно по отношению к размеру таблицы. Когда размер доходит до нескольких миллионов, простые запросы, например, поиск по идентификатору, начинают работать несколько секунд.
✅ Решение:
Использовать индексы - структуры данных, призванные ускорить поиск. Существует довольно много типов индексов, рассмотрим наиболее популярные:
1. B-Tree
B-Tree - наиболее распространенный и универсальный тип индекса. Представляет собой сбалансированное дерево поиска, которое позволяет быстро производить поиск по равенству, ренжу, префиксу строки.
2. Hash
Hash-индексы работают на основе хеш-таблицы. Позволяют производить быстрый поиск по равенству.
3. GIN
GIN (Generalized Inverted Index) представляет собой инвертированный индекс, который строится по столбцу с типом ts_vector для проведения эффективного полнотекстового поиска. Также (в Postgres) для GIN существуют operator classes, как, например, gin_trgm_ops, который позволяет эффективно искать по текстовому столбцу по запросам … where column like ‘%query%’ с помощью разбиения текста на триграммы.
🔥24👍9
⚡Индексирование больших таблиц
❓Проблема:
Есть большая таблица, хотим накатить на нее индекс. Обычный
✅ Решение:
Использовать конструкцию
Пример: хотим накатить следующий индекс
Для этого перед деплоем приложения руками делаем
И в файле миграции пишем
Смысл накатывания индекса руками в том, что инструменты для миграции схемы БД обычно применяют миграции синхронно, что при деплое приложения заставило бы ждать существенное время.
❓Проблема:
Есть большая таблица, хотим накатить на нее индекс. Обычный
create index блокирует таблицу на время создания индекса. В случае таблиц с сотнями миллионами строк длительность создания индекса может достигать часа. В это время все пишущие запросы будут падать с lock timeout из-за невозможности взятия блокировки => сервис начнет пятисотить => фактически получаем даунтайм.✅ Решение:
Использовать конструкцию
create index concurrently. Она позволяет создать индекс, не блокируя пишущие запросы, однако создание индекса в таком режиме занимает существенно больше времени.Пример: хотим накатить следующий индекс
create index idx__big_table__column
on big_table(column)
Для этого перед деплоем приложения руками делаем
create index concurrently idx__big_table__column
on big_table(column)
И в файле миграции пишем
create index if not exist idx__big_table__column
on big_table(column)
Смысл накатывания индекса руками в том, что инструменты для миграции схемы БД обычно применяют миграции синхронно, что при деплое приложения заставило бы ждать существенное время.
👍57🔥16
⚡Оптимистические блокировки в СУБД
❓Проблема:
В БД есть обновляемая сущность. Период между чтением сущности и записью может быть большим. При этом хотим, чтобы если пользователь отправляет запрос на обновление, он не перезаписывал изменения другого пользователя. То есть хотим избежать ситуаций
✅ Решение:
Использовать оптимистические блокировки. Они заключаются в том, что перед обновлением мы проверяем, что на руках имеем актуальную версию сущности. Реализовать это можно так:
1. Добавляем в сущность БД колонку
2. Навешиваем триггер, чтобы при каждом апдейте колонка инкрементилась
3. При обновлении проверяем версию
Запрос на обновление будет выглядеть так:
Далее если returning * ничего не вернул, значит мы имеем неактуальное состояние. Если вернул, то мы имели актуальное состояние и успешно сделали апдейт.
❓Проблема:
В БД есть обновляемая сущность. Период между чтением сущности и записью может быть большим. При этом хотим, чтобы если пользователь отправляет запрос на обновление, он не перезаписывал изменения другого пользователя. То есть хотим избежать ситуаций
user1: read
user2: read
user1: write
user2: write (перезаписали обновления user1)
✅ Решение:
Использовать оптимистические блокировки. Они заключаются в том, что перед обновлением мы проверяем, что на руках имеем актуальную версию сущности. Реализовать это можно так:
1. Добавляем в сущность БД колонку
revision с дефолтом 02. Навешиваем триггер, чтобы при каждом апдейте колонка инкрементилась
3. При обновлении проверяем версию
Запрос на обновление будет выглядеть так:
update entity
set …
where id = <entity_id>
and revision = <revision>
returning *;
Далее если returning * ничего не вернул, значит мы имеем неактуальное состояние. Если вернул, то мы имели актуальное состояние и успешно сделали апдейт.
👍32🔥9🙏4
⚡Оптимизация производительности приложений
Качественный обзорный доклад про роль, общие принципы профилирования при разработке ПО, а также про отличия оптимизаций в “зеленой”, “желтой” и “красной” зонах с множеством примеров.
Качественный обзорный доклад про роль, общие принципы профилирования при разработке ПО, а также про отличия оптимизаций в “зеленой”, “желтой” и “красной” зонах с множеством примеров.
YouTube
Алексей Шипилёв — Перформанс: Что В Имени Тебе Моём?
Подробнее о Java-конференциях:
— весной — JPoint: https://jrg.su/gTrwHx
— осенью — Joker: https://jrg.su/h7yvG4
— —
. . . . Алексей Шипилёв — Перформанс: Что В Имени Тебе Моём?
Java-конференция Joker 2016, Санкт-Петербург, 14-15.10.2016
Оптимизация производительности…
— весной — JPoint: https://jrg.su/gTrwHx
— осенью — Joker: https://jrg.su/h7yvG4
— —
. . . . Алексей Шипилёв — Перформанс: Что В Имени Тебе Моём?
Java-конференция Joker 2016, Санкт-Петербург, 14-15.10.2016
Оптимизация производительности…
🔥10👍4
⚡Deadline propagation
❓Проблема:
Есть бизнес-операция, задействующая несколько сервисов, с некоторым SLA времени ответа. В случае отказа одного из сервисов, мы не хотим заставлять пользователя ждать и нагружать остальные сервисы, а просто выводим понятное сообщение об ошибке.
✅ Решение:
Использовать технику deadline propagation. Она заключается в том, что на стороне клиента генерируется дедлайн, до которого нужно исполнить операцию, и отправляется вместе с запросом серверу. В случае межсервисных взаимодействий дедлайн пересылается от сервиса к сервису, что позволяет избежать ситуаций, когда изначальный запрос уже отменился, но у нас остались подвешенные межсервисные запросы.
❓Проблема:
Есть бизнес-операция, задействующая несколько сервисов, с некоторым SLA времени ответа. В случае отказа одного из сервисов, мы не хотим заставлять пользователя ждать и нагружать остальные сервисы, а просто выводим понятное сообщение об ошибке.
✅ Решение:
Использовать технику deadline propagation. Она заключается в том, что на стороне клиента генерируется дедлайн, до которого нужно исполнить операцию, и отправляется вместе с запросом серверу. В случае межсервисных взаимодействий дедлайн пересылается от сервиса к сервису, что позволяет избежать ситуаций, когда изначальный запрос уже отменился, но у нас остались подвешенные межсервисные запросы.
👍31🔥5
⚡Consistent hashing
❓Проблема:
Есть шардированное хранилище. При записи объекта по ключу определяем, в какое хранилище писать с помощью
✅ Решение:
Использовать консистентное хеширование. Это метод заключается в следующем: представим что наши сервера и ключи располагаются на окружности:
1. Назначаем каждому шарду псевдорандомное число-угол из [0, 360), которое определяет место на окружности
2. При записи объекта по ключу определяем его угол как
3. От полученного значения идем по окружности против часовой стрелки и находим ближайший шард, куда и записываем значение
Всё это нужно для того, чтобы при добавлении нового шарда B, который попал между A и C, нам достаточно было перехешировать объекты лишь находящиеся между A и B, а не все. Иначе говоря, добавление или удаление нового шарда требует перехеширований в среднем
❓Проблема:
Есть шардированное хранилище. При записи объекта по ключу определяем, в какое хранилище писать с помощью
hash(key) % n, где n - число серверов. Вдруг место заканчивается и нужно добавить новый шард, для этого мы должны перехешировать все ключи и перераспределить данные между шардами, что долго и дорого.✅ Решение:
Использовать консистентное хеширование. Это метод заключается в следующем: представим что наши сервера и ключи располагаются на окружности:
1. Назначаем каждому шарду псевдорандомное число-угол из [0, 360), которое определяет место на окружности
2. При записи объекта по ключу определяем его угол как
hash(key) % 3603. От полученного значения идем по окружности против часовой стрелки и находим ближайший шард, куда и записываем значение
Всё это нужно для того, чтобы при добавлении нового шарда B, который попал между A и C, нам достаточно было перехешировать объекты лишь находящиеся между A и B, а не все. Иначе говоря, добавление или удаление нового шарда требует перехеширований в среднем
n/m объектов вместо n, где n - общее число объектов, m - число шардов.👍47🔥16🤔4
⚡CQRS + Event Sourcing
CQRS - паттерн, призванный разделить commands (модифицирующие запросы) и queries (запросы на чтение). Обычно в таком случае для записи и чтения используются разные БД, более подходящие под конкретные нужды.
Event Sourcing - паттерн, при котором состояние сущности представляет собой последовательность некоторых событий, например:
В докладе на реальном примере рассказывается, почему CQRS и Event Sourcing хорошо сочетаются, а также преимущества и недостатки подобной архитектуры.
CQRS - паттерн, призванный разделить commands (модифицирующие запросы) и queries (запросы на чтение). Обычно в таком случае для записи и чтения используются разные БД, более подходящие под конкретные нужды.
Event Sourcing - паттерн, при котором состояние сущности представляет собой последовательность некоторых событий, например:
1. entity created
2. name changed
3. size changed
…
В докладе на реальном примере рассказывается, почему CQRS и Event Sourcing хорошо сочетаются, а также преимущества и недостатки подобной архитектуры.
YouTube
Ануар Нурмаканов — Event Sourcing и CQRS на конкретном примере
Подробнее о Java-конференциях:
— весной — JPoint: https://jrg.su/gTrwHx
— осенью — Joker: https://jrg.su/h7yvG4
— —
. . . . Что такое Event Sourcing и зачем нам CQRS? Все слышали об этих двух парадигмах — теперь пора разобраться конкретнее, как реализовать…
— весной — JPoint: https://jrg.su/gTrwHx
— осенью — Joker: https://jrg.su/h7yvG4
— —
. . . . Что такое Event Sourcing и зачем нам CQRS? Все слышали об этих двух парадигмах — теперь пора разобраться конкретнее, как реализовать…
🔥20👍8
⚡Distributed tracing
❓Проблема:
Начинает медленно работать операция, охватывающая несколько сервисов. Хочется локализовать проблему и найти конкретное место, которое тормозит.
✅ Решение:
Использовать distributed tracing, основными сущностями которого являются:
Trace - полный путь запроса через систему, который включает в себя операции, которые прошел запрос от начала до конца.
Span - непрерывный сегмент работы, выполняемый в рамках трейса. Span может быть, например, вызовом API или исполнением запроса к базе данных. Каждый span имеет имя, начальное время, продолжительность и дополнительные метаданные. Они выстраиваются в иерархию parent-chlid, например, если один сервис для выполнения запроса должен вызвать другой (на графике сервис A вызывает B, который вызывает C и т.д).
Таким образом, по спенам можно легко понять, что является узким местом, и исправить это.
❓Проблема:
Начинает медленно работать операция, охватывающая несколько сервисов. Хочется локализовать проблему и найти конкретное место, которое тормозит.
✅ Решение:
Использовать distributed tracing, основными сущностями которого являются:
Trace - полный путь запроса через систему, который включает в себя операции, которые прошел запрос от начала до конца.
Span - непрерывный сегмент работы, выполняемый в рамках трейса. Span может быть, например, вызовом API или исполнением запроса к базе данных. Каждый span имеет имя, начальное время, продолжительность и дополнительные метаданные. Они выстраиваются в иерархию parent-chlid, например, если один сервис для выполнения запроса должен вызвать другой (на графике сервис A вызывает B, который вызывает C и т.д).
Таким образом, по спенам можно легко понять, что является узким местом, и исправить это.
👍25🔥6🤔1💅1
⚡Bloom Filter
❓Проблема:
Есть высоконагруженная база данных. В случае гарантированного отсутствия запрашиваемого элемента в БД хотим не делать бессмысленное (и дорогое) IO с диском, а просто отдать ответ, что элемента нет.
✅ Решение:
Использовать фильтр Блума - структуру данных, которая по входной строке умеет давать два ответа:
- Элемента точно нет в множетсве
- Элемент возможно содержится в множетсве
Принцип работы следующий:
Заводится битовый массив длины N. Выбираются несколько фиксированных хеш-функций, отдающих значения из [0, N-1]. В примере на картинке их 3.
Добавление элемента в множество: для каждой хеш функции, считаем хеш по входной строке и выставляем этот бит в массиве в единичку.
Проверка наличия: если хотя бы для одной хеш-функции ее результат по входной строке в битовом массиве выставлен в 0, то такой строки гарантированно нет (в противном случае при добавлении этой строки, там бы была единичка). Однако, если все единички, то что-то утверждать мы не можем, поскольку единичка могла “прийти” от другой строки.
❓Проблема:
Есть высоконагруженная база данных. В случае гарантированного отсутствия запрашиваемого элемента в БД хотим не делать бессмысленное (и дорогое) IO с диском, а просто отдать ответ, что элемента нет.
✅ Решение:
Использовать фильтр Блума - структуру данных, которая по входной строке умеет давать два ответа:
- Элемента точно нет в множетсве
- Элемент возможно содержится в множетсве
Принцип работы следующий:
Заводится битовый массив длины N. Выбираются несколько фиксированных хеш-функций, отдающих значения из [0, N-1]. В примере на картинке их 3.
Добавление элемента в множество: для каждой хеш функции, считаем хеш по входной строке и выставляем этот бит в массиве в единичку.
Проверка наличия: если хотя бы для одной хеш-функции ее результат по входной строке в битовом массиве выставлен в 0, то такой строки гарантированно нет (в противном случае при добавлении этой строки, там бы была единичка). Однако, если все единички, то что-то утверждать мы не можем, поскольку единичка могла “прийти” от другой строки.
👍35🔥12
⚡Почему батчевые update/delete не безопасны
Конкурентно исполняющиеся стейтменты вида
могут приводить к дедлокам.
Почему так происходит? Чтобы произвести обновление или удаление, обычно производится следующий набор операций:
1. Выборка записей, подходящих под условие where
2. Блокировка записей
3. Повторная проверка условия + исполнение самого update/delete
И поскольку порядок выборки записей никак не регламентирован, на втором шаге может возникнуть дедлок:
Наиболее простым решением будет явное взятие блокировок в нужном порядке перед апдейтом:
Конкурентно исполняющиеся стейтменты вида
update entity set … where id in (1, 2)
могут приводить к дедлокам.
Почему так происходит? Чтобы произвести обновление или удаление, обычно производится следующий набор операций:
1. Выборка записей, подходящих под условие where
2. Блокировка записей
3. Повторная проверка условия + исполнение самого update/delete
И поскольку порядок выборки записей никак не регламентирован, на втором шаге может возникнуть дедлок:
tx1: lock(1) _____ lock(2) _____
tx2: _____ lock(2) ______ lock(1)
Наиболее простым решением будет явное взятие блокировок в нужном порядке перед апдейтом:
begin;
select * from entity where id in (1, 2) order by id for update;
update entity set … where id in (1, 2);
commit;
👍50🔥15🤯1
⚡Sharing data between services
❓Проблема:
Чтобы сервису X выполнить запрос, ему нужно получить данные из сервиса Y, который нестабильно/долго отвечает.
✅ Решение:
Сделать в сервисе X локальную копию требуемых данных, и обновлять их по событиям из сервиса Y.
Плюсы:
- уменьшается связность между сервисами, поскольку в случае отказа сервиса Y, сервис X продолжит работать
Минусы:
- сервис Y обязан публиковать события о своих обновлениях
- “лаг репликации” - в локальном view какое-то время могут быть неактуальные данные
❓Проблема:
Чтобы сервису X выполнить запрос, ему нужно получить данные из сервиса Y, который нестабильно/долго отвечает.
✅ Решение:
Сделать в сервисе X локальную копию требуемых данных, и обновлять их по событиям из сервиса Y.
Плюсы:
- уменьшается связность между сервисами, поскольку в случае отказа сервиса Y, сервис X продолжит работать
Минусы:
- сервис Y обязан публиковать события о своих обновлениях
- “лаг репликации” - в локальном view какое-то время могут быть неактуальные данные
👍34🔥5
⚡Conflict-free replicated data type
❓Проблема:
Есть некоторые данные, которые реплицируются на несколько узлов. Например, в рамках коллаборативного инструмента, в котором изменения от одного участника должны увидеть остальные. При этом, если два пользователя модифицируют одну и ту же часть данных, будут возникать конфликты, и их нужно решать.
✅ Решение:
CRDT - структура данных, реплицированная на несколько узлов, обладающая следующими свойствами:
- приложение может обновлять реплику без координации с другими репликами
- в случае конфликтов структура данных гарантированно умеет их разрешать
- в конечном счете все реплики будут согласованы
Разделяют operation-based и state-based типы: в одном случае рассылаются лишь обновления структуры данных, в другом - цельное состояние структуры.
Рассмотрим operation-based.
Принцип работы следующий:
- один из узлов хочет сделать обновление
- генирируется функция update, которая принимает на вход некоторое состояние структуры данных, и возвращает новое состояние
- функция применяется локально - на текущую реплику
- функция отсылается на остальные реплики, которые тоже применяют эту операцию
Ограничения на функцию update:
- либо гарантировать порядок доставки update-ов, либо гарантировать, что update_1(update_2(state)) = update_2(update_1(state)), иначе говоря, функции update_1 и update_2 должны коммутировать
- если update может быть доставлен несколько раз, то он должен быть идемпотентным, то есть update(state) = update(update(state))
Собственно, именно подобные ограничения и вызывают сложности. По многим типовым структурам данных написаны пейперы, про json, например, это.
❓Проблема:
Есть некоторые данные, которые реплицируются на несколько узлов. Например, в рамках коллаборативного инструмента, в котором изменения от одного участника должны увидеть остальные. При этом, если два пользователя модифицируют одну и ту же часть данных, будут возникать конфликты, и их нужно решать.
✅ Решение:
CRDT - структура данных, реплицированная на несколько узлов, обладающая следующими свойствами:
- приложение может обновлять реплику без координации с другими репликами
- в случае конфликтов структура данных гарантированно умеет их разрешать
- в конечном счете все реплики будут согласованы
Разделяют operation-based и state-based типы: в одном случае рассылаются лишь обновления структуры данных, в другом - цельное состояние структуры.
Рассмотрим operation-based.
Принцип работы следующий:
- один из узлов хочет сделать обновление
- генирируется функция update, которая принимает на вход некоторое состояние структуры данных, и возвращает новое состояние
- функция применяется локально - на текущую реплику
- функция отсылается на остальные реплики, которые тоже применяют эту операцию
Ограничения на функцию update:
- либо гарантировать порядок доставки update-ов, либо гарантировать, что update_1(update_2(state)) = update_2(update_1(state)), иначе говоря, функции update_1 и update_2 должны коммутировать
- если update может быть доставлен несколько раз, то он должен быть идемпотентным, то есть update(state) = update(update(state))
Собственно, именно подобные ограничения и вызывают сложности. По многим типовым структурам данных написаны пейперы, про json, например, это.
arXiv.org
A Conflict-Free Replicated JSON Datatype
Many applications model their data in a general-purpose storage format such as JSON. This data structure is modified by the application as a result of user input. Such modifications are well...
👍13🔥5
⚡Saga Orchestration
Saga - паттерн, позволяющий проводить “eventually-атомарные” распределенные транзакции. То есть, в конце концов части транзакции либо выполнятся на всех сервисах, либо нигде.
Saga Choreography - разновидность, где нет централизованного компонента управления, и сервисы координируют работу друг друга с помощью событий.
Saga Orchestration - наоборот, координирует выполнение транзакции с помощью одного централизованного компонента. Обычно это реализуется так:
- В окестратор прилетает запрос на исполнение транзакции
- В базе оркестратора создается стейт-машина, описывающая какие шаги были выполнены
- После выполнения очередного шага, записываем результат в базу
- Если какой-то шаг упал, запускаем компенсационную цепочку, чтобы отменить уже выполненную часть транзакции
- Если отмена невозможна, загорается мониторинг, и человек вручную разбирается
Из плюсов:
- Простота такого подхода сильно выше нежели у хореографии
- Хорошо подходит для сложных сценариев
- Сервисы могут совсем не знать друг про друга
Минусы:
- Оркестратор является единой точкой отказа
Saga - паттерн, позволяющий проводить “eventually-атомарные” распределенные транзакции. То есть, в конце концов части транзакции либо выполнятся на всех сервисах, либо нигде.
Saga Choreography - разновидность, где нет централизованного компонента управления, и сервисы координируют работу друг друга с помощью событий.
Saga Orchestration - наоборот, координирует выполнение транзакции с помощью одного централизованного компонента. Обычно это реализуется так:
- В окестратор прилетает запрос на исполнение транзакции
- В базе оркестратора создается стейт-машина, описывающая какие шаги были выполнены
- После выполнения очередного шага, записываем результат в базу
- Если какой-то шаг упал, запускаем компенсационную цепочку, чтобы отменить уже выполненную часть транзакции
- Если отмена невозможна, загорается мониторинг, и человек вручную разбирается
Из плюсов:
- Простота такого подхода сильно выше нежели у хореографии
- Хорошо подходит для сложных сценариев
- Сервисы могут совсем не знать друг про друга
Минусы:
- Оркестратор является единой точкой отказа
🔥28👍7
⚡Reverse Proxy
Тип прокси-сервера, который стоит перед группой серверов, и обеспечивает, что все запросы, адресованные этим серверам, проходят через него.
Возможные применения:
- Балансировка нагрузки: прокси будет равномерно распределять нагрузку на стоящие за ним серверы
- Rate-limiting: например, ограничения общего числа запросов в секунду (1k RPS) и ограничение числа запросов от одного ip адреса (20 RPS)
- Кеширование контента: например, чтобы напрямую клиенту отдавать какой-то статический контент, не совершая запрос к серверу
- Производить TLS шифрование/дешифрование, снимая нагрузку с целевых серверов
Без reverse-proxy пришлось бы дублировать всю эту логику на каждом сервере, а также раскрывать внутреннюю структуру сети, чтобы клиенты могли делать запросы напрямую целевым серверам.
Тип прокси-сервера, который стоит перед группой серверов, и обеспечивает, что все запросы, адресованные этим серверам, проходят через него.
Возможные применения:
- Балансировка нагрузки: прокси будет равномерно распределять нагрузку на стоящие за ним серверы
- Rate-limiting: например, ограничения общего числа запросов в секунду (1k RPS) и ограничение числа запросов от одного ip адреса (20 RPS)
- Кеширование контента: например, чтобы напрямую клиенту отдавать какой-то статический контент, не совершая запрос к серверу
- Производить TLS шифрование/дешифрование, снимая нагрузку с целевых серверов
Без reverse-proxy пришлось бы дублировать всю эту логику на каждом сервере, а также раскрывать внутреннюю структуру сети, чтобы клиенты могли делать запросы напрямую целевым серверам.
👍30🔥4💅3
⚡Asynchronous Request-Reply pattern
❓Проблема:
На бэкенде есть асинхронное апи запуска какой-то долгой операции, а клиент хочет получить результат этой операции. Делать апи синхронным - не вариант с точки зрения архитектуры.
✅ Решение:
Использовать поллинг на клиенте:
- Клиент идет в апи запуска операции
- Бэкенд отдает operationId
- Клиент раз в какое-то время идет в эндпоинт проверки статуса операции по operationId
- Когда операция завершится, бэкенд отдает uri, по которому можно найти результат операции (либо клиент сам знает, куда сходить)
Это один из самых простых вариантов решения проблемы на чистом http в случае если, например, в проект не хочется затаскивать вебсокеты.
❓Проблема:
На бэкенде есть асинхронное апи запуска какой-то долгой операции, а клиент хочет получить результат этой операции. Делать апи синхронным - не вариант с точки зрения архитектуры.
✅ Решение:
Использовать поллинг на клиенте:
- Клиент идет в апи запуска операции
- Бэкенд отдает operationId
- Клиент раз в какое-то время идет в эндпоинт проверки статуса операции по operationId
- Когда операция завершится, бэкенд отдает uri, по которому можно найти результат операции (либо клиент сам знает, куда сходить)
Это один из самых простых вариантов решения проблемы на чистом http в случае если, например, в проект не хочется затаскивать вебсокеты.
👍33🔥3💅3
Поддерживаете ли вы порядок отправки сообщений из outbox таблицы?
Если да, то будет классно, если в комментах напишите, как у вас это реализовано
Если да, то будет классно, если в комментах напишите, как у вас это реализовано
Anonymous Poll
40%
Да
60%
Нет
🔥6🤔4💅3
⚡Soft-delete pattern
Паттерн удаления, при котором происходит не удаление записи из БД, а проставление метки
Заменяется на
Обычно этот паттерн позиционируется как способ “более безопасного” удаления, тк мы можем легко восстановить записи, но зачастую на практике такого применения он не находит.
Более реальные юзкейсы:
- Отмена операции удаления: например, в UI после совершения удаления в течение 5 секунд можно отменить операцию. В случае c soft delete - это будет просто изменением статуса
- Историчность какой либо сущности: вместо update делаем soft delete старой версии и создание новой
Недостатки:
- Большинство запросов обычно оперирует с активными сущностями, поэтому придется во всех них добавлять условие на
- Сложнее поддерживать согласованность данных. В случае дефолтного удаления мы можем ее гарантировать с помощью внешних ключей. В случае мягкого удаления у нас могут возникать ситуации, когда
Паттерн удаления, при котором происходит не удаление записи из БД, а проставление метки
deleted_at = now() (либо status = ‘INACTIVE’):delete from entity
where id = 1;
Заменяется на
update entity
set deleted_at = now()
where id = 1;
Обычно этот паттерн позиционируется как способ “более безопасного” удаления, тк мы можем легко восстановить записи, но зачастую на практике такого применения он не находит.
Более реальные юзкейсы:
- Отмена операции удаления: например, в UI после совершения удаления в течение 5 секунд можно отменить операцию. В случае c soft delete - это будет просто изменением статуса
- Историчность какой либо сущности: вместо update делаем soft delete старой версии и создание новой
Недостатки:
- Большинство запросов обычно оперирует с активными сущностями, поэтому придется во всех них добавлять условие на
deleted_at is null- Сложнее поддерживать согласованность данных. В случае дефолтного удаления мы можем ее гарантировать с помощью внешних ключей. В случае мягкого удаления у нас могут возникать ситуации, когда
ACTIVE сущность ссылается на INACTIVE. Это решается либо более сложными внешними ключами, включающими статус, либо гарантиями со стороны приложения👍35🔥4💅3🤔1
⚡Keyset pagination
❓Проблема:
Пагинация через
Начинает работать медленно при большом оффсете, поскольку мы будем по-честному проходить по этой тысяче записей, прежде чем выбрать 10, которые нам нужны.
✅ Решение:
Использовать Keyset pagination, смысл которой заключается в том, что
Рассмотрим на примере:
Первая страница:
И так далее.
Запрос, сформированный таким образом, позволяет базе данных с помощью индекса на id сразу найти первую запись, где
❓Проблема:
Пагинация через
order by + limit + offsetselect *
from events
order by id desc
limit 10 offset 1000;
Начинает работать медленно при большом оффсете, поскольку мы будем по-честному проходить по этой тысяче записей, прежде чем выбрать 10, которые нам нужны.
✅ Решение:
Использовать Keyset pagination, смысл которой заключается в том, что
offset заменяется на условие id < <последний id предыдущей страницы>Рассмотрим на примере:
Первая страница:
select *Допустим, последний id был 253, тогда вторая страница:
from events
order by id desc
limit 10
select *Третья страница:
from events
where id < 253
order by id desc
limit 10
select *
from events
where id < 243
order by id desc
limit 10
И так далее.
Запрос, сформированный таким образом, позволяет базе данных с помощью индекса на id сразу найти первую запись, где
id < <последний id предыдущей страницы> и далее просто пройтись по индексу, взяв первые 10 записей.🔥40👍11🤯8🤔1💅1
⚡Structured logging
❓Проблема:
По логам в виде plain текста тяжело осуществлять поиск, фильтрацию.
✅ Решение:
Использовать идею Structured logging:
Вместо того, чтобы записывать лог в виде plain текста
Будем записывать его в виде структуры определенного формата
Это позволит складывать логи в какую-нибудь БД, например, Elasticsearch, индексировать и эффективно производить поиск и фильтрацию.
❓Проблема:
По логам в виде plain текста тяжело осуществлять поиск, фильтрацию.
✅ Решение:
Использовать идею Structured logging:
Вместо того, чтобы записывать лог в виде plain текста
[info] [Friday, 20-Jan-23 11:17:55 UTC] The application has started.
Будем записывать его в виде структуры определенного формата
{
"timestamp": "Friday, 20-Jan-23 11:17:55 UTC",
"level": "info",
"message": "The application has started."
}Это позволит складывать логи в какую-нибудь БД, например, Elasticsearch, индексировать и эффективно производить поиск и фильтрацию.
🔥26💅6👍4🤯3🤔1
⚡Durable executions
Представим, что есть некоторая задача
Если мы ее запустим, то нужно, чтобы воркер, который ее будет исполнять, был жив минимум два часа подряд. Если по-середине ожидания что-то пойдет не так, то весь прогресс потеряется.
Именно на этом примере можно описать концепцию Durable executions:
После того, как мы сделали
- Сохраним в состояние задачи, что мы уже сделали
- Зашедулим ее на +1 час
Спустя час какой-то другой воркер сможет взять эту задачу и продолжить с прыдыдущего “чекпоинта”. То есть мы сохраняем прогресс по определенным частям задачи, что позволяет переживать отказы воркеров и позволяет исполнять задачу по частям разными воркерами.
И сейчас существует довольно много Workflow engines, которые строятся на этой концепции.
Представим, что есть некоторая задача
workflow {
firstCall()
sleep(1 hour)
secondCall()
sleep(1 hour)
thirdCall()
}Если мы ее запустим, то нужно, чтобы воркер, который ее будет исполнять, был жив минимум два часа подряд. Если по-середине ожидания что-то пойдет не так, то весь прогресс потеряется.
Именно на этом примере можно описать концепцию Durable executions:
После того, как мы сделали
firstCall(), мы не будем ждать, а сделаем следующее:- Сохраним в состояние задачи, что мы уже сделали
firstCall()- Зашедулим ее на +1 час
Спустя час какой-то другой воркер сможет взять эту задачу и продолжить с прыдыдущего “чекпоинта”. То есть мы сохраняем прогресс по определенным частям задачи, что позволяет переживать отказы воркеров и позволяет исполнять задачу по частям разными воркерами.
И сейчас существует довольно много Workflow engines, которые строятся на этой концепции.
GitHub
GitHub - meirwah/awesome-workflow-engines: A curated list of awesome open source workflow engines
A curated list of awesome open source workflow engines - meirwah/awesome-workflow-engines
👍28🔥8💅6