Pull to refresh
22
0

Ph.D., senior Node.js developer (team lead)

Send message
Вместо этого ядро должно давать точную информацию о том, какие ожидания были у сеанса, сколько их было и сколько времени они заняли


То есть подробная статистика по сеансам, которая формируется в результате анализа событий, по аналогии с информацией о работе VACUUM. Причем анализировать нужно все сеансы и очень детально.

Кажется, что это достаточно большой и сложный модуль и возможно на его написание нет ресурсов.
Егор, спасибо за отличный цикл статей. Очень ждем следующего цикла :)

Вопрос скорее не про блокировки а в целом про экосистему PostgreSQL

К сожалению, единственная доступная информация об ожиданиях — информация на текущий момент. Никакой накопленной статистики не ведется. Единственный способ получить картину ожиданий во времени — семплирование состояния представления с определенным интервалом. Встроенных средств для этого не предусмотрено


Существует класс трудноулавливаемых и трудноизучаемых запросов — быстрых, короткоживущих. Например, где-то когда-то в цикле запрашивали строчки по одной, вместо того чтобы запросить их все сразу (типичный кейс ORM), а потом забыли об этом. Потом приложение выросло, запросы сами по себе легковесные и быстрые, но создают при этом большую нагрузку на систему, причину которой сложно отследить. Вот так я понимаю короткоживущие быстрые запросы, надеюсь, что правильно.

Я к тому, что для OLTP эта ситуация типична. Как так получилось, что в PostgreSQL до сих пор нет встроенных средств семплирования, которые позволят хоть как-то отловить подобные запросы? Либо все-таки подобные запросы не настолько сильно вредят Postgres, и поэтому семплирование неприоритетно. То есть блокировки, которые Вы предложили отловить семплированием — тоже редкий случай, не стоящий того, чтобы инструменты семплирования размещать в ядро.

Либо принципиально такие инструменты как семплирование — это не задача PostgreSQL и их некорректно встраивать. Было бы интересно услышать Ваше мнение по этому вопросу.
Немного смешалось 2 кейса, поэтому описание получилось расплывчатым. Изначально я забыл (или тогда еще не знал) о существовании частичного индекса и сделал с Redlock.

Затем появился кейс запрета параллельных действий пользователя. Приведенным Вами описанием выше это тоже можно решить, при условии, что каждое действие пишется в таблицу событий. А это так и есть. Спасибо. Кстати, еще один аргумент в пользу необходимости в проекте такой вот таблицы событий. Для rate limiters можно очень успешно использовать без необходимости REDIS
Я понял, спасибо. Принцип в том, чтобы упорядоченность соблюдалась в рамках страницы, а не в порядке следования строк в этой странице
Да, все верно.

Изначально была сделана просто вставка событий id + JSONB. Среди этих событий было голосование за публикации. Конечно, в итоге была сделана отдельная таблица для событий голосования с подходящим уникальным индексом. Но до этого, в качестве быстрого решения была сделана распределенная блокировка на REDIS. Она впоследствии осталась для соблюдения условия «пользователь может сделать только одно социальное действие (голосование либо публикацию комментария и т.п.)», чтобы усложнить написания злоумышленниками скрипта, который будет от лица пользователя делать множество запросов (лайков, например) параллельно.

А можно было бы сделать это на рекомендательных блокировках? Например заблокировать id пользователя (или хеш от него)
Рекомендательные блокировки

В приведенном примере блокировка действует до конца сеанса, а не транзакции, как обычно.


У меня был случай, когда в силу недостатков архитектуры нельзя было явно поставить уникальность на вставку нового значения. Чтобы защититься от дубликатов, я реализовал REDIS lock. Перед тем как осуществить действие, происходит попытка захвата блокировки действия. После успешного завершения действия (или ошибки) — блокировка снимается. Или же она снимается по таймауту.

Получается, что вместо этого можно было бы использовать рекомендательные блокировки? Заблокировать условный ресурс и быть уверенным, что другой сеанс будет ждать освобождения ресурса? Разве что кажется, что есть недостаток. Если сеанс внезапно завершается (обрывается соединение по какой то причине, если я понимаю правильно, что такое сеанс) — то блокировка мгновенно опускается. С другой стороны, раз блокировки нет, то действие откатилось и вроде все хорошо.

Однако как быть, если сеанс зависнет и блокировка тоже застрянет? Тут тогда таймаут снятия блокировки будет равен таймауту завершения сеанса?
Егор, читаю (по 2 раза минимум, для глубокого понимания) все ваши статьи, но к сожалению, времени стало намного меньше, поэтому перестал задавать вопросы. Но по этой статье все таки задам :)

Раньше таблицы расширялись только на одну страницу за раз. Это вызывало проблемы при одновременной вставке строк несколькими процессами, поэтому в версии PostgreSQL 9.6 сделали так, чтобы к таблицам добавлялось сразу несколько страниц (пропорционально числу ожидающих блокировку процессов, но не более 512).


Интересен тогда кейс append-only:
* Пусть у нас идет интенсивная вставка значений в таблицу, допустим 1000 строк в секунду.
* Пусть так получилось, что это не batch — именно 1000 в секунду, причем это делают разные процессы. Извиняюсь за натянутость кейса, он специально преувеличен, чтобы понять суть.
* Пусть строки достаточно «тяжелые».

То есть вероятен описанный выше кейс, когда разные процессы начнут расширять таблицу на новую страницу. Пусть мы достигли предела и расширились на 512 страниц за раз из разных процессов.
А потом продолжили вставку новых значений (она не останавливалась).

Значит ли это все, что строки будут лежать в файлах сильно фрагментировано? То есть мы ожидаем append-only и вставку «строка за строкой, id за id». А из за такого вот расширения упорядоченность строк будет сильно нарушена. Что, в частности, помешает эффективному использованию индекса BRIN
А в чем было дело? Очень интересно, может быть какой-то интересный кейс тюнинга?
Если я все правильно понял, контрольная точка не пишет на максимальной скорости в том числе и потому, чтобы не расходовать железные ресурсы. При необходимости ускорения железные ресурсы начнут больше расходоваться и можно «неожиданно» получить деградацию производительности. То есть цена ускорения это всегда потенциальная деградация?

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

А если страница вытесняется на диск с целью заменить ее на новую с диска (все страницы буффера заняты, требуется вытеснение)? В этом случае «можно читать» вероятно, не работает, потому что содержимое страницы в какой-то момент полностью будет изменено.
Блокируют ли грязную буфферную страницу процессы checkpointer/background writer перед записью ее на диск?
В заключение все нежурналируемые таблицы перезаписываются с помощью образов в init-файлах.


имеются ввиду слои init, которые остались на диске на момент отказа системы? Могут ли они быть повреждены, ведь они могут быть в неконсистентном состоянии (на то и WAL для других операций)
Надо просто продолжить выполнение текущей, но ускориться.


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

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

Правильно ли я понял, что процесс checkpointer это популярный кандидат (один из кандидатов) на оптимизацию, если вдруг система «ни с того ни с сего» начала работать медленнее?
Егор, спасибо за отличную статью. Вопрос, связанный с заморозкой.

В заголовке фрагмента WAL хранится:
номер транзакции, к которой относится запись;

полагаю, что этот номер для чего-то используется, например, при восстановлении.

Но как быть если номер транзакции заморожен, прошло уже много времени и в системе появилась уже другая транзакция, номер которой совпадает с замороженным номером?

Как в целом процесс заморозки дружит с WAL с точки зрения номеров транзакций?
Спасибо, а как происходит освобождение «уже ненужного» буфферного кольца. Буфферы, ранее выделенные на кольцо, полностью очищаются? Ведь насколько я понял трудно будет «убрать кольцо и сделать буфферы кольца доступными для всех», ведь буферы кольца узко специализированы были под конкретный массовый оператор. И врят ли будут полезны другим транзакциям.
Алгоритм clock-sweep перебирает по кругу все буферы (используя указатель на «следующую жертву»), уменьшая на единицу их счетчики обращений.


Вопрос 1 — Правильно ли я понял, что:
* Вытеснение начинается только когда нет свободных буферов, а в буферы надо поднять страницу
* Алгоритм уменьшает счетчики буферам, «которым не повезло оказаться до буфера с нулевым счетчиком». Потому что как только алгоритм находит буфер с нулевым счетчиком — он его вытесняет, а последующие буферы уже не трогает.
* То есть скорость пробегания полного круга существенно зависит от количества «уже нулевых счетчиков» на пути алгоритма.

Размышления:
На первый взгляд кажется, что было бы неплохо иметь хеш таблицу с указателем на буферы с нулевыми счетчиками, чтобы при необходимости вытеснения сначала сразу же вытеснять их и не трогать другие буферы, которым «не посчастливилось оказаться до нулевого буфера».

А алгоритм уменьшения счетчиков сделать отдельным, который лишь уменьшает счетчик и когда счетчик уменьшился до нуля — делает новую запись в хеш таблицу.

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

Вопрос 2 — Правильно ли я понял, что алгоритм вытеснения выполняет 2 задачи сразу
* Уменьшает счетчики
* Вытесняет нулевые буферы

Вопрос 3 — Фоновый процесс записи грязных страниц на диск. Использует ли он как-то информацию о счетчиках обращений?
А насколько переход на directIO способен увеличить производительность? Действительно ли если постгрес возьмет на себя часть функций ОС — это будет хорошим решением? Не идем ли мы таким образом в сторону, когда постгрес — это и есть операционная система, ведь она берет часть ее функций на себя.

И это может значительно замедлить скорость развития самой Постгрес.

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


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

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


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

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

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

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

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

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

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

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


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


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

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

Подскажите, пожалуйста, что происходит в данной ситуации. Можно было бы, конечно, происследовать вопрос самостоятельно, но это непросто, когда нет точки, от которой можно оттолкнуться.

Information

Rating
Does not participate
Registered
Activity