← Назад к вопросам

Как обеспечивал транзакции между двумя базами данных?

2.4 Senior🔥 91 комментариев
#Архитектура и паттерны#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как обеспечивал транзакции между двумя базами данных?

Это классический distributed transaction problem. ACID гарантии работают в одной БД, но между двумя - сложнее.

Проблема:

// Transfer money: remove from user1, add to user2
await db1.users.update({id: user1, balance: balance - 100});
await db2.users.update({id: user2, balance: balance + 100});
// Если первый сработал, второй нет - потеря денег!

Решение 1: Two-Phase Commit (2PC)

1. Prepare phase: обе БД проверяют может ли transaction успешно выполниться
2. Commit phase: обе БД фиксируют изменения или откатывают

Проблемы: медленно, требует поддержки БД, не всегда reliable.

Решение 2: Event Sourcing + Saga Pattern (лучше)

// 1. Запишем event
await eventLog.create({id: uuid(), type: 'transfer_initiated', from: user1, to: user2, amount: 100});

// 2. Обновим БД 1
await db1.users.update({id: user1, balance: balance - 100});
if (error) {
  // Откатим event
  await eventLog.update({id, status: 'failed'});
  throw error;
}

// 3. Обновим БД 2
await db2.users.update({id: user2, balance: balance + 100});
if (error) {
  // ОТКАТА: вернём деньги в БД 1
  await db1.users.update({id: user1, balance: balance + 100});
  await eventLog.update({id, status: 'failed'});
  throw error;
}

// 4. Завершим
await eventLog.update({id, status: 'completed'});

Решение 3: Outbox Pattern (production-ready)

Проблема: что если процесс крашится между db1 и db2 update?

// Всё в одной БД транзакции:
await db1.transaction(async (trx) => {
  // 1. Обновляем пользователя
  await trx('users').where('id', user1).decrement('balance', 100);
  
  // 2. Пишем event в outbox (same transaction!)
  await trx('outbox').insert({
    id: uuid(),
    event_type: 'transfer_completed',
    payload: {to: user2, amount: 100},
    created_at: now()
  });
});

// Отдельный процесс читает outbox и отправляет в db2:
setInterval(async () => {
  const events = await db1('outbox').where('processed', false);
  
  for (const event of events) {
    try {
      // Отправляем в db2
      await db2.users.update({id: event.payload.to, balance: ...});
      // Помечаем как processed
      await db1('outbox').where('id', event.id).update({processed: true});
    } catch (e) {
      // Retry логика
    }
  }
}, 5000);

Решение 4: Message Queue (самое надёжное)

// В db1:
await db1.transaction(async (trx) => {
  await trx('users').decrement('balance', 100);
  // Пусти message в очередь (same transaction или after)
  await queue.publish('transfer.completed', {user_id: user2, amount: 100});
});

// Consumer слушает очередь и обновляет db2:
queue.subscribe('transfer.completed', async (message) => {
  try {
    await db2.users.update({id: message.user_id, balance: ...});
    // Acknowledge message
    message.ack();
  } catch (e) {
    message.nack(); // Retry
  }
});

Какой выбрать?

  • 2PC: Старые legacy системы
  • Event Sourcing: Когда нужна история всех операций
  • Outbox: Хороший баланс
  • Message Queue: Для микросервисов, максимальная надёжность

Практический совет: Часто лучше избежать distributed transactions перепроектировав архитектуру. Например, вместо двух БД - одна БД с хорошей архитектурой.