Как стать автором
Обновить
21
0

Пользователь

Отправить сообщение
При операциях, выполняющих массовое чтение или запись данных, есть опасность быстрого вытеснения полезных страниц из буферного кеша «одноразовыми» данными.


Имеются ввиду массовое чтение или запись данных в рамках одной транзакции? Или массовое чтение в рамках сессии, соединения? Или даже в рамках одного оператора внутри транзакции?

Чтобы этого не происходило, для таких операций используются так называемые буферные кольца (buffer ring) — для каждой операции выделяется небольшая часть буферного кеша. Вытеснение действует только в пределах кольца, поэтому остальные данные буферного кеша не страдают.


Расскажите, пожалуйста, поподробнее, для каких операций выделяются буфферные кольца. Интересная мысль возникла — если буфферное кольцо — это хорошая идея, то «хватит ли всем желающим» буфферных колец? Может ли такое получиться, что буфферные кольца займут весь буффер или значительную его часть, и остальным операциям «придется тесниться» на малом количестве оставшихся без колец буфферов?
Егор, очень рад продолжению цикла статей, спасибо за труд.

Интересное подозрение возникло при чтении особенностей вытеснения.
1. Пусть нужно прочитать страницу в буфферный пул с целью ее изменить — например, добавить новую строку в таблицу
2. Находим свободный слот и пусть этот слот был последним из свободных. Пишем страницу в него.
3. Изменяем страницу — добавляем строку. Отпускаем buffer pin блокировку.
4. Счетчик буфферного слота стал равен 1. Может и больше, но тут важен момент что страница «свежая» и счетчик «маленький»

И получается следущий интересный момент. Я всегда предполагал, что вытеснять нужно «старые» слоты, к которым уже давно нет обращений. Но для «свежих» буферов это получается не так. То есть свежезаписанные буфферы имеют те же шансы выжить, что и «старые» буфферы, счетчик которых мал, потому что к ним действительно долго не обращались.

На мой взгляд это «немного нечестно» для буферов-новичков. Это все равно что условно говоря на бирже фрилансеров банить новичков за то, что у них «давно не было заказов».

Прошу прощения за такое нетехническое сравнение, но я таким образом попытался передать свою мысль. Если чуть более технически написать — если идет работа с очень большими потоками данных, то «новичков» будут сразу же «вымывать» из буфферов. А это может быть не то, что нужно системе для производительной работы.

На ум приходит идея дать «буферам-новичкам» бонус в виде базового значения счетчика не 1 а например 5.
Егор, большое Вам спасибо за отличный цикл статей. И самое главное — отдельное спасибо за обратную связь, ответы на вопросы. С нетерпением жду новых статей.

Я изучал заморозку по курсу на сайте postgrespro и к сожалению не понял, на мой взгляд, очень важный момент.

Чтобы не допустить таких «путешествий» из прошлого в будущее, процесс очистки (помимо освобождения места в страницах) выполняет еще одну задачу. Он находит достаточно старые и «холодные» версии строк (которые видны во всех снимках и изменение которых уже маловероятно) и специальным образом помечает — «замораживает» — их


Заморозку можно вызвать вручную командой VACUUM FREEZE — при этом будут заморожены все версии строк, без оглядки на возраст транзакций


* То есть, у этой строки установлено 2 бита — бит фиксации и бит отмены
* Пусть все-таки строку начала менять транзакция.
* устанавливается xmax
* снимаются биты — фиксации и отмены, потому что они используются в MVCC
* xmin начинает уже иметь значение — с какой транзакции строка видна.

Как быть? ведь xmin уже полностью нерелевантен, но его надо использовать для видимости.
Думаю, что его надо каким-то числом перезаписать или установить еще какие-то некие служебные биты, чтобы «на xmin никто не обращал внимания».

Подскажите, пожалуйста, что происходит в данной ситуации. Можно было бы, конечно, происследовать вопрос самостоятельно, но это непросто, когда нет точки, от которой можно оттолкнуться.
Запустившись, рабочий процесс подключается к указанной ему базе данных и начинает с того, что строит список:
всех таблиц, материализованных представлений и toast-таблиц, требующих очистки,


И снова вопрос про TOAST, немного не в тему статьи
* Пусть в таблице есть 2 поля — JSONB и TEXT.
* Пусть есть строки, в которых содержатся достаточно крупные значения в этих полях, чтобы работал TOAST
* берем одну из строк
* меняем один символ в JSONB, например в поле number_of_posts было значение 1, а стало — 2.
* Создается ли новая версия TOAST только для JSONB? То есть TOAST это таблица, где данные хранятся в виде ключ-значение?
И каждой строке основной таблицы соответствует 2 строки TOAST таблицы (на каждое из полей)?
Очистка не блокирует другие процессы, поскольку работает постранично


Но она работает с буфферным кешем? То есть закрепляет страницы буфферного кеша + ставит физическую блокировку, чтобы
работать со страницей эксклюзивно. То есть получается, что блокировка все таки есть, если я все правильно понял.
С автоанализом дело обстоит примерно так же. Считается, что анализа требуют ту таблицы, у которых число измененных (с момента прошлого анализа) версий строк превышает пороговое значение


А где хранится информация о том, когда именно была изменена строка?
* Запоминается дата последнего анализа
* Как именно выбрираются строки, которые изменились после? Вероятно, запоминается xmax (или горизонт событий), а не сама дата. И мониторится
разница (дельта) между горизонтом событий и последней транзакцией, которая относится к таблице. Верно?
Число мертвых версий постоянно собирается коллектором статистики


Анализ таблиц, который осуществляет AUTOVACUUM и коллектор статистики — это разные процессы?
Егор, спасибо за статью и отдельное спасибо за темп 1 статья в неделю. Такой темп непросто выдерживать, поэтому вдвойне благодарен. Традиционные вопросы.

Процесс autovacuum launcher составляет список баз данных, в которых есть какая-либо активность. Активность определяется по статистике, а чтобы она собиралась, должен быть установлен параметр track_counts. Никогда не выключайте autovacuum и track_counts, иначе автоочистка не будет работать.


Получается, есть еще какой-то фоновый сборщик статистики? Потому что автовакуум сам по себе собирает статистику.
Вероятно не получится так, что на основе своей же статистики он решает когда запускать сбор статистики.
Еще вариант: длинные транзакции заранее планируются и запускаются по расписанию. Но тут нужно участие DBA — хотелось бы этого избежать тоже
Я немного расплывчато сформулировал кейс. Он такой:
* Пусть решили сделать «отчетную» реплику по просьбе аналитиков (и чтобы они нам не грузили мастер).
* Создали отдельного читающего юзера для всех аналитиков, поставили уровень изоляции Repeatable Read для согласованности, раздали логин-пароль (один на всех)
* Аналитики начали экспериментировать, строить свои отчеты.
* И вот аналитик Вася запускает отчет на таблице orders, длительность которого 3 часа.
* Реплика начала отставать все больше и больше с каждым часом, потому что запрос Васи затронул очень много данных в таблице orders.
* Вася не подозревал что получится такой долгий запрос и терпеливо решил подождать.
* Аналитик Петя решил посмотреть данные за последний час, он предполагает что данные актуальны и не знает о запросе Васи.
* Аналитик Петя строит аггрегаты, получает результат, не подозревая, что он пользуется устаревшими данными.
* И не дай бог это некий отчет, который будет использоваться при финансовых расчетах.

Если я все правильно понял, такая ситуация вполне имеет место быть.

Получается, что придется делать вот такое:
* Мониторим отставание (само собой)
* Учим аналитиков как смотреть свежесть данных, выводим им где-то например текущее оставание реплики в админке
* Учим аналитиков смотреть текущие транзакции, помогаем отследить «ждунов» — запускающих слишком длинные транзакции без согласования с остальными.
* Бедным аналитикам приходится кооперироваться между собой в чатиках, составлять расписания «длинных выгрузок» и т.п.

Очень неудобно. Как вариант можно сделать «отчетную реплику для коротких запросов» и «отчетную тормозящую реплику» для длинных. И даже может «финансовую реплику» куда ходить будут строго по расписанию.

Как Вам такая идея? Может есть идея получше? Кейс очень актуальный.

Сорри, что немного не в тему статьи
Будет ли мастер хранить сегмент WAL в этом случае до тех пор, пока реплика его себе не накатит?

Получается затятная ситуация:
* Очень длинные отчеты нагружают мастер. Только ли тем, что нужно хранить сегменты WAL? Не страдает ли от этого VACUUM, table bloat? Так ли критично то, что сегменты хранятся полдня? Но задержки такие идут скажем на постоянной основе.
* Очень длинные отчеты конкурируют между собой. Если один аналитик запустил отчет на полдня, то другие должны учитывать, что не могут получить актуальные данные в этот период.

Тогда получается, что нужно делать даже 2 реплики — одну для очень долгих отчетов, другую — для отчетов но покороче.
Однако надо понимать, что кластеризация не поддерживается: при последующих изменениях таблицы физический порядок версий строк будет нарушаться.


О команде CLUSTER очень часто упоминают и постоянно критикуют ее за то, что ее эффект «выдыхается» (в силу MVCC) и за то, что она блокирует все.

Когда все-таки ее целесообразно использовать?
Егор, большое спасибо за очередную отличную статью! Вопросы :)

Поэтому в PostgreSQL плохо сочетаются OLTP- и OLAP-нагрузка в одной базе: отчеты, выполняющиеся часами, не дадут часто обновляемым таблицам вовремя очищаться. Возможным решением может быть создание отдельной «отчетной» реплики.


Но при этом физически они выглядят одинаково. Пусть на мастере совершилась очистка а на реплике идет отчетная транзакция, которая завершится через пару часов условно.

WAL sender отправляет сегмент, где страницы уже очищены. Реплика не будет его накатывать, пока транзакция не завершится? И все остальные сегменты тоже выстроятся в очередь.

Если так то отчетная реплика может существенно отставать от мастера
Цитата из статьи

TOAST-таблица используется только при обращении к «длинному» значению. Кроме того, для toast-таблицы поддерживается своя версионность: если обновление данных не затрагивает «длинное» значение, новая версия строки будет ссылаться на то же самое значение в TOAST-таблице — это экономит место.


А если происходит обратная ситуация — меняется только «длинное значение»? Что происходит? Создается новая версия строки, по сути, копия уже существующей с той лишь разницей, что ссылка будет указывать на новую версию TOAST-таблицы?

Если так, то понятно, как будет работать HOT в данном случае. Если нет — поясните, пожалуйста, механизм с TOAST.
Вероятно, следующий вопрос слишком низкоуровневый — а как фоновый AUTO VACUUM и внутристраничная очистка «делят между собой» процесс очистки? Пусть автовакуум хочет удалить цепочку, а начавшаяся внутристраничная очистка хочет цепочку продолжить.

Что будет происходить? Вероятно, ситуация решается физическими блокировками страниц?
Внутристраничная очистка и VACUUM

Получается, что последующая процесс AUTO VACUUM почистит индексные страницы и уберет unused указатели? А также удалит цепочку.
Все неактуальные версии строк (0,1), (0,2) и (0,3) очищены; после этого на освободившееся место добавлена новая версия строки (0,5).


Ради интереса я выполнил UPDATE в транзакции и потом откатил транзакцию. Строки все равно остались очищенными. То есть внутристраничная очистка от транзакции, видимо, никак не зависит. Как и проставление битов статусов транзакций. То есть, такие операции нетранзакционны, видимо, в силу того, что такая транзакционность никогда не требуется.
Егор, спасибо за статью. Особенно интересна рекомендация про fulfill фактор как возможность оптимизации производительности. Надеюсь, выпадет случай применить ее на практике.

Так как Вам удобнее отвечать на вопросы в отдельных комментариях — напишу их также отдельно.

Внутристраничная очистка убирает версии строк, не видимые ни в одном снимке (находящиеся за «горизонтом событий» базы данных, об этом мы говорили в прошлый раз), но работает строго в пределах одной табличной страницы.


Пусть изменилось поле text, которое настолько велико, что хранится в TOAST. Пусть его изменили несколько раз. Началась внутристраничная очистка. Полагаю, что для TOAST она не работает? Потому что TOAST — это размещение данных на нескольких страницах.

применяется ли MVCC к TOAST? Вероятно, это будет раскрыто в последующих статьях и вопрос преждевременный.

Спасибо, а как пул соединений поможет в данном случае? Насколько я понял, источник информации о состоянии транзакций «один на всех»

Егор, большое спасибо за статью!

Традиционно, несколько вопросов:

№1.
На уровне изоляции Read Committed снимок создается в начале каждого оператора транзакции. Такой снимок активен, пока выполняется оператор.


То есть имеем операции «создания»/«удаления» снимка. Их тем больше, чем больше операторов в транзакции

На уровнях Repeatable Read и Serializable снимок создается один раз в начале первого оператора транзакции. Такой снимок остается активным до самого конца транзакции.


То есть имеем всего одну операцию «создания»/«удаления» снимка.

Сам вопрос: Насколько затратны операции «создания»/«удаления»? Можно ли сказать, что уровни Repeatable Read и Serializable
значительно меньше нагружают сервер БД? Или разница ничтожно мала по сравнению с другими действиями для этих транзакций? Я имею ввиду порядки затрат, соизмеримые с порядками затрат оптимизатора, то есть микросекунды. Понятно, что по сравнению с IO операциями передачи данных по сети эти затраты будут ничтожно малы.

Имеется ввиду затратность по:
* RAM — насколько «тяжеловесна» информация о снимке.
* CPU/IO — насколько трудоемко создавать новые снимки (и видимо помечать неактивные к удалению).

№2.
На уровне изоляции Read Committed снимок создается в начале каждого оператора транзакции. Такой снимок активен, пока выполняется оператор.


Что означает понятие «снимок активен» в применении к Read Committed? Это означает, что при создании нового снимка, ранее созданный (например, на момент создания транзакции) уже никак не участвует в транзакции? Как удаляются такие снимки? Они помечаются к удалению или удаляются в рамках той же транзакции?

№3
такие интервалы не пересекаются, поэтому одна строка представлена в любом снимке максимум одной своей версией.


То есть невозможна ситуация, при которой xmin xmax одной и той же строки будут перезаписаны в разных транзакциях? То есть если xmin/xmax когда-либо были записаны — измениться они уже не могут (immutable). При условии, если транзакция изменившая их первой — зафиксировалась.

№4
А для DDL-запросов (тоже транзакционны) изоляции и снимки тоже используются? Насколько механизмы изоляций и снимков отличаются от уже описанных механизмов в этой и в предыдущих статьях?

Информация

В рейтинге
8 777-й
Зарегистрирован
Активность