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

Как реализуется Saga в SQL?

3.0 Senior🔥 61 комментариев
#Архитектура и микросервисы#Базы данных и SQL#Брокеры сообщений и интеграция

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Реализация Saga в SQL

Saga — это паттерн для управления долгими бизнес-транзакциями, состоящими из нескольких независимых шагов, где каждый шаг имеет свою локальную транзакцию. В контексте SQL реализация Saga предполагает хранение состояния процесса, координацию шагов и обеспечение компенсационных действий для восстановления консистентности при ошибках.

Основные подходы к реализации Saga

Существует два основных стиля координации Saga:

  1. Choreography (Хореография) — где каждый шаг самостоятельно публикует события и реагирует на события других шагов.
  2. Orchestration (Оркестрация) — где центральный координатор (оркестратор) управляет выполнением шагов и компенсациями.

В SQL-ориентированных системах чаще используется Orchestration, так как он позволяет централизованно хранить состояние и логику процесса в базе данных.

Ключевые компоненты SQL Saga

Для реализации Saga в SQL обычно создаются следующие таблицы:

CREATE TABLE sagas (
    saga_id UUID PRIMARY KEY,
    saga_type VARCHAR(50) NOT NULL,
    current_state VARCHAR(50) NOT NULL,
    last_step_completed INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    data JSONB -- для хранения контекста saga
);

CREATE TABLE saga_steps (
    step_id INT PRIMARY KEY,
    saga_id UUID REFERENCES sagas(saga_id),
    step_type VARCHAR(50) NOT NULL, -- 'action' или 'compensation'
    step_name VARCHAR(100) NOT NULL,
    status VARCHAR(20) NOT NULL, -- 'pending', 'completed', 'failed'
    executed_at TIMESTAMP,
    parameters JSONB,
    error_message TEXT
);

Процесс выполнения Saga

Пример бизнес-процесса "Оформление заказа":

  1. Создание заказа в orders таблице
  2. Проверка доступности товара в inventory
  3. Списание средств с accounts
  4. Запись в shipments для доставки

Реализация в виде Saga:

-- Таблица для отслеживания шагов saga
CREATE TABLE order_saga_steps (
    saga_id UUID,
    step_number INT,
    step_name VARCHAR(50),
    status VARCHAR(20),
    FOREIGN KEY (saga_id) REFERENCES sagas(saga_id)
);

Оркестрация Saga через хранимые процедуры

Оркестратор может быть реализован как набор хранимых процедур, которые:

  • Создают записи Saga
  • Выполняют шаги последовательно
  • Обрабатывают ошибки и запускают компенсации
CREATE OR REPLACE PROCEDURE start_order_saga(
    order_id UUID,
    customer_id UUID,
    amount DECIMAL
)
LANGUAGE plpgsql
AS $$
DECLARE
    saga_uuid UUID;
BEGIN
    -- 1. Создание saga
    INSERT INTO sagas(saga_id, saga_type, current_state) 
    VALUES (gen_random_uuid(), 'order_processing', 'started')
    RETURNING saga_id INTO saga_uuid;
    
    -- 2. Выполнение шага 1: создание заказа
    BEGIN
        INSERT INTO orders(id, customer_id, amount, status) 
        VALUES (order_id, customer_id, amount, 'pending');
        
        UPDATE saga_steps 
        SET status = 'completed', executed_at = NOW()
        WHERE saga_id = saga_uuid AND step_name = 'create_order';
        
    EXCEPTION WHEN others THEN
        -- Если ошибка, saga сразу завершается
        UPDATE sagas 
        SET current_state = 'failed' 
        WHERE saga_id = saga_uuid;
        RETURN;
    END;
    
    -- 3. Шаг 2: проверка inventory (аналогично)
    -- ...
END;
$$;

Компенсационные транзакции

При ошибке на любом шаге Saga должна выполнить компенсационные действия для отката уже выполненных шагов. Например:

CREATE OR REPLACE PROCEDURE compensate_order_saga(saga_id UUID)
LANGUAGE plpgsql
AS $$
DECLARE
    order_record RECORD;
BEGIN
    -- Получаем данные заказа для компенсации
    SELECT * FROM orders WHERE saga_id = saga_id INTO order_record;
    
    -- Компенсация шага списания средств
    IF order_record.payment_deducted THEN
        UPDATE accounts 
        SET balance = balance + order_record.amount 
        WHERE customer_id = order_record.customer_id;
    END IF;
    
    -- Компенсация шага резервирования товара
    IF order_record.inventory_reserved THEN
        UPDATE inventory 
        SET reserved_count = reserved_count - order_record.item_count 
        WHERE product_id = order_record.product_id;
    END IF;
    
    -- Обновление статуса saga
    UPDATE sagas SET current_state = 'compensated' WHERE saga_id = saga_id;
END;
$$;

Управление состоянием и восстановление

Таблица состояний Saga должна поддерживать:

  • Текущий статус (started, in_progress, completed, failed, compensated)
  • Индикатор последнего выполненного шага
  • Данные контекста (например, в формате JSONB)

Для обработки частичных отказов и повторного выполнения можно добавить механизм retry:

CREATE TABLE saga_retries (
    saga_id UUID REFERENCES sagas(saga_id),
    step_name VARCHAR(100),
    retry_count INT DEFAULT 0,
    last_retry_at TIMESTAMP,
    max_retries INT DEFAULT 3
);

Проблемы и решения при SQL реализации

  1. Консистентность данных: Saga не гарантирует ACID на уровне всей бизнес-транзакции, поэтому важно:

    • Использовать версионирование данных для предотвращения конфликтов
    • Реализовать механизмы обнаружения расхождений и восстановления
  2. Долгие транзакции: Saga может выполняться часами или днями, поэтому:

    • Необходимо периодическое сохранение состояния
    • Реализация механизма wake-up для продолжения Saga после пауз
  3. Мониторинг и диагностика:

    • Добавление аудита всех шагов и компенсаций
    • Логирование в отдельные таблицы для анализа проблем

Пример полной Saga в одном транзакционном скрипте

-- Пример выполнения Saga с компенсацией
BEGIN;
    -- Шаг 1
    INSERT INTO orders (...) VALUES (...);
    UPDATE sagas SET current_state = 'step1_completed';
    
    -- Шаг 2
    UPDATE inventory SET reserved = reserved + 1 WHERE product_id = ...;
    UPDATE sagas SET current_state = 'step2_completed';
    
    -- Шаг 3 (если ошибка, компенсация)
    BEGIN
        UPDATE accounts SET balance = balance - 100 WHERE user_id = ...;
        UPDATE sagas SET current_state = 'step3_completed';
    EXCEPTION WHEN others THEN
        -- Компенсация шагов 2 и 1
        UPDATE inventory SET reserved = reserved - 1 WHERE product_id = ...;
        DELETE FROM orders WHERE id = ...;
        UPDATE sagas SET current_state = 'failed';
    END;
COMMIT;

Инструменты и фреймворки

Для сложных реализаций Saga в SQL-мире используются:

  • Бизнес-процессовые движки (Camunda, jBPM) с персистентностью в SQL
  • Специализированные фреймворки (например, Saga из MassTransit для .NET)
  • Собственные реализации на основе триггеров и хранимых процедур

Заключение

Реализация Saga в SQL требует тщательного проектирования таблиц состояния, механизмов оркестрации и компенсационных процедур. Этот подход обеспечивает надежное выполнение долгих бизнес-процессов в распределенных системах, где традиционные ACID транзакции неприменимы. Ключевые преимущества — централизованный контроль, возможность аудита и относительная простота реализации в рамках знакомой SQL-парадигмы.