Comments 25
Немного вопросов:
№1
XACT — это аббревиатура. Как ее можно расшифровать, пусть даже и условно?
№2
Поэтому выясненный однажды статус транзакции записывается в биты xmin_committed и xmin_aborted версии строки. Если один из этих битов установлен, то состояние транзакции xmin считается известным и следующей транзакции уже не придется обращаться к XACT.
Что будет происходить при race conditions?
Несколько транзакций параллельно (в рамках разных соединений) пытаются посмотреть эти биты, не найдя их — идут в XACT. И далее одновременно пишут биты в заголовок. Одна транзакция установит биты, вторая — установит те же самые значения. И все.
Видимо, никаких проблем тут не будет кроме лишнего похода в XACT, что не является думаю критичным.
Поправьте, пожалуйста, если что-то не так понял.
№3
XACT — не таблица системного каталога; это файлы в каталоге PGDATA/pg_xact.
А работа с этими файлами ведется постранично, как и со всеми другими.
Используется ли буферный кеш для XACТ, как и для таблиц? Или работаем как с обычными файлами, средствами ОС (файловый кеш в RAM на уровне ОС). Если это так, то то интересно, почему так решили? Насколько я успел привыкнуть — в PostgreSQL стараются всю информацию представлять в едином, табличном виде (способе хранения и работы с данными)
№4
Например, для B-дерева строки, относящиеся к листовым страницам, содержат значение ключа индексирования и ссылку (ctid) на соответствующую строку таблицы. В общем случае индекс может быть устроен совсем другим образом.
А далее:
Можно считать, что ссылки из индекса ведут на все табличные версии строк — так что разобраться, какую из версий увидит транзакций, можно только заглянув в таблицу.
Поясните, пожалуйста, для полноты картины. Получается, что одному значению ctid соответствует несколько записей? Хотя в указанных примерах это вроде бы не так. По какому свойству строки индекс находит все возможные версии? Видимо, это приватный ключ — ID.
Или же — при создании новой версии строки происходит какое-то изменение индекса чтобы он знал о новой версии?
№5
И немного не в тему статьи — индекс перестраивается в рамках транзакции но сразу после COMMIT? То есть в рамках снимка индекс, условно говоря, может быть «устаревшим»?
№6
Когда создается новая версия строки при UPDATE — создается полная копия строки в базе данных? Соответственно, если есть строка с большим значением внутри TEXT, то UPDATE создаст его копию даже если изменилось другое поле — например, у поста счетчик number_of_upvotes увеличился на единицу, но сам текст поста никто не изменял.
Поэтому частые UPDATE могут существенно влиять на размер таблицы?
Спасибо
Пожалуй, отвечу на каждый из вопросов отдельно.
1.
XACT — не аббревиатура, а сокращение, образованное по непонятным мне правилам. Дело в том, что в исходных кодах PostgreSQL транзакция часто обозначается буквой «икс». Отсюда всякие xmin, xmax, xid и пр. И «xact» обозначает ровно то же (в «act» угадывается часть слова trans-act-ion). Отсюда multixact — мультитранзакция (про этого зверя я планирую написать, когда дойду до блокировок). В общем, расшифровка такая: нечто, имеющее отношение к транзакциям.
У меня такое же понимание. Исключительная блокировка на строку при проверке видимости не накладывается, так что по идее возможна ситуация, когда обе транзакции обновят те же биты.
«Лишние» походы в XACT случаются (и не только по этой причине), и в этом действительно ничего страшного нет.
Да, кеш там конечно же есть, но отдельный. XACT — довольно специфическая штука, ее под таблицу сложно замаскировать.
Пока мы для простоты считаем, что каждой табличной версии строки в индексе (B-дереве) соответствует своя строка, которая ссылается на эту версию. (И если появляется новая версия строки, она тоже добавляется в индекс.)
Когда мы обращаемся к индексу (дай мне строку, где id = 1), мы получаем ссылки на все версии строки с id = 1. И дальше проверяем по таблице, какую из этих версий нам действительно можно увидеть.
Но это упрощенная картина. Есть карта видимости, есть HOT-цепочки… Это мы все рассмотрим, но позже, чтобы не погрязнуть в деталях.
Индекс действительно может распухнуть, но чтобы "на порядок" — это что-то странное. Возможно, в том случае были ещё какие-то обстоятельства.
В общем, про это тоже будет.
Вообще, всегда при массовых изменениях в таблице (будь то UPDATE, INSERT или DELETE) рекомендую делать:
VACUUM ANALYZE
для актуализации статистики, иначе у планировщика будут устаревшие данные и он может выбирать странные и не эффективные (для новых данных) планы запросов.- Пересоздание всех индексов без блокировки:
CREATE INDEX CONCURRENTLY "new"
+DROP INDEX "old"
+ALTER INDEX "new" RENAME TO "old"
.
Причем обе процедуры можно смело делать "на бою" без простоя.
У меня был случай, когда после массовой операции (вычищение устаревших данных в таблице) только на пересоздании индексов удалось выиграть 100 Гб места на диске.
Всегда и никогда — нехорошие слова. Лучше, когда действия осознанные.
Пересоздание всех индексов без блокировки: CREATE INDEX CONCURRENTLY «new» + DROP INDEX «old» + ALTER INDEX «new» RENAME TO «old».
* После подобной процедуры необходимо также запускать проверку целостности индекса, т.к. индекс в CONCURRENTLY режиме может получиться «битый»
* Не все индексы, думаю, нужно пересоздавать, а только те, которые затронуты были массовыми изменениями
Причем обе процедуры можно смело делать «на бою» без простоя.
Эти процедуры могут расходовать ресурсы и таким образом влиять на производительность БД на продакшене. Думаю, выполнять их нужно с осторожностью.
У меня был случай, когда после массовой операции (вычищение устаревших данных в таблице) только на пересоздании индексов удалось выиграть 100 Гб места на диске.
Если есть возможность — опишите свой кейс более подробно, будет интересно.
Индекс перестраивается сразу же по ходу транзакции, не дожидаясь фиксации. Какой смысл откладывать?
Это мы возвращаемся к TOAST-у?
Если длинное значение лежит в toast-таблице, а изменилось только поле в основной таблице, то только в основной таблице и появится новая версия строки. И она будет ссылаться на ту же самую строку в toast-таблице. Иными словами, длинное значение в этом случае не дублируется.
* Делаем массовый апдейт description поля типа TEXT, например вычищают XSS injections (или решили ругательные слова заменить точками постфактум, как бывает в стартапах)
* Сразу получаем минимум 2х к размеру TOAST-таблицы.
* И это место видимо без FULL VACUUM оказывается очень тяжело высвободить для ОС (и для других таблиц). Даже если обновлять пачками.
Пример несколько надуманный, так ошибиться сложно, если знать требования по безопасности. Просто для примера.
Очень хотелось бы прочитать про эту проблему в будущем, потому что я часто слышал кейсы, когда они что-то обновляли массово а потом ломался индекс или место не могли высвободить. И пришлось просто все блокировать и запускать VACUUM FULL. может еще какие то решения есть
INSERT INTO t VALUES ('BAR');
столбец не указанВ результате заголовок получается довольно большой — минимум 23 байта на каждую версию строки, а обычно больше из-за битовой карты NULL-ов. Если таблица «узкая» (то есть содержит мало столбцов), накладные расходы могут занимать больше, чем полезная информация.
Так есть какие-то издержки производительности при операциях вставки (и обновления тоже) в случае если поля допускают NULL значения?
Я-то здесь имел в виду не производительность, а расход места на диске.
Если говорить про производительность, то (несколько я себе представляю) основные потери в этом месте связаны с тем, что прочитанную версию строки приходится «разбирать» на отдельные поля. Если бы все поля имели постоянное смещение относительно начала версии строки, все было бы просто и быстро, но у нас есть поля переменной длины (такие как number, varchar и т. п.) и поля, допускающие NULL (потенциально нулевой длины). Поэтому, чтобы добраться до какого-то поля, приходится пробежать и по всем предыдущим.
Если заниматься микрооптимизацией, то выгодно поля без NULL и фиксированной длины ставить первыми. Тогда хотя бы для этих полей смещения кешируются и доступ идёт чуть быстрее. Но, ещё раз повторюсь, это всё гомеопатия.
У меня как-то был спор по поводу испльзования NULL. Меня пытались убедить с пеной у рта как это плохо (или даже опасно) использовать NULL, но при это абсолютно не могли обосновать свое мнение. Речь шла не о Postgres, а MySQL и даже чуть позже мне прислали ссылку на доку с оффсайта, что не следует использовать NULL значения, если это возможно, но при этом абсолютно не давалось никакого объяснения на вопрос «Почему» )
Рад, что читаете!
Я как раз стараюсь показать, как самому во всем убедиться, благо в Постгресе много «интроспективных» возможностей.
А насчёт NULL — ну да, у него есть особенности, о которых надо помнить, и он делает SQL менее стройным и логичным. Об это теоретики много копий сломали (например). Но на практике без NULL было бы плохо, и все равно он уже есть. Так что не вижу причин его безудержно избегать.
MVCC-3. Версии строк