⚡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
Ставьте 💅 на этот пост, если нужен рассказ про возможности Temporal
temporal.io
Durable Execution Solutions
Build invincible apps with Temporal's open source durable execution platform. Eliminate complexity and ship features faster. Talk to an expert today!
💅146✍1🤔1
⚡Temporal
Temporal - оркестрационный движок, который координирует работу распределенных воркеров, сохраняет промежуточные результаты, делает ретраи и т.д.
Temporal cluster состоит из следующих компонентов:
- Frontend gateway: авторизация, rate limit
- History subsystem: хранит состояние задач в БД
- Matching subsystem: управляет очередьми задач
- Worker Service: исполняет внутренние (не пользовательские) фоновые процессы
❗️Пользовательские workflow исполняются не temporal кластером, а внешними пользовательскими воркерами. Temporal лишь координирует их работу.
Возможности:
- Обработка распределенных транзакций
- Запуск задач по крону
- Исполнение стейт машин
- И еще некоторые вещи
Как описываются задачи:
Activity - некоторое атомарное действие, например, вызов API или запрос в базу. Это действие должно быть идемпотентным, чтобы, например, в случае ретраев из-за сетевых проблем не сделать некоторое действие дважды.
Workflow состоят из вызовов activity и некоторой дополнительной логики. И как раз в местах вызова activity сохраняются промежуточные результаты workflow.
Всё это описывается с помощью SDK на стороне приложения, на текущий момент существуют реализации для Go, Java, PHP, Python, Typenoscript, .NET. Посмотреть пример описания workflow можно тут.
Temporal - оркестрационный движок, который координирует работу распределенных воркеров, сохраняет промежуточные результаты, делает ретраи и т.д.
Temporal cluster состоит из следующих компонентов:
- Frontend gateway: авторизация, rate limit
- History subsystem: хранит состояние задач в БД
- Matching subsystem: управляет очередьми задач
- Worker Service: исполняет внутренние (не пользовательские) фоновые процессы
❗️Пользовательские workflow исполняются не temporal кластером, а внешними пользовательскими воркерами. Temporal лишь координирует их работу.
Возможности:
- Обработка распределенных транзакций
- Запуск задач по крону
- Исполнение стейт машин
- И еще некоторые вещи
Как описываются задачи:
Activity - некоторое атомарное действие, например, вызов API или запрос в базу. Это действие должно быть идемпотентным, чтобы, например, в случае ретраев из-за сетевых проблем не сделать некоторое действие дважды.
Workflow состоят из вызовов activity и некоторой дополнительной логики. И как раз в местах вызова activity сохраняются промежуточные результаты workflow.
Всё это описывается с помощью SDK на стороне приложения, на текущий момент существуют реализации для Go, Java, PHP, Python, Typenoscript, .NET. Посмотреть пример описания workflow можно тут.
👍14🔥8💅5
⚡Caching patterns
При работе с кешом возникает вопрос “в какой момент нужно синхронизировать данные из бд и кеша?”. Рассмотрим три часто встречающихся паттерна:
1. Read-aside caching
Наиболее простой и часто используемый паттерн.
Как происходит чтение:
- Пробуем достать данные из кеша
- Если не получилось, идем в БД, складываем в кеш
Как происходит запись:
- Просто пишем в БД
2. Write-aside caching
Паттерн с чтением аналогичным предыдущему варианту, но при записи сразу обновляем и кеш, что позволяет в кеше иметь актуальные данные и в теории увеличить hit rate ценой того, что в кеш могут попадать данные, которые не нужны для чтения.
Как происходит чтение:
- Пробуем достать данные из кеша
- Если не получилось, идем в БД, складываем в кеш
Как происходит запись:
- Пишем в БД
- Пишем в кеш
3. Full caching
Кеш (обычно по крону) сам себя обновляет, подгружая все необходимые данные из БД. Подходит для случаев, когда хотим добиться ~100% hit rate, и данные целиком влезают в кеш.
Как происходит чтение:
- Просто читаем из кеша
Как происходит запись:
- Просто пишем в БД
При работе с кешом возникает вопрос “в какой момент нужно синхронизировать данные из бд и кеша?”. Рассмотрим три часто встречающихся паттерна:
1. Read-aside caching
Наиболее простой и часто используемый паттерн.
Как происходит чтение:
- Пробуем достать данные из кеша
- Если не получилось, идем в БД, складываем в кеш
Как происходит запись:
- Просто пишем в БД
2. Write-aside caching
Паттерн с чтением аналогичным предыдущему варианту, но при записи сразу обновляем и кеш, что позволяет в кеше иметь актуальные данные и в теории увеличить hit rate ценой того, что в кеш могут попадать данные, которые не нужны для чтения.
Как происходит чтение:
- Пробуем достать данные из кеша
- Если не получилось, идем в БД, складываем в кеш
Как происходит запись:
- Пишем в БД
- Пишем в кеш
3. Full caching
Кеш (обычно по крону) сам себя обновляет, подгружая все необходимые данные из БД. Подходит для случаев, когда хотим добиться ~100% hit rate, и данные целиком влезают в кеш.
Как происходит чтение:
- Просто читаем из кеша
Как происходит запись:
- Просто пишем в БД
👍35🔥7💅3 2
⚡Пара подходов к описанию деревьев в реляционной БД
Зачастую требуется хранить какие-то иерархичные данные, например, подобие файловой системы или какую-то организационную структуру.
1. id + parent_id
Самый простой подход, при котором храним идентификатор родительской сущности (либо null, если сущность и так корневая).
Плюсы:
- Простота модели
- Простая вставка
- Простой перенос поддерева
Минусы:
- “Дерево” может стать не деревом - МД не запрещает циклические ссылки
- Рекурсивные запросы, чтобы доставать поддерево по id корня
2. id, path + parent_id, parent_path
Помимо id добавляется path - путь по айдишникам до текущей сущности. Это нам позволяет сделать более интересные констрейнты и гарантировать, что мы имеем дело реально с деревом.
Здесь мы гарантируем, что
1) path корневой папки - это /<id>/
2) path некорневой папки - это <parent_path><id>/
Что позволяет обеспечить отсутствие циклов
Плюсы:
- Гарантия отсутствия циклов
- Простой запрос поддерева - where path like ‘/1/2/3/%’
Минусы:
- Сложность модели
- Более сложная вставка
- Сложный перенос поддерева
Зачастую требуется хранить какие-то иерархичные данные, например, подобие файловой системы или какую-то организационную структуру.
1. id + parent_id
create table folders
(
id bigserial not null,
parent_id bigint null references folders (id),
data jsonb not null
);
Самый простой подход, при котором храним идентификатор родительской сущности (либо null, если сущность и так корневая).
Плюсы:
- Простота модели
- Простая вставка
- Простой перенос поддерева
Минусы:
- “Дерево” может стать не деревом - МД не запрещает циклические ссылки
- Рекурсивные запросы, чтобы доставать поддерево по id корня
2. id, path + parent_id, parent_path
Помимо id добавляется path - путь по айдишникам до текущей сущности. Это нам позволяет сделать более интересные констрейнты и гарантировать, что мы имеем дело реально с деревом.
create table folders
(
id bigserial not null,
path varchar not null,
parent_id bigint null,
parent_path varchar null,
data jsonb not null
constraint ck__folders__path
check (path = coalesce(parent_path, '/') || id || '/'),
-- нужен для FK
constraint uc__folders__path__id
unique (path, id),
constraint fk__folders__parent_id__parent_path
foreign key (parent_path, parent_id) references folders (path, id)
match full
);
Здесь мы гарантируем, что
1) path корневой папки - это /<id>/
2) path некорневой папки - это <parent_path><id>/
Что позволяет обеспечить отсутствие циклов
Плюсы:
- Гарантия отсутствия циклов
- Простой запрос поддерева - where path like ‘/1/2/3/%’
Минусы:
- Сложность модели
- Более сложная вставка
- Сложный перенос поддерева
👍34🔥3 3🤔2 1
⚡Token bucket rate limiting
Один из алгоритмов для ограничения числа входящих запросов. На данный момент используется во многих системах, например, AWS.
Принцип работы:
Есть ограниченный набор токенов, наличие токена = право исполнить запрос.
Когда приходит входящий запрос, сначала проверяем, есть ли свободный токен, если есть - забираем его и исполняем запрос, если нет - отдаем 429.
И поскольку токены назад не возвращаются, их надо как-то восстанавливать. Этим занимается фоновый процесс, который по крону (обычно раз в секунду) восстанавливает число свободных токенов до максимума.
Один из алгоритмов для ограничения числа входящих запросов. На данный момент используется во многих системах, например, AWS.
Принцип работы:
Есть ограниченный набор токенов, наличие токена = право исполнить запрос.
Когда приходит входящий запрос, сначала проверяем, есть ли свободный токен, если есть - забираем его и исполняем запрос, если нет - отдаем 429.
И поскольку токены назад не возвращаются, их надо как-то восстанавливать. Этим занимается фоновый процесс, который по крону (обычно раз в секунду) восстанавливает число свободных токенов до максимума.
👍33🔥7 2💅1
🔥5 2 2✍1
⚡Continuous profiling
Continuous profiling - техника постоянного сбора данных о ресурсах, затрачиваемых приложением
Обычно профилирование применяется как ситуативная мера, чтобы здесь и сейчас подебагать перфоманс приложения. Но это не позволяет анализировать и разбирать инциденты, которые случились в прошлом
Постоянно профилирование позволяет строить графики и анализировать ретроспективные данные по затратам CPU, RAM и т.п. И одна из наиболее полезных вещей - построение flame graphs, которые позволяют посмотреть, на что именно приложение тратило память или процессор за определенный промежуток времени (например, во время какого-то инцидента)
Из популярных тулов для этих целей существует Grafana Pyroscope, который умеет запускаться как из приложения, так и отдельным агентом. Далее он собирает метрики и отливает их в графану, которая уже строит красивые графики
Continuous profiling - техника постоянного сбора данных о ресурсах, затрачиваемых приложением
Обычно профилирование применяется как ситуативная мера, чтобы здесь и сейчас подебагать перфоманс приложения. Но это не позволяет анализировать и разбирать инциденты, которые случились в прошлом
Постоянно профилирование позволяет строить графики и анализировать ретроспективные данные по затратам CPU, RAM и т.п. И одна из наиболее полезных вещей - построение flame graphs, которые позволяют посмотреть, на что именно приложение тратило память или процессор за определенный промежуток времени (например, во время какого-то инцидента)
Из популярных тулов для этих целей существует Grafana Pyroscope, который умеет запускаться как из приложения, так и отдельным агентом. Далее он собирает метрики и отливает их в графану, которая уже строит красивые графики
🔥17💅5👍4✍1 1 1