Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Как и зачем MVCC мешает нам пользоваться базой данных и почему это не плохо

Как и зачем MVCC мешает нам пользоваться базой данных и почему это не плохо

Несмотря на распространение различных NoSQL решений для хранения данных в последние годы, реляционные базы данных остаются популярным выбором среди разработчиков из–за своей универсальности и предсказуемости. Большинство из них следуют принципам ACID, который дает нам определенные гарантии. Однако, сделать так, чтобы БД им соответствовала не так то просто, и мы рассмотрим один из подходов к этому — MVCC. Иногда его работа приводит к неожиданным сайд–эффектам, и в докладе мы рассмотрим несколько примеров.

В качестве примеров я буду использовать PostgreSQL, но MVCC работает одинаково практически везде, так что адаптировать новые знания к своей любимой БД будет довольно просто. Доклад будет полезен как начинающим, так и продвинутым бэкэндерам.

Dmitry Tsepelev

September 25, 2021
Tweet

More Decks by Dmitry Tsepelev

Other Decks in Programming

Transcript

  1. DmitryTsepelev
    404fest'21
    Задача: нужно сбросить всем
    пользователям флаг подтверждения
    почты
    2

    View full-size slide

  2. DmitryTsepelev
    404fest'21 3
    UPDATE users SET email_conf
    i
    rmed = FALSE;

    View full-size slide

  3. DmitryTsepelev
    404fest'21 4
    А что если данных много? 🤔
    SELECT pg_size_pretty(pg_total_relation_size('users'));


    - -
    200 Gb

    View full-size slide

  4. DmitryTsepelev
    404fest'21
    Обновляем в цикле
    5
    User.in_batches do |batch|


    batch.update_all(email_conf
    i
    rmed: false)


    end


    SELECT users.id FROM users WHERE users.id > 0 ORDER BY users.id ASC LIMIT 1000;


    UPDATE users SET email_conf
    i
    rmed = FALSE WHERE users.id IN (
    . . .
    );


    SELECT users.id FROM users WHERE users.id > 1000 ORDER BY users.id ASC LIMIT 1000;


    UPDATE users SET email_conf
    i
    rmed = FALSE WHERE users.id IN (
    . . .
    );

    View full-size slide

  5. DmitryTsepelev
    404fest'21 6
    Ой, места на диске не осталось
    SELECT pg_size_pretty(pg_total_relation_size('users'));


    - -
    400 Gb

    View full-size slide

  6. DmitryTsepelev
    404fest'21 7
    @dmitrytsepelev

    View full-size slide

  7. Как и зачем MVCC мешает нам
    пользоваться базой данных
    и почему это не плохо

    View full-size slide

  8. DmitryTsepelev
    404fest'21 9

    View full-size slide

  9. DmitryTsepelev
    404fest'21
    БД и разработчики
    • есть таблички с данными;


    • данные можно изменять и запрашивать;


    • таблички можно объединять (JOINs) в запросах;


    • таблички связаны с помощью foreign keys;


    • есть constraints для валидации данных.
    10

    View full-size slide

  10. DmitryTsepelev
    404fest'21
    Делаем биллинг
    def handle_payment(user, payment)


    payment.update(processed: true)


    subscription_active_until =


    [user.subscription_active_until, Date.current].max + payment.days_covered


    user.update(subscription_active_until: subscription_active_until)


    end


    UPDATE payments SET processed = true WHERE id = ?;


    UPDATE users SET subscription_active_until = '2021-10-23' WHERE id = ?;


    11

    View full-size slide

  11. DmitryTsepelev
    404fest'21
    ACID
    • Atomicity


    • Consistency


    • Isolation


    • Durability
    13

    View full-size slide

  12. DmitryTsepelev
    404fest'21
    Делаем биллинг
    def handle_payment(user, payment)


    payment.update(processed: true)


    subscription_active_until =


    [user.subscription_active_until, Date.current].max + payment.days_covered


    user.update(subscription_active_until: subscription_active_until)


    end
    14
    А вдруг user.update выбросит исключение? 🤔

    View full-size slide

  13. DmitryTsepelev
    404fest'21
    Atomicity (доделываем биллинг)
    def handle_payment(user, payment)


    ActiveRecord
    : :
    Base.transaction do


    payment.update(processed: true)


    subscription_active_until =


    [user.subscription_active_until, Date.current].max + payment.days_covered


    user.update(subscription_active_until: subscription_active_until)


    end


    end


    BEGIN;


    UPDATE payments SET processed = true WHERE id = ?;


    UPDATE users SET subscription_active_until = '2021-10-23' WHERE id = ?;


    COMMIT;
    15

    View full-size slide

  14. DmitryTsepelev
    404fest'21
    Транзакция
    • переводит БД из одного состояния в другое;


    • может содержать несколько операций;


    • фиксируется только если все операции прошли успешно;


    • фиксация изменений происходит одновременно;


    • работает независимо от других транзакций.
    16

    View full-size slide

  15. DmitryTsepelev
    404fest'21
    Спин–офф: локи
    def handle_payment(user, payment)


    user.with_lock do


    return if payment.reload.processed? # на случай если платеж уже запроцессили


    payment.update(processed: true)


    subscription_active_until =


    [user.subscription_active_until, Date.current].max + payment.days_covered


    user.update(subscription_active_until: subscription_active_until)


    end


    end


    BEGIN;


    SELECT * FROM payments WHERE id = ? FOR UPDATE;


    UPDATE payments SET processed = true WHERE id = ?;


    UPDATE users SET subscription_active_until = '2021-10-23' WHERE id = ?;


    COMMIT;
    17

    View full-size slide

  16. DmitryTsepelev
    404fest'21
    ACID
    • Atomicity


    • Consistency


    • Isolation


    • Durability
    18

    View full-size slide

  17. DmitryTsepelev
    404fest'21
    Целостность и консистентность
    • целостные данные соответствуют ограничениям БД (UNIQUE, NOT
    NULL, …);


    • корректные данные соответствуют всем ограничениям
    приложения;


    • БД не всегда может гарантировать корректность.
    19

    View full-size slide

  18. DmitryTsepelev
    404fest'21
    ACID
    • Atomicity


    • Consistency


    • Isolation


    • Durability
    20

    View full-size slide

  19. DmitryTsepelev
    404fest'21
    Isolation
    21
    BEGIN;


    UPDATE payments SET processed = true WHERE id = 1;


    SELECT processed FROM payments WHERE id = 1;


    - -
    true


    COMMIT;
    BEGIN;


    SELECT processed FROM payments WHERE id = 1;


    - -
    false


    COMMIT;


    BEGIN;


    SELECT processed FROM payments WHERE id = 1;


    - -
    true


    View full-size slide

  20. DmitryTsepelev
    404fest'21
    ACID
    • Atomicity


    • Consistency


    • Isolation


    • Durability
    22

    View full-size slide

  21. Как БД хранит данные

    View full-size slide

  22. DmitryTsepelev
    404fest'21
    Как хранятся данные на диске
    24
    users
    id name
    1 John
    2 Jane
    42 (< 1GB)
    users forks
    Database
    orders
    id user_id
    1 2
    123 (< 1GB)
    orders forks
    Disk

    View full-size slide

  23. DmitryTsepelev
    404fest'21
    Как хранятся данные на диске
    25
    users
    id name
    1 John
    2 Jane
    Disk
    42 (< 1GB)
    users forks
    Database
    orders
    id user_id
    1 2
    123 (1GB)
    orders forks
    123.1 (< 1GB)
    Fork
    fi
    le
    page 1 (8 kB) page 2 (8 kB)
    page 3 (8 kB)

    View full-size slide

  24. DmitryTsepelev
    404fest'21
    Tuple
    • tuple = header + user data;


    • tuple почти никогда не обновляется (БД может, мы нет);


    • DELETE = UPDATE xmax;


    • UPDATE = INSERT + DELETE.
    26
    xmin xmax ctid infomask id name
    123 125 (3, 15) 111 1 Jon
    123 0 (4, 1) 111 2 Jane
    125 0 (3, 15) 111 1 Jonh

    View full-size slide

  25. DmitryTsepelev
    404fest'21
    Tuple header
    27
    BEGIN;


    SELECT txid_current();


    ┌──────────────┐


    │ txid_current │


    ├──────────────┤


    │ 8249614 │


    └──────────────┘


    INSERT INTO users (email) VALUES ('[email protected]');


    SELECT *, ctid, xmin, xmax FROM users;


    ┌────┬──────────────────┬───────┬─────────┬──────┐


    │ id │ email │ ctid │ xmin │ xmax │


    ├────┼──────────────────┼───────┼─────────┼──────┤


    │ 1 │ [email protected] │ (0,1) │ 8249614 │ 0 │


    └────┴──────────────────┴───────┴─────────┴──────┘


    View full-size slide

  26. DmitryTsepelev
    404fest'21
    Tuple header
    28
    CREATE EXTENSION pageinspect;


    SELECT (t_infomask & 256) > 0 AS xmin_commited, *


    FROM heap_page_items(get_raw_page('users', 0));


    ┌───────────────┬─────────┬────────┬──────────┬────────┬─────────────┬────────────┬────────┬────────┬───────┬────────┐


    │ xmin_commited │ t_xmin │ t_xmax │ t_f
    i
    eld3 │ t_ctid │ t_infomask2 │ t_infomask │ t_hoff │ t_bits │ t_oid │ t_data │


    ├───────────────┼─────────┼────────┼──────────┼────────┼─────────────┼────────────┼────────┼────────┼───────┼────────┤


    │ f │ 8249614 │ 0 │ 11 │ (0,1) │ 2 │ 2050 │ 24 │ ¤ │ ¤ │
    . . .



    └───────────────┴─────────┴────────┴──────────┴────────┴─────────────┴────────────┴────────┴────────┴───────┴────────┘


    COMMIT;


    ┌───────────────┬─────────┬────────┬──────────┬────────┬─────────────┬────────────┬────────┬────────┬───────┬────────┐


    │ xmin_commited │ t_xmin │ t_xmax │ t_f
    i
    eld3 │ t_ctid │ t_infomask2 │ t_infomask │ t_hoff │ t_bits │ t_oid │ t_data │


    ├───────────────┼─────────┼────────┼──────────┼────────┼─────────────┼────────────┼────────┼────────┼───────┼────────┤


    │ t │ 8249614 │ 0 │ 11 │ (0,1) │ 2 │ 2050 │ 24 │ ¤ │ ¤ │
    . . .



    └───────────────┴─────────┴────────┴──────────┴────────┴─────────────┴────────────┴────────┴────────┴───────┴────────┘

    View full-size slide

  27. DmitryTsepelev
    404fest'21
    Update tuple
    29
    BEGIN;


    SELECT txid_current();


    ┌──────────────┐


    │ txid_current │


    ├──────────────┤


    │ 8249618 │


    └──────────────┘


    UPDATE users SET email = '[email protected]' WHERE id = 1;


    SELECT *, ctid, xmin, xmax FROM users;


    ┌────┬──────────────────────┬───────┬─────────┬──────┐


    │ id │ email │ ctid │ xmin │ xmax │


    ├────┼──────────────────────┼───────┼─────────┼──────┤


    │ 1 │ [email protected] │ (0,2) │ 8249618 │ 0 │


    └────┴──────────────────────┴───────┴─────────┴──────┘


    View full-size slide

  28. DmitryTsepelev
    404fest'21
    Update tuple
    30
    SELECT t_xmin, t_xmax, t_ctid FROM heap_page_items(get_raw_page('users', 0));


    ┌─────────┬─────────┬────────┐


    │ t_xmin │ t_xmax │ t_ctid │


    ├─────────┼─────────┼────────┤


    │ 8249616 │ 8249618 │ (0,2) │


    │ 8249619 │ 0 │ (0,2) │


    └─────────┴─────────┴────────┘

    View full-size slide

  29. DmitryTsepelev
    404fest'21
    Delete tuple
    31
    BEGIN;


    SELECT txid_current();


    ┌──────────────┐


    │ txid_current │


    ├──────────────┤


    │ 8249620 │


    └──────────────┘


    DELETE FROM users WHERE id = 1;


    SELECT t_xmin, t_xmax, t_ctid FROM heap_page_items(get_raw_page('users', 0));


    ┌─────────┬─────────┬────────┐


    │ t_xmin │ t_xmax │ t_ctid │


    ├─────────┼─────────┼────────┤


    │ 8249616 │ 8249619 │ (0,2) │


    │ 8249619 │ 8249621 │ (0,2) │


    └─────────┴─────────┴────────┘


    View full-size slide

  30. Multi Version Concurrency
    Control

    View full-size slide

  31. DmitryTsepelev
    404fest'21
    MVCC
    • чтение не блокирует запись;


    • запись не блокирует чтение;


    • каждая транзакция видит свой «слепок» данных.
    33

    View full-size slide

  32. DmitryTsepelev
    404fest'21
    Snapshot
    • физическая копия данных не делается;


    • «видимые» данные определяются исходя из условий.
    34
    SELECT *


    FROM users


    WHERE xmin
    < =
    txid_current()


    AND (xmax = 0 OR txid_current() < xmax);


    - - -
    ^^ ^^


    - - -
    запись еще не удалена запись удалена позже


    View full-size slide

  33. DmitryTsepelev
    404fest'21
    Snapshot
    T1
    T2
    T3
    T4
    S1 S2 S3
    • S1: 🤷


    • S2: T1


    • S3: T1, T2
    35

    View full-size slide

  34. DmitryTsepelev
    404fest'21
    Snapshot
    36
    BEGIN;


    SELECT txid_current();


    ┌──────────────┐


    │ txid_current │


    ├──────────────┤


    │ 8257515 │


    └──────────────┘


    SELECT txid_current_snapshot();


    ┌──────────────────────────────┐


    │ txid_current_snapshot │


    ├──────────────────────────────┤


    │ 8257515
    :
    8257515
    :
    8257515 │


    └──────────────────────────────┘


    INSERT INTO users (email) VALUES ('[email protected]');


    SELECT *, ctid, xmin, xmax FROM users;


    ┌────┬──────────────────┬───────┬─────────┬──────┐


    │ id │ email │ ctid │ xmin │ xmax │


    ├────┼──────────────────┼───────┼─────────┼──────┤


    │ 2 │ [email protected] │ (0,3) │ 8257515 │ 0 │


    └────┴──────────────────┴───────┴─────────┴──────┘
    xmin
    xmax
    список активных транзакций

    View full-size slide

  35. DmitryTsepelev
    404fest'21
    Savepoints
    37
    BEGIN;


    SELECT txid_current();


    ┌──────────────┐


    │ txid_current │


    ├──────────────┤


    │ 8257515 │


    └──────────────┘


    SELECT txid_current_snapshot();


    ┌──────────────────────────────┐


    │ txid_current_snapshot │


    ├──────────────────────────────┤


    │ 8257515
    :
    8257515
    :
    8257515 │


    └──────────────────────────────┘


    INSERT INTO users (email) VALUES ('[email protected]');


    SELECT *, ctid, xmin, xmax FROM users;


    ┌────┬──────────────────┬───────┬─────────┬──────┐


    │ id │ email │ ctid │ xmin │ xmax │


    ├────┼──────────────────┼───────┼─────────┼──────┤


    │ 2 │ [email protected] │ (0,3) │ 8257516 │ 0 │


    └────┴──────────────────┴───────┴─────────┴──────┘

    View full-size slide

  36. DmitryTsepelev
    404fest'21
    Savepoints
    • механизм для "вложенных" транзакций;


    • позволяет откатить часть изменений, не откатывая всю
    транзакцию;


    • txid_current возвращает идентификатор главной транзакции!
    38
    BEGIN;


    SAVEPOINT s1;


    INSERT INTO users (email) VALUES ('[email protected]');


    ROLLBACK TO s1;

    View full-size slide

  37. DmitryTsepelev
    404fest'21
    Где кроме PSQL используется MVCC?
    • MySQL (InnoDB/Falcon/Archive storage)


    • MariaDB (XtraDB/PBXT)


    • MongoDB (WiredTiger storage)


    • Oracle (> 4)


    • CouchDB


    • Couchbase


    • …
    39

    View full-size slide

  38. Спецэффекты

    View full-size slide

  39. DmitryTsepelev
    404fest'21
    Аномалии изоляции
    • Dirty Read


    • Nonrepeatable Read


    • Phantom Read


    • Serialization Anomaly
    41

    View full-size slide

  40. DmitryTsepelev
    404fest'21
    Уровни изоляции транзакций
    42
    Isolation Level Dirty Read
    Nonrepeatable
    Reads
    Phantom Reads
    Serialization
    Anomaly
    Read
    Uncommited
    (not PG) (not PG) x x
    Read Commited x x x
    Repeatable Read (not PG) x
    Serializable

    View full-size slide

  41. DmitryTsepelev
    404fest'21
    Как работают уровни изоляции?
    43
    • в Read Committed снепшот создается перед каждым запросом;


    • в Repeatable Read и Serializable снепшот создается только один раз.

    View full-size slide

  42. DmitryTsepelev
    404fest'21
    Как работает Serializable
    44
    • гарантируется, что результат работы транзакций не зависит от
    порядка выполнения;


    • для полностью корректной работы нужно обходить граф
    зависимостей;


    • упрощенно: ищем ситуации, когда одна транзакция изменила
    строку, а другая — прочитала предыдущую версию этой строки;


    • простой подход может вызывать ложно–положительные
    срабатывания.

    View full-size slide

  43. DmitryTsepelev
    404fest'21
    Почему не использовать Serializable всегда?
    45
    • придется использовать его для ВСЕХ транзакций;


    • придется всегда ловить исключение и перезапускать операцию;


    • Serializable работает медленнее других уровней.

    View full-size slide

  44. DmitryTsepelev
    404fest'21 46
    INSERT INTO users (email) VALUES ('[email protected]');


    INSERT INTO users (email) VALUES ('[email protected]');


    INSERT INTO users (email) VALUES ('[email protected]');


    SELECT * FROM users;


    ┌────┬──────────────────┐


    │ id │ email │


    ├────┼──────────────────┤


    │ 1 │ [email protected]


    │ 2 │ [email protected]


    │ 3 │ [email protected]


    └────┴──────────────────┘





    SELECT * FROM users;


    ❓ ❓ ❓

    View full-size slide

  45. DmitryTsepelev
    404fest'21
    INSERT INTO users (email) VALUES ('[email protected]');


    INSERT INTO users (email) VALUES ('[email protected]');


    INSERT INTO users (email) VALUES ('[email protected]');


    SELECT * FROM users;


    ┌────┬──────────────────┐


    │ id │ email │


    ├────┼──────────────────┤


    │ 1 │ [email protected]


    │ 2 │ [email protected]


    │ 3 │ [email protected]


    └────┴──────────────────┘





    SELECT * FROM users;


    ┌────┬─────────────────────┐


    │ id │ email │


    ├────┼─────────────────────┤


    │ 2 │ [email protected]


    │ 3 │ [email protected]


    │ 1 │ [email protected]


    └────┴─────────────────────┘
    Проблема: нет явного порядка
    47

    View full-size slide

  46. DmitryTsepelev
    404fest'21
    INSERT INTO users (email) VALUES ('[email protected]');


    INSERT INTO users (email) VALUES ('[email protected]');


    INSERT INTO users (email) VALUES ('[email protected]');


    SELECT * FROM users;


    ┌────┬──────────────────┐


    │ id │ email │


    ├────┼──────────────────┤


    │ 1 │ [email protected]


    │ 2 │ [email protected]


    │ 3 │ [email protected]


    └────┴──────────────────┘


    UPDATE users SET email = '[email protected]' WHERE id = 1;


    SELECT * FROM users;


    ┌────┬─────────────────────┐


    │ id │ email │


    ├────┼─────────────────────┤


    │ 2 │ [email protected]


    │ 3 │ [email protected]


    │ 1 │ [email protected]


    └────┴─────────────────────┘
    Проблема: нет явного порядка
    48

    View full-size slide

  47. DmitryTsepelev
    404fest'21
    SELECT t_xmin, t_xmax, t_ctid


    FROM heap_page_items(get_raw_page('users', 0));


    ┌─────────┬─────────┬────────┐


    │ t_xmin │ t_xmax │ t_ctid │


    ├─────────┼─────────┼────────┤


    │ 8257533 │ 8257536 │ (0,4) │


    │ 8257534 │ 0 │ (0,2) │


    │ 8257535 │ 0 │ (0,3) │


    │ 8257536 │ 0 │ (0,4) │


    └─────────┴─────────┴────────┘
    Проблема: нет явного порядка
    49

    View full-size slide

  48. DmitryTsepelev
    404fest'21
    Фича: определяем была ли запись создана в upsert
    50
    INSERT INTO users (name, email) VALUES ('John', '[email protected]');


    INSERT INTO users (name, email)


    VALUES


    ('John!', '[email protected]'),


    ('Jane', '[email protected]')


    ON CONFLICT (email)


    DO UPDATE SET name = EXCLUDED.name


    RETURNING id, (xmax = '0') AS inserted, name AS new_name;


    ┌─────────┬──────────┬──────────┐


    │ id │ inserted │ new_name │


    ├─────────┼──────────┼──────────┤


    │ 1 │ f │ John! │


    │ 2 │ t │ Jane │


    └─────────┴──────────┴──────────┘

    View full-size slide

  49. DmitryTsepelev
    404fest'21
    Фича: определяем была ли запись создана в upsert
    51
    select *, xmin, xmax, ctid from users;


    ┌─────────┬──────────────────┬───────┬─────────┬─────────┬───────┐


    │ id │ email │ name │ xmin │ xmax │ ctid │


    ├─────────┼──────────────────┼───────┼─────────┼─────────┼───────┤


    │ 1 │ [email protected] │ John! │ 8843025 │ 8843025 │ (0,2) │


    │ 2 │ [email protected] │ Jane │ 8843025 │ 0 │ (0,3) │


    └─────────┴──────────────────┴───────┴─────────┴─────────┴───────┘


    🤔🤔🤔

    View full-size slide

  50. DmitryTsepelev
    404fest'21
    Фича: определяем была ли запись создана в upsert
    52
    select *, xmin, xmax, ctid from users;


    ┌─────────┬──────────────────┬───────┬─────────┬─────────┬───────┐


    │ id │ email │ name │ xmin │ xmax │ ctid │


    ├─────────┼──────────────────┼───────┼─────────┼─────────┼───────┤


    │ 1 │ [email protected] │ John! │ 8843025 │ 8843025 │ (0,2) │


    │ 2 │ [email protected] │ Jane │ 8843025 │ 0 │ (0,3) │


    └─────────┴──────────────────┴───────┴─────────┴─────────┴───────┘


    🤔🤔🤔
    В некоторых случаях xmax может означать всего лишь лок:


    https://stackover
    fl
    ow.com/a/39204667

    View full-size slide

  51. DmitryTsepelev
    404fest'21 53
    INSERT INTO users (email)


    SELECT random()
    : :
    text


    FROM generate_series(1, 1000000);


    SELECT pg_size_pretty(pg_total_relation_size('users'));


    - -
    72 MB


    UPDATE users SET email = 'something';


    SELECT pg_size_pretty(pg_total_relation_size('users'));


    ❓ ❓ ❓

    View full-size slide

  52. DmitryTsepelev
    404fest'21
    Проблема: большой апдейт может раздуть таблицу
    54
    INSERT INTO users (email)


    SELECT random()
    : :
    text


    FROM generate_series(1, 1000000);


    SELECT pg_size_pretty(pg_total_relation_size('users'));


    - -
    72 MB


    UPDATE users SET email = 'something';


    SELECT pg_size_pretty(pg_total_relation_size('users'));


    - -
    135 MB

    View full-size slide

  53. DmitryTsepelev
    404fest'21
    Проблема: удаление данных из таблицы не освобождает место на диске
    55
    VACUUM users;


    SELECT pg_size_pretty(


    pg_total_relation_size('users')


    );


    - -
    135 MB


    VACUUM FULL users;


    SELECT pg_size_pretty(


    pg_total_relation_size('users')


    );


    - -
    64 MB

    View full-size slide

  54. DmitryTsepelev
    404fest'21
    Проблема: ID транзакций могут «кончиться»
    • тип ID транзакции XID, 32 бита (~4 000 000 000 значений);


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


    • БД начинает счет заново при достижении максимального
    значения;


    • VACUUM «замораживает» (freeze) старые таплы.
    56

    View full-size slide

  55. DmitryTsepelev
    404fest'21
    Выводы
    • MVCC нужен для обеспечения изоляции транзакций;


    • на низком уровне данные не обновляются, а записываются;


    • реализация MVCC иногда приводит к неожиданным сайд–
    эффектам.
    57

    View full-size slide

  56. DmitryTsepelev
    404fest'21
    Куда пойти дальше
    • Цикл статей про MVCC:


    • 🇬🇧 https://postgrespro.com/blog/pgsql/5967856;


    • 🇷🇺 https://habr.com/ru/company/postgrespro/blog/442804/;


    • The Internals of PostgreSQL — http://www.interdb.jp/pg/;


    • Serializable Snapshot Isolation in PostgreSQL — https://arxiv.org/pdf/
    1208.4179.pdf;


    • Database Internals — https://www.databass.dev.
    58

    View full-size slide

  57. evl.ms/blog
    @dmitrytsepelev @evilmartians
    evl.ms/telegram
    Спасибо!
    @dmitrytsepelev
    59

    View full-size slide