← Назад к вопросам
Выполняется ли ACID в микросервисной архитектуре
2.0 Middle🔥 121 комментариев
#Архитектура и паттерны#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Выполняется ли ACID в микросервисной архитектуре
Коротко: НЕТ, не полностью. Это один из главных компромиссов микросервисной архитектуры.
ACID - это что?
ACID - это четыре гарантии для транзакций:
- Atomicity (Атомарность): либо всё либо ничего
- Consistency (Согласованность): данные всегда в валидном состоянии
- Isolation (Изоляция): транзакции не мешают друг другу
- Durability (Стойкость): если завершилась, она не потеряется
-- ACID гарантирует в одной БД:
BEGIN TRANSACTION
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- Или выполнится всё, или ничего
-- Невозможен случай где деньги потеряются
Проблема в микросервисах
В микросервисной архитектуре данные распределены по разным БД:
User Service Order Service Payment Service
┌──────────┐ ┌──────────┐ ┌──────────┐
│PostgreSQL│ │PostgreSQL│ │PostgreSQL│
└──────────┘ └──────────┘ └──────────┘
| | |
+───────────────+──────────────────+
Проблема: как сделать ACID операцию между несколькими БД?
1. Пользователь нажимает "Купить"
2. Order Service создаёт заказ
3. Payment Service снимает деньги
4. Inventory Service уменьшает остаток
Что если на шаге 3 ошибка?
Заказ создан, но деньги не снялись!
Решение 1: Distributed Transactions (2PC)
Two-Phase Commit - попытка сохранить ACID:
Coordinator
|
+---- "Prepare" Request to all services
| |
| Order Service: "Готов?" ✓
| Payment Service: "Готов?" ✓
| Inventory Service: "Готов?" ✓
|
+---- "Commit" Request if all ready
|
Order Service: COMMIT ✓
Payment Service: COMMIT ✓
Inventory Service: COMMIT ✓
# Псевдокод 2PC
class DistributedTransaction:
def execute(self):
try:
# Phase 1: Prepare
order_service.prepare(order_data)
payment_service.prepare(payment_data)
inventory_service.prepare(inventory_data)
# Phase 2: Commit
order_service.commit()
payment_service.commit()
inventory_service.commit()
except:
# Rollback if any fails
order_service.rollback()
payment_service.rollback()
inventory_service.rollback()
Проблемы 2PC:
- Очень медленно (много сетевых операций)
- Хрупко (если сервис упадёт в phase 1?)
- Deadlocks
- Не работает в облаке (высокая latency)
- Это антипаттерн в микросервисах
Решение 2: Saga Pattern
Saga - это последовательность локальных транзакций:
Customer Service Order Service Payment Service
| | |
+--create order---------->| |
| +--charge payment----->|
| |<--payment OK---------|
|<--order confirmed-------| |
Если Payment падает:
+--cancel order---------->|
| |
Есть два варианта:
Choreography (события)
# Order Service
async def create_order(order_data):
order = Order.create(order_data)
await publish_event('order.created', order_data)
# Event Bus распространит это дальше
# Payment Service подслушивает
@event_listener('order.created')
async def charge_payment(order_data):
try:
payment = await charge_card(order_data.payment_info)
await publish_event('payment.completed', payment)
except:
await publish_event('payment.failed', order_data)
# Inventory Service подслушивает
@event_listener('payment.completed')
async def reserve_inventory(payment_data):
try:
await reserve(payment_data.items)
await publish_event('inventory.reserved', data)
except:
await publish_event('inventory.reservation.failed', data)
Orchestration (оркестр)
# Order Orchestrator управляет всеми
class OrderOrchestrator:
async def process_order(self, order_data):
try:
# 1. Create order
order = await order_service.create(order_data)
# 2. Charge payment
try:
payment = await payment_service.charge(order)
except:
await order_service.cancel(order)
raise
# 3. Reserve inventory
try:
inventory = await inventory_service.reserve(order)
except:
await payment_service.refund(payment)
await order_service.cancel(order)
raise
return order
except Exception as e:
# Compensating transaction
await self._compensate(order)
raise
async def _compensate(self, order):
# Откатываем всё что сделали
pass
ACID в микросервисах - что сохранилось?
ACID в классике ACID в микросервисах
┌──────────────────────┬──────────────────────┐
│ Atomicity | 100% │ 0% (Saga компенсирует)|
│ Consistency | 100% │ ~70% (eventual) │
│ Isolation | 100% │ 0% (no locking) │
│ Durability | 100% │ 100% │
└──────────────────────┴──────────────────────┘
Eventual Consistency вместо ACID
В микросервисах используют Eventual Consistency:
Время:
T0: Деньги сняты (Payment Service)
T1: Заказ создан (Order Service)
T2: Инвентарь обновлен (Inventory Service)
T3: Email отправлен (Notification Service)
Между T0 и T3 система в inconsistent состоянии!
Но в конце концов (~100ms) всё согласовано.
Плюсы:
- Быстро
- Масштабируемо
- Resilient (если сервис упадёт, другие работают)
Минусы:
- Может быть lag между сервисами
- Нужно handle race conditions
- Сложнее написать код
Пример: очистка от неудачи
# Order Service создал заказ
order = Order.create(customer_id=123)
# Payment Service не ответил (timeout)
try:
payment = await payment_service.charge(order_id=order.id)
except TimeoutError:
# Что делать?
# Опция 1: Отменить заказ (но может быть платёж прошёл!)
await order.cancel()
# Опция 2: Оставить в pending и retry
await order.mark_pending()
# Опция 3: Компенсирующая транзакция
await compensate_order(order)
Когда НЕ использовать микросервисы
Если тебе нужен полный ACID:
# Финансовая операция - перевод денег
# Требует гарантии что одно из двух:
# 1. Деньги переведены
# 2. Ничего не произошло
# Вариант: остаться в monolith с одной БД
# Или используй:
# - Distributed transaction coordinator (dTCC)
# - Blockchain (если trust нужна)
# - Синхронные API вместо асинхронных (но медленнее)
Best Practices для микросервисов
# 1. Используй Saga для компенсации
# 2. Делай локальные транзакции в каждом сервисе
# 3. Используй идемпотентные операции (safe to retry)
# 4. Дай timeout и retry логику
# 5. Логируй все события для audit trail
# Идемпотентная операция:
def charge_payment(order_id, payment_id, amount):
# Даже если вызовешь дважды - результат одинаков
payment = Payment.find_or_create(
payment_id=payment_id,
order_id=order_id,
amount=amount
)
return payment
Сравнение стратегий
Monolith | Microservices(2PC) | Microservices(Saga)
────────────────────────────────────────────────────────────────
ACID | ✓✓✓ | ✓✓ | ✓
Скорость | ✓✓ | ✓ (медленно) | ✓✓✓
Масштаб | ✓ | ✓✓✓ | ✓✓✓
Resilience | ✓ | ✗ | ✓✓✓
Complexity | ✓ | ✗✗ | ✗✗
Реальный пример: Amazon
Amazon использует микросервисы без ACID:
1. Order создан (confirmed)
2. Payment submitted (async)
3. Inventory reserved (async)
4. Fulfillment scheduled (async)
5. Email sent (async)
Если payment failed:
- Order переходит в "payment failed"
- Inventory зарезервирован отменяется
- Customer получает email "Платёж не прошёл"
- Eventual consistency за ~5 секунд
Инструменты для Saga
- Apache Kafka - event streaming
- RabbitMQ - message broker
- Temporal - orchestration
- Axon - event sourcing
- Camunda - BPMN workflow
Заключение
- ACID не выполняется полностью в микросервисах
- Используй Saga pattern для компенсирующих транзакций
- Eventual Consistency - это обычно OK на практике
- Не используй микросервисы если нужен полный ACID
- Idempotency - важнее чем ACID в микросервисах