DBA2-10 Тема 1 «Изоляция»
Транзакция - множество операций, которые переводят базу данных из одного корректного состояни в другое корретное (соглассованость) при условии, что транзакция выполнена полностью (атомарность) и без помех со стороны других транзакций (изоляция)
Атомарность - транзакция либо выполняется полностью, либо не оставляет никаких следов
Согласованность определяет ограничениями целостности и семантикой приложения
Неполная изоляция приводит к нарушению корректности (аномалиям) при конкурентном выполнении
Возможные аномалии (согласно стандарту, но на деле их больше):
- “грязное” чтение - запрос видит незафиксированные изменения других транзакций
- неповторяющееся чтение - повторное чтение строки вернет другое значение, если оно было изменено (и зафиксировано) другой транзакцией (тут про ячейки)
- фантомное чтение - повторный запрос по одниоу и тому же условию вернет иную выборку, если другая транзакция добавила (и зафиксировала) новые строки, удовлетворяюще этому условию (тут про строки)
- остутсвие всяких аномалий - результат выполнения транзакций совпадает с результатом последовательного выполнения транзакций
Уровни изоляции (согласно стандарта)
| Грязное чтение | неповторяющееся чтение | фантомное чтение | другие аномалии | | ------------------- | -------------------------- | -------------------- | ------------------- | --- | | Read Uncommited | да | да | да | да | | Read Committed | - | да | да | да | | Repeatable Read | - | - | да | да | | Serializable | - | - | - | - |
на всех уровнях не допускается потеря зафиксированных изменений
Уровни изоляции postgres
| Грязное чтение | неповторяющееся чтение | фантомное чтение | другие аномалии | | --------------------------------- | -------------------------- | -------------------- | ------------------- | --- | | Read Committed (по умолчанию) | - | да | да | да | | Repeatable Read | - | - | - | да | | Serializable | - | - | - | - |
Демо
SHOW transaction_isolation; -- текущий уровень изоляции
SHOW default_transaction_isolation; -- значение по умолчаниюВ read committed данные вычитываются согласованно на начало запуска оператора
select pg_sleep(2), * from demo; -- пока ждем 2 сек, табличка поменялась, но мы не увидим этих измененийОбновляющие транзакции, в случае конфликтов, вынужденны перевычитать данные (перечитываются только измененные строки)
DBA2-10 Тема 2 «Страницы и версии строк»
- Страницы (8кб)
- табличная страница
- заголовок страницы (24 байта)
- указатели на версии строк (массив)
- строка
- заголовок весрии строки
- версия строки
- страница индекса
- заголовок страницы (24 байта)
- указатели на версии строк (массив)
- специальная область (специфичные данные для разных видов индексов)
- строка
- индексируемое значение
- указатель на версию строки в странице таблички (обратная ссылка)
- табличная страница
Благодаря тому что индексы ссылаются только на указатели сами данные внутри страничек можно реорганизовать никак не трогая индекс
Указатели на версии строк хранят:
- статус версии строки
- указатель на версию строки и ее длина
Версии строк в табличной странице
За это критикуют postgres, т.к. вне зависимости от размера строки будет всегда плюс 24 байта
- xmin committerd, xmin aborted - биты позволяющие ответить на статус завершения транзакции xmin
- ctid - указатель на следующую версию этой же строки (аля linked list), если следующей версии нет - указатель смотрит сам на себя
Для индексов все в принципе так же, за исключением того что за вместо данных мы храним сам ключ (значения колонок), а ссылка ведет на саму строку
В индексе нет xmin, xmax, по этому смотря в индекс нельзя сказать должна ли быть видна эта строка, из-за чего необходимо идти в табличку за xmin, xmax после чего уже становиться понятно
Страницы читаются в оперативную память “как есть”, данные неперносимы между разными платформами, между полями данных возможны пропуски из-за выравнивания
Вставка
xmax aborted выставился в ture, что говорит о том что еще нет транзакции удалившей эту версию и поле xmax можно игнорить
XACT - журнал транзакций, здесь мы видим что транзакция под номером 100 все еще жужжит
После комита, в строке ничего не происходит (бит как был так и не проставлялся) последующие запросы будут ходить и сверяться с журналом XACT и уже последующая транзакция выполнив эту проверку проставит этот бит
Это может быть читающая транзакция
Интересный пример, после COPY перегона большого объема данных, потом если выполнить select count(*) будет большая дисковая активность, так как этот sleect начнет проставлять битики
Выставлять сразу не выходит т.к. нужно помнить кучу мест где мы меняли и сам коммит станет дорогим т.к. мы будетм всюду бегать и повторно менять
Commit дешевый, но плата за это - следующая транзакиция читающая эту строку будет чуть дольше так как будет сверять и обновлять байты
проставляем 101 в xmax, транзакция по все еще бежит (так же ведет себя select for update)
следующая транзакция видит xmax и то что 101 еще не завершилась - как следствие строка заблокированна и надо ждать
так же как и с commit, при вызове rollback обновляется xact, но биты в строке не меняются, бит в строке будет обновлен когда будет обращение от последующей строки
при обновлении мы проставляем 102 в xmax старой версии
содаем новую версию в которой 102 будет в xmin, ссылка старой строки обновляется и смотрит на новую версию
в индексе так же появляется вторая строка ссылающаяся на вторую версию
Каждый выполняемый запрос - это транзакция, как следствие будет иметь свой номер, при выполнении транзакции внтури другой транзакции база трекает их и следит за тем что бы применилось все сразу
Вложенные транзакции
- собственный номер и статус в XACT - конечный статус зависит от статуса основной тразакции
- информация о вложенности сохраняется на диске
- каталог
$PGDATA/pg_subtrans/ - данные кэшируются в буферах общей памяти (аналогично XACT)
- каталог
- примеры использования
- точка SAVEPOINT
- обработка исключений в PL/pgSQL (EXCEPTION)
- режим
psql ON_ERROR_ROLLBACK = on/interactive
Пример
CREATE TABLE t(id serial, s text);
CREATE INDEX t_s ON t(s);
-- что бы изучить структуру страницы и версий строк, есть расширение pageinspect
CREATE EXTENSION pageinspect;
-- вьюха что бы смотреть
-- get_raw_page - читает сырые байтики без понимание что это
-- heap_page_items - парсит именно табличную страничку
CREATE VIEW t_v AS
SELECT '(0,'||lp||')' AS ctid,
CASE lp_flags
WHEN 0 THEN 'unused'
WHEN 1 THEN 'normal'
WHEN 2 THEN 'redirect to '||lp_off
WHEN 3 THEN 'dead'
END AS state,
t_xmin as xmin,
t_xmax as xmax,
CASE WHEN (t_infomask & 256) > 0 THEN 't' END AS xmin_c,
CASE WHEN (t_infomask & 512) > 0 THEN 't' END AS xmin_a,
CASE WHEN (t_infomask & 1024) > 0 THEN 't' END AS xmax_c,
CASE WHEN (t_infomask & 2048) > 0 THEN 't' END AS xmax_a,
t_ctid
FROM heap_page_items(get_raw_page('t',0))
ORDER BY lp;
-- похожее представление для индекса
-- у индекса первая страничка 0 - занята служебными данными, потому тут мы читаем 1-ю
CREATE VIEW t_s_v AS
SELECT itemoffset,
ctid
FROM bt_page_items('t_s',1);В сессии
BEGIN;
INSERT INTO t(s) VALUES('FOO');
SELECT txid_current(); -- 493
SELECT * FROM t_v;
-- похожее но менее информативное
SELECT xmin, xmax, * FROM t;
COMMIT;
SELECT * FROM t_v; -- ничего не поменялось
SELECT * FROM t;
SELECT * FROM t_v; -- а вот теперь будут байтикиСтурктура странички
SELECT lower, upper, special, pagesize FROM page_header(get_raw_page('t',0));- 0 - начало заголвка странички и указатели на версии строк
- lower - начало свободного места
- upper - начало данных (версий строк)
- special - начало спец. данных (только для индексов)
- pagesize - конец страницы
- от 0 до lower - занимает заголовок странички (24 байта) и массив указателей - можно ответить на вопрос сколько указателей
- от lower до upper - это свободное место
- от upper до special - это занятое место
DBA2-10 Тема 3 «Снимки данных»
Снимок - дает согласованную картину данных на момент времени (видны только зафиксированные на этот момент данные)
Создается
- read commited - в начале каждого оператора
- repeatable read, serializable - в начале первого оператора транзакции
Видимость версий строк
- видимость версии строки ограничена xmin и xmax
- версия попадает в снимок, когда
- изменения транзакции xmin видны для снимка
- изменения транзакции xmax не видны для снимка
- изменения транзакции видны, когда
- либо это та же самая тразакция, что создала снимок
- либо она звершилась фиксацией до момента создания снимка
- отдельные правила для видимости собственных изменений
- учитывается порядковый номер операции в транзакции (cmin/cmax)
Снимок данных
- номер самой ранней активной транзакции
- номер следующей транзакции (еще не существующей)
- список активных транзакций
- горизонт - точка до которой проходит autovacuum
- пока транзакция висит - автовакуум не может продвигаться вперед
old_snapshot_threshold- время, на которое транзакции разрешено транзакции держать снимок, вакууму разрешено удалять строки, если транзакции понадобяться строки она завершиться с ошибкой и откатитьсяidle_in_transaction_session_timeout- прибивать транзакции которые ничего не делают но держат вакуум
Виртуальные транзакции
Только читающая транзакция никак не влияет на видимость
- обслуживающий процесс выделает виртуальный номер
- виртуальный номер не учитывается в снимках
- виртуальный номер никогда не попадает в страницы
- “настроящий” номер выделяется при первом изменении данных
select txid_current(); --
select txid_current_if_assigned(); -- более честный\оптимальный способ узнать свой номер
select txid_current_snapshot(); -- xmin:xmax:xip_list
select backend_xmin from pg_stat_activity where pid = pg_backend_pid(); -- горизонтDBA2-10 Тема 4 «HOT-обновления»
Проблемы обновления
- при любом обновлении строки надо изменять индекс - страдает производительность вставок и изменений
- в индексе накапливаются ссылки на неактуальные версии - размер индекса растет, требуется очистка
- все сложности умножаются на количество индексов, построенных по таблице
HOT-обновление
- HOT - heap only tuple
- heap - подразумевается табличка
- tuple - кортеж, строка
- heap only tuple - эта версия строки находиться только в таблице и на нее нет ссылок из индекса
- heap hot upd - признак что было обновлено HOT
- заскочив в табличку пойдет по heap hot upd - пока не встретит false
HOT-обновления
- Обновляемый столбей не долджен входить ни в один индекс - иначе на версию строки будет ссылка из индекса и ее нельзя пометить как heap only
- Цепочка обновлений - только в пределах одной страницы
- не требуется обращение к другим страница, обход цепочки не ухудшает производительность
- если в табличной странице не хватает места для новой версии, цепочка обрывается (как если бы оптимизация не работала)
- место в странице можно зарезервировать. уменьшив параметр хранения таблицы fillfactor (100% → 10%)
Внутристраничная очистка
- выполняется при любом обращении к старнице
- если ранее выполненное обновление не нашло места для новой версии строки на этой же странице
- если страница заполнена больше, чем на fillfactor
- действует в пределах одной табличной страницы
- не освобождает указатели, на которые могут ссылаться индексы
- не обновляет карту свободного пространтсва
- не обновляет карту видимости
Происходит зачистка, но индекс не трогается, т.к. это другая страничка
Примечания:
- фактическую очистку проведет вакуум
- индекс, впервые сходив и увидев что колонка dead пометит и у себя как мерткую
- unused - это как бы по умолчанию, можно использовать
- теоретически очистка внутри странички может прям укладываться в страничку и как следствие и вакууму делать ничего не надо будет
DBA2-10 Тема 5 «Очистка»
Обычная очистка
- выполняется командой VACUUM - не конфликтует с обычной активностью в системе
- обрабатывает таблицу и все ее индексы
- очищает ненужные версии строк в табличных страницах (пропуская страницы, уже отмеченные в карте видимости)
- очищает индексные записи, ссылающиеся на очищенные версии страниц
- освобождает указатели
- обновляет карту свободного пространтсва - спец структура, в которой для таблицы храняться “дырки” куда можно вставить новые строки
- обновляет карту видимости - для каждой странички выставляется бит, являются ли все версии на этой страничке видимыми для всех пользователей (аля можно не чистить)
maintenance_work_mem - сюда складываются указатели которые нужно зачищать
Мониторинг
VACUUM VERBOSE- будет выводить инфу о том что он сейчас делает- представление
pg_stat_progress_vacuum- полный размер таблицы
- число прочитанных страниц и число очищенных страниц
- количество уже завершенных циклов очистки индексов
- число идентификаторов версий строк, помещающихся в память и текущее число идентификаторов в памяти
- текущая фаза очистки
Регулирование нагрузки
- процесс чередует работу и ожидание - примерно vacuum_cost_limit условных единиц работы, затем засыпает на vacuum_cost_delay мс
- настройки
- vacuum_cost_limit = 200
- vacuum_cost_delay = 0ms
- стоимость обработки
- vacuum_cost_page_hit = 1 - страницы в кэше
- vacuum_cost_page_miss = 10 - страницы на диске (нет в памяти, нужно вычитать с диска)
- vacuum_cost_page_dirty = 20 - грязной страницы (означает что нужно будет записать на диск)
Анализ
- выполняется при VACUUM ANALYZE или ANALYZE - не конфликтует с обычной активностью в системе
- собирает статистику для планировщика
DBA2-10 Тема 6 «Автоочистка»
Autovacuum launcher
для каждой баззы данных (в которой есть активность) запускать рабочий процесс раз в autovacuum_naptime, количесво рабочих процессов < autovacuum_max_workers
Настройки
- autovacuum = on
- track_counts = on # required, иначе автовакуум не будет знать куда ходить
- autovacuum_naptime = 60s
- autovacuum_max_workers=3
Autovacuum worker
выполнять по очереди очистку всех таблиц (включая TOAST), в которых число ненужных версий строк превышает autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factory * число строк в таблице
а так же выполнять анализ всех таблиц, в которых число изменившихся версий стррок превышает autovacuum_analyze_threshold + authovacuum_analyze_scale_factor * число строк в табличке
в одной БД может параллельно работать несколько процессов
Настройки автоочистки
- autovacuum_vacuum_threshold = 50
- autovacuum_vacuum_scale_factor = 0.2 # рекомендуется уменьшать, скажем 5, 3, 2 процента, т.к. 20% на некоторых сетапах это может уже быть очееень много, можно устанавливать отдельно на каждую табличку, см примеры ниже
Параметры хранения таблиц
- autovacuum_enabled
- toast.autovacuum_enabled
- autovacuum_vacuum_threshold
- toast.autovacuum_vacuum_threshold
- autovacuum_vacuum_scale_factor
- toast.autovacuum_vacuum_scale_factor
Мониторинг
- log_autovacuum_min_duration
- pg_stat_progress_vacuum
Подход к настройке
- баланс между разрастанием и накладными расходами - итеративный процесс
- основные параметры
- autovacuum_vacuum_scale_factor - частота обработки (понизить на порядок)
- autovacuum_max_workers - параллелизм (по умолчанию 3, стоит увеличить, но надо не забыть увеличить лимиты, иначе нет смысла)
- autovacuum_vacuum_cost_limit - скорость работы (увеличить пропорционально кол-ву рабочих)
- индивидуальная настройка важных таблиц параметрами хранения
- мониторинг
- разрастание таблиц
- очередь таблиц, ожидающих очистки
- нагрузка на диски
CREATE DATABASE mvcc_autovacuum;
\c mvcc_autovacuum
CREATE TABLE tvac(id serial, n numeric);
INSERT INTO tvac(n) SELECT 1 FROM generate_series(1,1000);
-- Выставим настройки автоочистки.
-- Небольшое время ожидания, чтобы сразу видеть результат:
ALTER SYSTEM SET autovacuum_naptime = 1;
-- Один процент строк:
ALTER SYSTEM SET autovacuum_vacuum_scale_factor = 0.01;
-- Нулевой порог:
ALTER SYSTEM SET autovacuum_vacuum_threshold = 0;
-- Выставим настройки автоанализа.
-- Два процента строк:
ALTER SYSTEM SET autovacuum_analyze_scale_factor = 0.02;
-- Нулевой порог:
ALTER SYSTEM SET autovacuum_analyze_threshold = 0;
-- Перечитаем настройки:
SELECT pg_reload_conf();
-- Создадим представление, показывающее, нуждается ли наша таблица в очистке.
CREATE VIEW vacuum_v AS
WITH params AS (
SELECT (SELECT setting::integer
FROM pg_settings
WHERE name = 'autovacuum_vacuum_threshold') AS vacuum_threshold,
(SELECT setting::float
FROM pg_settings
WHERE name = 'autovacuum_vacuum_scale_factor') AS vacuum_scale_factor
)
SELECT st.relname,
st.n_dead_tup dead_tup,
(p.vacuum_threshold + p.vacuum_scale_factor*c.reltuples)::integer max_dead_tup,
st.n_dead_tup > (p.vacuum_threshold + p.vacuum_scale_factor*c.reltuples)::integer need_vacuum,
st.last_autovacuum
FROM pg_stat_all_tables st,
pg_class c,
params p
WHERE c.oid = st.relid
AND c.relname = 'tvac';
SELECT * FROM vacuum_v;
-- relname | dead_tup | max_dead_tup | need_vacuum | last_autovacuum
-----------+----------+--------------+-------------+-----------------
-- tvac | 0 | 10 | f |
-- Таблица не требует очистки (0 ненужных версий) и ни разу не очищалась.
-- Можно создать аналогичное представление и для анализа:
CREATE VIEW analyze_v AS
WITH params AS (
SELECT (SELECT setting::integer
FROM pg_settings
WHERE name = 'autovacuum_analyze_threshold') as analyze_threshold,
(SELECT setting::float
FROM pg_settings
WHERE name = 'autovacuum_analyze_scale_factor') as analyze_scale_factor
)
SELECT st.relname,
st.n_mod_since_analyze mod_tup,
(p.analyze_threshold + p.analyze_scale_factor*c.reltuples)::integer max_mod_tup,
st.n_mod_since_analyze > (p.analyze_threshold + p.analyze_scale_factor*c.reltuples)::integer need_analyze,
st.last_autoanalyze
FROM pg_stat_all_tables st,
pg_class c,
params p
WHERE c.oid = st.relid
AND c.relname = 'tvac';
-- Таблица не требует анализа; автоанализ уже был выполнен.
-- Отключим автоочистку на уровне таблицы и изменим 11 строк (больше 1 %):
ALTER TABLE tvac SET (autovacuum_enabled = off);
UPDATE tvac SET n = n + 1 WHERE id <= 11;
SELECT * FROM vacuum_v;
-- relname | dead_tup | max_dead_tup | need_vacuum | last_autovacuum
-----------+----------+--------------+-------------+-----------------
-- tvac | 11 | 10 | t |
-- Как видно, таблице требуется автоочистка.
-- Включим автоочистку для таблицы и подождем несколько секунд...
ALTER TABLE tvac SET (autovacuum_enabled = on);
SELECT * FROM vacuum_v;
-- relname | dead_tup | max_dead_tup | need_vacuum | last_autovacuum
-----------+----------+--------------+-------------+-------------------------------
-- tvac | 0 | 10 | f | 2019-08-12 17:15:32.258085+03DBA2-10 Тема 7 «Заморозка»
Это история про второй круг счетчика транзакций
Настройка
- vacuum_freeze_min_age - минимальный возраст, с которого начинается заморозка
- vacuum_freeze_table_age - при достижении такого возраста замораживаются версии строк на всех страницах, используется карта заморозки
- autovacuum_freeze_max_age - при достижении такого возраста заморозка запускается принудительно, определяет размер XACT
DBA2-10 Тема 8 «Буферный кэш»
Диск - большой но медленный
ОЗУ - быстрая но мало
Отводим в памяти кусок, в котором кэшируем данные которые читаем с диска
Буферный кэш - это общая для всех процессов область памяти, в которой находиться массив буфферов.
Каждый буфекр это место под страничку данных (8кб) плюс заголовок
shared_buffers - определяет кол-во этих буферов, требует перезапуска, захватывается сразу
В заголовке буфера информация о том от куда пришла страница (файл, номер страницы внутри файла и т.п.) там же счетчик обращений к буферу (что бы понимать какие буфферы активно используются, а какие нет). Так же есть флаг помечающий буфер как грязный (поменялись данные, требуется сохранение на диск)
При запуске сервера, все буферы пустые, и храняться в виде списка, есть указатели на свободные буферы и “следующую жертву” которая будет вытесняться
Так же в памяти храниться хэш таблица для быстрого перехода к нужному буферу
Несколько процессов могут “закрепить” один и тот же буффер (pincount), эдакая мягкая блокировка, не разрешает страничку выкинуть из буфера, но при этом позволяет ее менять
Настройка размера кэша
- настройка shared_buffers = 128MB - очень мало
- буферный кэш должен содержать “активные” данные
- при меньшем размере постоянно вытеняются полезные страницы
- при большем размере бессмысленно расхуд накладные расходы
- начальное приближение - 0.25 ОЗУ (рекомендация с потолка, с чего начать)
- нужно учитывать двойное кэширование
- если страницы нет в кэше СУБД, она может оказаться в кэше ОС
- алгоритм вытеснения ОС не учитывает специфики базы данных
Временные таблицы
- данные временных таблиц - видны только одному секансу - нет смысла использовать общий кэш, существуют в перделах сеанса - не жалко потерять при сбое
- используется локальный буферный кэш
- не требуются блокировки
- память выделяется по необходимости в пределах temp_buffers
- обычный алгорит вытеснения
постгрес 11 умеет запоминать что было в буферном кэше и после перезапуска “прогревать” его
DBA2-10 Тема 9 «Журнал предзаписи»
Логическое устройство
- последовательность записей
- номер записи - 64 битный LSN (log sequence number)
- специальный тип pg_lsn
Физическое йстройство
- В памяти - кольцевой буферный кэш wal_buffers = -1 - означает 1/32 от shared_buffers
- На диске - файлы (сегменты) по 16мб, $PGDATA/pgwal/000100010001 - веткавремени:start_lsn:end_lsn?
Упреждающая запись
- Когда мы хотим что либо поменять в буферном кэше - мы помещаем ее в wal, получаем адрес этой записи pg_current_wal_insert_lsn(), полученный lsn выставляем в странице
- При фиксации транзакции ставим бит в XACT, после чего создаем новую запись в WAL и снова получаем lsn, который прописываем в xact
- При вытеснении страницы из кэша, если у странички есть lsn который еще не на диске, вначале сбрасываем на диск wal
pg_current_wal_lsn() -- то что меньше, уже на диске pg_current_wal_insert_lsn() - голова
Упрощенный алгоритм востановления
при старте сервера после сбоя (состояние кластера в pg_control отличается от shutdown)
- для каждой журнальной записи
- определить страницу, к которой относиться эта запись
- применить запись, если ее lsn больше, чем lsn страницы
- перезаписаать нежурналируемые табличцы init-файлами
DBA2-10 Тема 10 «Контрольная точка»
checkpoint_completion_target = 0.5
checkpoint_timeout = 5min – обычно делают пореже, 30-60 мин
max_wal_size = 1Gb
DBA2-10 Тема 11 «Настройка журнала»
Уровни журнала
- minimal - воставновление после сбоя
- replica - по умолчанию, восстановление из резервной копии, репликация плюс операции массовой обработки данных, блокировки
- logical - логическая репликация плюс информация для логического декодирования
Чем выше уровень, тем больше размер журнала