Транзакции и изоляция
Глубина транзакций и конкурентности за пределами ACID — уровни изоляции и допускаемые каждым аномалии, оптимистичные и пессимистичные блокировки, взаимоблокировки, MVCC, 2PC против saga и идемпотентность на уровне данных.
8 вопросов
SeniorДизайнОчень частоДва микросервиса должны либо оба применить изменение, либо оба отменить: перевод денег списывает в сервисе счетов и зачисляет в сервисе реестра, и наполовину выполненный перевод недопустим. Сравните протокол распределённой фиксации 2PC (two-phase commit) с основанным на компенсациях паттерном Saga для этого случая — как каждый добивается «всё или ничего» и чем платит по доступности и связанности, — затем выберите один для перевода денег и обоснуйте выбор.
Два микросервиса должны либо оба применить изменение, либо оба отменить: перевод денег списывает в сервисе счетов и зачисляет в сервисе реестра, и наполовину выполненный перевод недопустим. Сравните протокол распределённой фиксации 2PC (two-phase commit) с основанным на компенсациях паттерном Saga для этого случая — как каждый добивается «всё или ничего» и чем платит по доступности и связанности, — затем выберите один для перевода денег и обоснуйте выбор.
2PC даёт сильную атомарность: координатор фиксирует, только если оба сервиса согласны, — но держит блокировки по сети и зависает при падении координатора. Saga использует локальные транзакции с компенсациями; остаётся доступной, но согласована лишь со временем. Лучше Saga с идемпотентными шагами и сверкой.
Типичные ошибки
- ✗Называть
2PCнеблокирующим или высокодоступным - ✗Думать, что
Sagaдаёт немедленную сильную согласованность без компенсаций - ✗Игнорировать идемпотентность и сверку при выборе
Saga
Уточняющие вопросы
- →Почему протокол
2PC(two-phase commit) зависает при падении координатора? - →Чем компенсирующая транзакция отличается от обычного rollback?
SeniorДизайнОчень частоПлатёжный поток списывает у отправителя в сервисе A, затем зовёт сервис B зачислить получателю. Списание в A прошло, а зачисление в B упало или отвалилось по таймауту, поэтому деньги ушли из A, но так и не пришли в B. Общей базы нет, распределённой транзакции на два сервиса тоже нет. Какие механизмы гарантируют, что деньги не потеряются и стороны в итоге сойдутся, — без задвоения зачисления, если упавший вызов позже повторят? Опишите подход, который заложите.
Платёжный поток списывает у отправителя в сервисе A, затем зовёт сервис B зачислить получателю. Списание в A прошло, а зачисление в B упало или отвалилось по таймауту, поэтому деньги ушли из A, но так и не пришли в B. Общей базы нет, распределённой транзакции на два сервиса тоже нет. Какие механизмы гарантируют, что деньги не потеряются и стороны в итоге сойдутся, — без задвоения зачисления, если упавший вызов позже повторят? Опишите подход, который заложите.
Не полагаться на единый кросс-сервисный commit. Атомарно сохранить в A списание и намерение зачислить (transactional outbox), доставить в B с повторами at-least-once. Сделать зачисление в B идемпотентным по уникальному id, чтобы повторы не задваивали. При сбое — компенсирующий возврат в A; сверка ловит отставших.
Типичные ошибки
- ✗Полагать, что распределённая транзакция между сервисами доступна или надёжна
- ✗Повторять неидемпотентное зачисление и задваивать получателю
- ✗Пропускать компенсацию, оставив деньги списанными, но не зачисленными
Уточняющие вопросы
- →Как transactional outbox устраняет проблему двойной записи?
- →Когда выбрать сверку вместо синхронной компенсации?
JuniorТеорияЧастоКакие уровни изоляции определяет стандарт SQL и какой из них — по умолчанию в PostgreSQL?
Какие уровни изоляции определяет стандарт SQL и какой из них — по умолчанию в PostgreSQL?
Стандарт SQL определяет четыре уровня изоляции, от слабого к сильному: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ и SERIALIZABLE. Каждый следующий уровень запрещает больше аномалий, но снижает пропускную способность. По умолчанию в PostgreSQL — READ COMMITTED, и он не допускает грязного чтения даже на низшем уровне.
Типичные ошибки
- ✗Называть лишь два уровня или выдумывать режимы READ/WRITE
- ✗Считать, что высокая изоляция повышает пропускную способность, а не снижает
- ✗Полагать, что по умолчанию в PostgreSQL — SERIALIZABLE
Уточняющие вопросы
- →Какую аномалию всё ещё допускает READ COMMITTED?
- →Почему PostgreSQL никогда не допускает грязного чтения?
JuniorТеорияЧастоЧто такое блокировка, чем отличаются shared- и exclusive-блокировки и что такое deadlock?
Что такое блокировка, чем отличаются shared- и exclusive-блокировки и что такое deadlock?
Блокировка — это метка, которую транзакция ставит на строку или таблицу, чтобы управлять параллельным доступом. Shared-блокировка (на чтение) даёт другим читать, но не писать; exclusive (на запись) не даёт ни читать, ни писать. Deadlock — когда две транзакции держат нужные друг другу блокировки и ждут вечно; СУБД находит цикл и откатывает одну.
Типичные ошибки
- ✗Путать смысл shared (чтение) и exclusive (запись)
- ✗Думать, что deadlock разрешает клиент, а не СУБД
- ✗Считать, что deadlock — это просто медленный запрос
Уточняющие вопросы
- →Как СУБД выбирает, какую транзакцию откатить при deadlock?
- →Как единый порядок захвата блокировок предотвращает deadlock?
MiddleТеорияЧастоКакую аномалию всё ещё допускает уровень изоляции READ COMMITTED, а какую — REPEATABLE READ?
Какую аномалию всё ещё допускает уровень изоляции READ COMMITTED, а какую — REPEATABLE READ?
READ COMMITTED предотвращает грязное чтение, но допускает неповторяющееся чтение и фантомы — повторный read вернёт новые данные или строки. REPEATABLE READ устраняет и неповторяющееся чтение; по стандарту SQL он всё ещё допускает фантомы, хотя PostgreSQL блокирует и их. Лишь SERIALIZABLE запрещает все аномалии.
Типичные ошибки
- ✗Думать, что
READ COMMITTEDуже блокирует неповторяющееся чтение - ✗Считать, что высокая изоляция допускает больше аномалий, а не меньше
- ✗Забывать, что стандарт всё ещё допускает фантомы на
REPEATABLE READ
Уточняющие вопросы
- →Чем
REPEATABLE READв PostgreSQL отличается от стандарта SQL? - →Какой лишней ценой
SERIALIZABLEустраняет фантомы?
MiddleТеорияЧастоКак сделать запись на уровне данных идемпотентной и что даёт уникальный бизнес-ключ?
Как сделать запись на уровне данных идемпотентной и что даёт уникальный бизнес-ключ?
Идемпотентная запись оставляет то же состояние, сколько бы раз ни повторили, — без дубля. Определяешь уникальный бизнес-ключ (номер заказа или request id клиента) и подкрепляешь unique-ограничением. Тогда INSERT ... ON CONFLICT DO NOTHING или upsert делает повтор no-op, и база отклоняет дубли при повторах.
Типичные ошибки
- ✗Считать повторы в приложении вместо ограничения в БД
- ✗Брать метку времени или случайный id, делая каждую попытку уникальной
- ✗Думать, что уникальный ключ не остановит параллельные дубли
Уточняющие вопросы
- →Как с этим связан idempotency key на уровне API?
- →Какой HTTP-глагол по той же причине естественно идемпотентен?
MiddleТеорияЧастоЧто такое MVCC и как он позволяет читателям не блокировать писателей?
Что такое MVCC и как он позволяет читателям не блокировать писателей?
MVCC (multiversion concurrency control) хранит несколько версий каждой строки вместо перезаписи на месте. Читатель видит снимок на момент начала своей транзакции, читая старую версию, пока писатель создаёт новую. Поэтому читатели не блокируют писателей и наоборот; конкурируют лишь два писателя на одной строке.
Типичные ошибки
- ✗Думать, что MVCC хранит одну версию и перезаписывает на месте
- ✗Считать, что при MVCC читатели всё ещё блокируют писателей
- ✗Путать MVCC с форматом резервной копии или журнала
Уточняющие вопросы
- →Какую аномалию снимок всё ещё допускает на
READ COMMITTED? - →Как потом убираются устаревшие версии строк (vacuum)?
MiddleТеорияЧастоЧем отличаются оптимистичная и пессимистичная блокировки и как колонка-версия реализует оптимистичную?
Чем отличаются оптимистичная и пессимистичная блокировки и как колонка-версия реализует оптимистичную?
Пессимистичная блокировка берёт lock заранее, конфликты блокируются; для высокой конкуренции. Оптимистичная lock не берёт: читаешь колонку version, обновляешь через WHERE id = ? AND version = ? и увеличиваешь её. Ноль совпавших строк — её изменили раньше, нужен повтор. Годится при низкой конкуренции.
Типичные ошибки
- ✗Путать, какая стратегия берёт блокировку заранее
- ✗Думать, что колонка
versionсортирует строки, а не ловит конфликты - ✗Применять оптимистичную блокировку при самой высокой конкуренции
Уточняющие вопросы
- →Почему оптимистичная блокировка лучше масштабируется при низкой конкуренции?
- →Как возникает потерянная запись без проверки версии?