Вопрос 6 · Раздел 11

Что такое Serializable?

Все аномалии полностью исключены.

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Serializable — это самый строгий уровень изоляции транзакций. Он гарантирует, что результат параллельных транзакций будет эквивалентен какому-то одному последовательному выполнению (не обязательно фактическому хронологическому порядку).

Основные свойства

  • Грязное чтение: Запрещено
  • Неповторяющееся чтение: Запрещено
  • Фантомное чтение: Запрещено

Все аномалии полностью исключены.

Простой пример

Если две транзакции работают с одними данными, на уровне Serializable они выполнятся так, будто работают строго по очереди — одна за другой.

Когда использовать

  • Финансовые транзакции
  • Системы бронирования
  • Когда цена ошибки в данных очень высока

Главный недостаток

Самый медленный уровень. На write-heavy нагрузке throughput может быть в 2-10x ниже, чем на Read Committed. На read-only — разница минимальна. Транзакции могут конфликтовать и отменяться. Приложение должно уметь повторять такие транзакции.

Когда НЕ использовать Serializable

  1. Read-heavy reporting — избыточные overhead
  2. >50 concurrent writers — постоянные serialization failures
  3. Транзакции >1 сек — чем дольше транзакция, тем выше шанс конфликта
  4. Hot-spot updates (счётчики, очереди) — все пишут в одну строку

🟡 Middle Level

Механизмы реализации

Исторически существует два подхода:

Пессимистичный: Two-Phase Locking (2PL)

  • Блокируются все данные, которые транзакция читает или записывает
  • Чтение блокирует запись, запись блокирует чтение и запись
  • Минус: Низкая производительность и частые Deadlocks
  • Используется в старых СУБД (SQL Server по умолчанию для этого уровня)

Оптимистичный: Serializable Snapshot Isolation (SSI)

  • Современный метод (PostgreSQL с 9.1)
  • Не блокирует чтение/запись, позволяет параллельное выполнение через MVCC
  • Отслеживает зависимости между транзакциями
  • При обнаружении конфликта одна транзакция прерывается с ошибкой

Главный вызов: Retries

На уровне Serializable СУБД не гарантирует успешное завершение транзакции. База может выдать ошибку:

ERROR: could not serialize access due to read/write dependencies among transactions

Приложение обязано реализовывать механизм повторных попыток (Retry Logic).

Когда использовать Serializable

  1. Финансовые транзакции: Переводы между множеством счетов с инвариантами
  2. Системы бронирования: Нельзя допустить двойную продажу
  3. Сложные бизнес-правила: Логика зависит от набора данных, которые могут измениться

Как минимизировать конфликты

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

🔴 Senior Level

Serializable Snapshot Isolation (SSI) — Internal Implementation

Dependency Graph Construction

PostgreSQL SSI builds a Serializable Graph tracking:

  • RW-conflicts: When transaction T1 reads a row that T2 later writes
  • Dangerous structures: Cycles in the dependency graph that could cause anomalies
T1 reads row X (T2 hasn't written yet)
T2 writes row X
T2 reads row Y (T3 hasn't written yet)
T3 writes row Y
T3 reads row X (that T2 wrote)

This forms a cycle → potential anomaly → one transaction must abort.

Цикл опасен, потому что означает: результат зависит от порядка выполнения, и нет serial order, который дал бы тот же результат. Поэтому СУБД прерывает одну транзакцию с ошибкой serialization failure.

Predicate Locks

Unlike traditional row/table locks, SSI uses predicate locks:

  • Locks are on index ranges or full table scans
  • If predicate overlaps → potential conflict detected
  • Locks are tracked in memory, not on disk
  • Coarsening: individual page locks may be promoted to relation-level locks to save memory

Conflict Detection Algorithm

On each read:
  - Register predicate lock on accessed data
  
On each write:
  - Check for RW-dependencies with other transactions
  - If dangerous structure detected:
    - Mark one transaction for abortion
    - Error code: SQLSTATE 40001 (serialization_failure)

Performance Characteristics

Overhead Analysis

  • Memory: Predicate locks stored in shared memory (~few MBs typically)
  • CPU: Dependency graph traversal on each read/write
  • Abort rate: 1-10% under moderate contention, can spike to 30%+ under heavy writes
  • Throughput: 2-10x lower than Read Committed depending on workload

When SSI Performs Well

  • Read-heavy workloads (predicate locks don’t conflict)
  • Transactions accessing disjoint data sets
  • Short transactions with minimal overlap

When SSI Struggles

  • High write contention on same rows
  • Long-running transactions (more conflict window)
  • Hot-spot updates (counters, queues)

Production Retry Implementation

@Configuration
@EnableRetry
public class RetryConfig {
}

@Service
public class TransactionService {
    
    @Retryable(
        value = {CannotSerializeTransactionException.class},
        backoff = @Backoff(
            delay = 50,
            maxDelay = 2000,
            multiplier = 3,
            random = true  // Jitter to prevent thundering herd
        ),
        maxAttempts = 5
    )
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public Result performCriticalOperation(Input input) {
        // Business logic here
    }
    
    @Recover
    public Result recover(CannotSerializeTransactionException e, Input input) {
        // Fallback or throw business exception
        throw new BusinessException("Could not complete after retries", e);
    }
}

Serialization Failure Metrics to Monitor

-- PostgreSQL: Track serialization failures
SELECT 
    datname,
    xact_commit,
    xact_rollback,
    conflicts
FROM pg_stat_database
WHERE datname = 'your_db';

-- Application-level: Track retry rate
// Use Micrometer/Prometheus
Counter.builder("transaction.serialization.retries")
    .tag("operation", "transfer")
    .register(meterRegistry);

Serializable vs Alternative Approaches

Approach Guarantees Performance Complexity
Serializable (SSI) Full ACID Lowest Medium (retries)
Repeatable Read + FOR UPDATE Row-level Medium Low
Optimistic Locking (@Version) Application-level High Medium
Application-level locking Varies High High

Edge Cases and Gotchas

  1. Read-only transactions: Can still cause serialization failures if they read data being written
  2. Deferred constraints: Checked at COMMIT time, can trigger serialization failures
  3. Temporary tables: Not tracked by SSI, not protected
  4. Advisory locks: Not tracked by SSI, not protected
  5. Sequence values: nextval() is NOT rolled back on abort

Migration Strategy from RC to Serializable

  1. Start with monitoring: track potential conflicts under RC
  2. Implement retry logic first
  3. Enable Serializable for new endpoints only
  4. Gradually migrate critical paths
  5. Measure abort rates and adjust

🎯 Шпаргалка для интервью

Обязательно знать:

  • Serializable — самый строгий уровень, исключает все аномалии (dirty, non-repeatable, phantom)
  • PostgreSQL использует SSI (Serializable Snapshot Isolation) с 9.1 версии
  • SSI отслеживает RW-dependencies и при обнаружении цикла прерывает транзакцию с ошибкой 40001
  • Приложение ОБЯЗАНО реализовывать retry logic при использовании Serializable
  • Throughput на write-heavy нагрузке может быть в 2-10x ниже, чем на Read Committed
  • Predicate locks используются вместо традиционных row/table locks

Частые уточняющие вопросы:

  • Что такое SSI? — Serializable Snapshot Isolation: граф зависимостей + selective abortion
  • Когда использовать Serializable? — Финансы, бронирование, где цена ошибки высока
  • Когда НЕ использовать? — Read-heavy reporting, >50 concurrent writers, транзакции >1 сек
  • Что произойдёт при конфликте? — Ошибка serialization_failure (40001), транзакция abort-ится

Красные флаги (НЕ говорить):

  • “Serializable = просто ставлю аннотацию и всё работает” — нужен retry logic
  • “Serializable подходит для любых нагрузок” — на write-heavy throughput падает в разы
  • “PostgreSQL использует 2PL для Serializable” — используется SSI, не two-phase locking

Связанные темы:

  • [[2. Какие уровни изоляции транзакций существуют]]
  • [[5. Что такое Repeatable Read]]
  • [[11. Какой уровень изоляции по умолчанию в PostgreSQL]]
  • [[16. Что такое аннотация @Transactional]]