Microservices | Вопросы с Собеседований
Вопросы и авторские статьи по микросервисам, архитектуре, БД Контент по Kotlin/Java: t.me/KotlinQuestions
Show more3 406
Subscribers
-124 hours
-97 days
+530 days
- Subscribers
- Post coverage
- ER - engagement ratio
Data loading in progress...
Subscriber growth rate
Data loading in progress...
Матчится ли контент с названием канала?)Anonymous voting
- Да
- Нет
🔥 5 2 2✍ 1
⚡Придумать эффективный запрос
Есть таблица, описывающая некоторые задачи
create table tasks
(
id bigserial not null primary key,
parent_id bigint null,
status varchar(128) not null,
data jsonb not null
);
У задач иерархичная структура, которая задается с помощью id, parent_id. Задача должна начать выполняться только тогда, когда все дочерние задачи выполнены. Для простоты можно считать что status принимает значения PENDING и COMPLETED
Хочется придумать эффективный способ доставать задачи, которые нужно выполнить. То есть те, у которых все дочерние задачи выполнены
Ограничения:
- В конкретный момент PENDING задач около 1млн
- В конкретных момент PENDING задач, у которых все дочерние COMPLETED около 100
- У одной задачи не более 10 дочерних🤔 14💅 2🔥 1
Photo unavailableShow in Telegram
⚡Token bucket rate limiting
Один из алгоритмов для ограничения числа входящих запросов. На данный момент используется во многих системах, например, AWS.
Принцип работы:
Есть ограниченный набор токенов, наличие токена = право исполнить запрос.
Когда приходит входящий запрос, сначала проверяем, есть ли свободный токен, если есть - забираем его и исполняем запрос, если нет - отдаем 429.
И поскольку токены назад не возвращаются, их надо как-то восстанавливать. Этим занимается фоновый процесс, который по крону (обычно раз в секунду) восстанавливает число свободных токенов до максимума.
👍 28🔥 6 2💅 1
⚡Пара подходов к описанию деревьев в реляционной БД
Зачастую требуется хранить какие-то иерархичные данные, например, подобие файловой системы или какую-то организационную структуру.
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/%’
Минусы:
- Сложность модели
- Более сложная вставка
- Сложный перенос поддерева👍 31🔥 3 3🤔 1 1
⚡Caching patterns
При работе с кешом возникает вопрос “в какой момент нужно синхронизировать данные из бд и кеша?”. Рассмотрим три часто встречающихся паттерна:
1. Read-aside caching
Наиболее простой и часто используемый паттерн.
Как происходит чтение:
- Пробуем достать данные из кеша
- Если не получилось, идем в БД, складываем в кеш
Как происходит запись:
- Просто пишем в БД
2. Write-aside caching
Паттерн с чтением аналогичным предыдущему варианту, но при записи сразу обновляем и кеш, что в теории может позволить увеличить hit rate ценой того, что в кеш могут попадать данные, которые не нужны для чтения.
Как происходит чтение:
- Пробуем достать данные из кеша
- Если не получилось, идем в БД, складываем в кеш
Как происходит запись:
- Пишем в БД
- Пишем в кеш
3. Full caching
Кеш (обычно по крону) сам себя обновляет, подгружая все необходимые данные из БД. Подходит для случаев, когда хотим добиться ~100% hit rate, и данные целиком влезают в кеш.
Как происходит чтение:
- Просто читаем из кеша
Как происходит запись:
- Просто пишем в БД
👍 27🔥 6💅 3 2
⚡Школы unit-тестирования
Лондонская:
- Все зависимости (изменяемые) заменяются на моки
- Под юнитом подразумевается один класс
- Акцент на проверку взаимодействия компонентов
Пример:
@Test
fun `test user creation`() {
// given
val userSaver = mock<UserSaver>()
val userService = UserService(userSaver)
// when
userService.createUser("username")
// then
verify(userSaver).save(any<User>())
}
Классическая:
- За редким исключением только внепроцессные зависимости (БД, брокер) заменяются на моки
- Под юнитом может подразумеваться набор классов
- Акцент на проверку результатов вызова методов и состояния
@Test
fun `test user creation`() {
// given
val userService = UserService(UserSaver())
// when
userService.createUser("username")
// then
assertEquals(User(“username”), userService.getUser(“username”))
}
Лондонская школа позволяет разрабатывать в стиле TDD, начиная с высокоуровневых тестов, поскольку нам не требуются реальные зависимости. И проверяют какой-то аспект поведения класса (что был вызов UserSaver::save при вызове UserService::createUser). В классической школе зачастую проверяется корректная работа конкретного сценария (что UserService::createUser корректно сохранил пользователя).
А какую школу используете вы и почему?💅 8👍 6🔥 5
⚡Testcontainers
❓Проблема:
В рамках интеграционного тестирования хочется не мокать, а честно поднимать зависимости, такие как БД, брокер сообщений и т.д.
✅ Решение:
Testcontainers - фреймворк, позволяющий запускать контейнеры с различными зависимостями прямо из кода.
Пример на Java:
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
"postgres:15-alpine"
);
@BeforeAll
static void beforeAll() {
postgres.start();
}
@AfterAll
static void afterAll() {
postgres.stop();
}
На данный момент поддержано более 50 заранее сконфигурированных контейнеров, включая Postgres, Kafka, Elasticsearch, MySQL.
И сама реализация фреймворка существует для .NET, Go, Java, Node.js, также есть кастомные пользовательские реализации для многих других языков.👍 29🔥 13💅 4
Photo unavailableShow in Telegram
⚡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, Typescript, .NET. Посмотреть пример описания workflow можно тут.
👍 12🔥 7💅 5
Ставьте 💅 на этот пост, если нужен рассказ про возможности Temporal
Open Source Durable Execution
Build invincible apps with Temporal's open-source durable execution platform to guarantee successful execution, even in the presence of failures.
💅 141✍ 1🤔 1
⚡Durable executions
Представим, что есть некоторая задача
workflow { firstCall() sleep(1 hour) secondCall() sleep(1 hour) thirdCall() }Если мы ее запустим, то нужно, чтобы воркер, который ее будет исполнять, был жив минимум два часа подряд. Если по-середине ожидания что-то пойдет не так, то весь прогресс потеряется. Именно на этом примере можно описать концепцию Durable executions: После того, как мы сделали
firstCall()
, мы не будем ждать, а сделаем следующее:
- Сохраним в состояние задачи, что мы уже сделали firstCall()
- Зашедулим ее на +1 час
Спустя час какой-то другой воркер сможет взять эту задачу и продолжить с прыдыдущего “чекпоинта”. То есть мы сохраняем прогресс по определенным частям задачи, что позволяет переживать отказы воркеров и позволяет исполнять задачу по частям разными воркерами.
И сейчас существует довольно много Workflow engines, которые строятся на этой концепции.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
👍 24🔥 7💅 5
Choose a Different Plan
Your current plan allows analytics for only 5 channels. To get more, please choose a different plan.