Питання 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

Підхід Гарантії Продуктивність Складність
Serializable (SSI) Full ACID Найнижча Medium (retries)
Repeatable Read + FOR UPDATE Row-level Середня Low
Optimistic Locking (@Version) Application-level Висока Medium
Application-level locking Varies Висока 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]]