Pull to refresh

Comments 14

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

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


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

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

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

Владимир, добрый день!


Если строки добавляются в таблицу многими процессами параллельно, то "строка за строкой" все равно не получится, независимо от того, на сколько страниц за раз расширяется таблица. Все равно строки будут как-то произвольно перемешаны.
Но BRIN-у это не должно мешать, потому что локальность все равно будет соблюдаться. Ведь даже если в пределах страницы строки перемешаны, все id страницы P окажутся меньше любого id страницы Q, если Q немного отстоит от P.


и расширились на 512 страниц за раз из разных процессов

Только не из разных, а из одного. Для того и блокировка.

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

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


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

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

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

Я правильно понял, что смысл этой блокировки был в том, чтобы сериализовать вставку, то есть чтобы никто не вставлял новые значения, пока идет проверка уникальности? А как была устроена проверка? И что мешало просто объявить уникальность в базе?

Да, все верно.

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

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

Если так, то да, конечно можно. Но как вы проверяете отсутствие дубликата без подходящего индекса? А если есть такой индекс, то почему не объявить его уникальным?

test=# CREATE TABLE events(id serial, content jsonb);
test=# CREATE UNIQUE INDEX ON events( (content->>'referenced_id') )
  WHERE content->>'type' = 'vote';
test=# INSERT INTO events(content) VALUES (
  '{ "type": "vote", "score": 1, "referenced_id": 12345 }'::jsonb
);
test=# INSERT INTO events(content) VALUES (
  '{ "type": "comment", "text": "I like it!", "referenced_id": 12345 }'::jsonb
);
test=# INSERT INTO events(content) VALUES (
  '{ "type": "vote", "score": -1, "referenced_id": 12345 }'::jsonb
);
ERROR:  duplicate key value violates unique constraint "events_expr_idx"
DETAIL:  Key ((content ->> 'referenced_id'::text))=(12345) already exists.
Немного смешалось 2 кейса, поэтому описание получилось расплывчатым. Изначально я забыл (или тогда еще не знал) о существовании частичного индекса и сделал с Redlock.

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

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

На мой взгляд не хватает конкретных примеров использования рекомендательных блокировок.
Это вообще класс явных блокировок, с которым у меня возникли наибольшие сложности в понимании.
Т.е. даже после прочтения документации все равно неясно, в каких случаях разработчику приложения может понадобится такой тип блокировок

Я бы так сказал: про рекомендательные блокировки надо вспомнить, когда потребуется сделать что-то такое, что на обычные блокировки ложится плохо. Не всегда может найтись подходящая таблица.

И еще - рекомендательные блокировки работают быстрее блокировок строк, поскольку никак не меняют страницы данных. В каких-то случаях это может оказаться важным для быстродействия.

Понятней не стало, но на всякий случай буду знать, что такие блокировки есть)

Sign up to leave a comment.