Pull to refresh

Comments 55

Вспомнил один хороший рецепт для PG.
Как известно, для регистронезависимого поиска в PG есть ILIKE. Ещё известно, что он значительно медленнее, чем просто LIKE.

Я в своё время нашёл вот такой рецепт:
Очевидная часть рецепта: Поле по которому осуществляется поиск надо обязательно проиндектировать.
Не очень очевидная часть рецепта:
1) В индексе установить operator class = text_pattern_ops
2) Сделать индекс функциональным LOWER/UPPER(text)
3) Осуществлять поиск при помощи LIKE LOWER/UPPER(text)

У нас при работе с КЛАДРом скорость выборки выросла раз в 10, по сравнению с голым ILIKE и обычным индексом с настройками по-дефолту.
Интересно. Если будет время, попробую провести и опубликовать тесты по вашему рецепту и тому, что сам написал. С тестами то оно нагляднее будет.
Ещё иногда спасает изменение «поведения функции» (не знаю как это точно называется) IMMUTABLE | STABLE | VOLATILE. По умолчанию все функции VOLATILE, что является самым надёжных/безопасным поведением с точки зрения отстреливания себе конечностей. Но в циклах VOLATILE очень медленный.

Как правило, если функция не изменяет данные, а только читает их и не выполняется в циклах вместе с попутным изменением данных, то можно смело ставить IMMUTABLE и функция будет выполнятся только один раз при первом обращении в цикле, далее значение «закэшируется» и повторный вызов функции вернёт значение из «кэша». Тоже позволяло в некоторых случаях уменьшить время выполнения запросов.

P.S.: А у меня появилось желание пощупать «fill factor» =)
С fill factor может получиться интересно, но там результат вроде как сильно зависит от характера обновлений/удаления записей. Если обновлять записи большими пачками, то никакого fill factor'а не хватит ). Аналогично, если запись идёт в множество потоков, может потребоваться ставить значение и пониже, до 80 — 70. С другой стороны, размер БД гарантированно возрастает и стоит ли овчинка выделки — с каждой таблицей нужно смотреть, как обычно, отдельно. В общем, всё туда же — надо тесты погонять )
fill_factor работает только если размер «запаса» превышает размер измененой части таблицы между двумя итерациями автовакума для нее.

Даже немного не так… В «правильной ситуации» размер update между автовакумами должен быть некой постоянной величиной… Например у вас между автовакумами обновляется обычно 10% таблицы. Т.е. в нормальном состоянии у вас 10% файла — дырки в которые нельзя писать, просто потому что FSM про них не знает. Поэтому писаться новые будут — увеличивая файл. fill_factor позволяет создать некий запас в который будут помещаться записи, пока FSM прочухает.

Немного не так. IMMUTABLE можно ставить если значение зависит только от аргумента, а не от данных бд. Она сама не будет выполнять никаких запросов в БД. Она выполняется вообще один раз как только вычислен параметр а если он не зависит от данных БД, то вообще посчитается на этапе планирования запроса.

STABLE — значение зависит от данных в БД, но при последовательных проходах по одним и тем же данным будет возвращать один результат. Соответственно ее можно закешировать, и сделать что то вроде временной таблички.
Память меня подвела) спасибо, что поправили.
Только есть один момент, если в IMMUTABLE функцию поместить SELECT, то он же сделает выборку из таблицы, разве не так? Т.е. не обязательно, чтобы функция не работала с данными БД вообще.
По поводу первого пункта «Большие размеры таблиц и индексов» и постгресовской реализации MVCC.

Перечисленые вами пункты «для смягчения проблемы» имо очень незначительно смягчают ее и эти рекомендации скорее касаются «администраторов» БД, а не разработчиков.
В целом MVCC придуман, чтобы мы не заморачивались с блокировками, dirty read и т.д. Оно очень облегчает жизнь, но имеет кучу недостатков, включая описаную вами проблему с ростом размера таблицы и постоянным VACUUM, который кстати тоже не делает все это хозяйство быстрее (т.к. VACUUM — операция блокирующая, даже хуже read-write-блокирующая).

Я это к тому, что как-только разработчик получает от MVCC больше проблем, чем приимуществ (в том числе и падающая performance) ему стоит внимательнейшим образом почитать следующую главу в документации:
PostgreSQL: Documentation: Explicit Locking.
Просто настоятельно рекомендую. Это конечно не MVCC и нужно понимать что делаешь (например можно огрести неслабые deadlock), но при правильном применении, это не просто «смягчает» проблему с реально большими данными — это иногда позволяет просто в разы ускорить некоторые операции.
Особенно интересно и с точки зрения performance наиболее продуктивно использование «advisory locks».
VACUUM — операция блокирующая, даже хуже read-write-блокирующая

Да что вы:
Plain VACUUM (without FULL) simply reclaims space and makes it available for re-use. This form of the command can operate in parallel with normal reading and writing of the table, as an exclusive lock is not obtained.
Те записи, которые перетаскиваются, имеют row lock. Что к сожалению при использовании индексов, те которые влияют на join или sort выборку, т.е. даже напрямую не затронутые vacuum, но стоящие после них, тоже приводит к блокировкам.
Приведенная вами цитата имеет ввиду, что не осуществляется table lock.
VACUUM не перетаскивает ничего, он удаленные строки (которые уже гарантированно никому не нужны) окончательно удаляет
Ну да, поэтому после vacuum (не FULL) иногда размер таблицы в несколько раз меньше…
Индексы, особенно GINы, перестраиваются полностью, кластеры удаляются иногда целыми кусками.
Про FULL я вообще молчу…
Нет. Вы не правы.

Обычный вакум не перетаскивает строки. Обычный вакуум может только «подрезать» файл таблицы с конца если все записи в конце файла помеченны как удаленные. Особенно это начинает работать если fill_factor не 100% и все новые записи у вас «месятся» в начале файла. Старые оказываются в конце файла. Индексы как раз не перестраиваются и в этом преимущество обычного вакуума над FULL. У вас в индексе по прежнему все записи показывают на теже смещения в исходном файле таблицы. А вот когда работает FULL то он перемещает строки и индексы «распухают». Поэтому после full стоит делать REINDEX.

Вакуум обычный иногда требует краткосрочный TABLE LOCK или ROW LOCK для того чтобы поменять метаинформацию по файлу таблицы.

Вообщем про это достаточно подробно рассказывал Брюс Момджан на HL++ 2013 www.highload.ru/2012/abstracts/410.html про вакуум начинается с 44 слайда.
Особенно это начинает работать если fill_factor не 100% и все новые записи у вас «месятся» в начале файла.

Именно в начале файла или в свободной зоне, определяемой значением fill factor?
скорее в «начале файла» в том смысле что чем ближе к началу тем лучше. Т.е. когда у вас запись апдейтиться лежавшая в конце файла то ее новая версия ляжет ближе к началу. Очевидно что чем больше «свободное место fill_factor» тем больше шансов что оно окажется ближе к началу файла.

Почему работает «уже 90%» потому что обновляется менее чем 10% базы данных при апдейтах за интервал между автовакуумами. Если у вас апдейты будут менять сразу полтаблицы то конечно от филфактора будет немного проку.
Большая база бекапится у нас около часа. Практически всё это время проект бездействует. Тут, откровенно говоря, посоветовать ничего не могу. Наоборот, буду рад выслушать, кто и как с ситуацией справляется. Единственная мысль — бить на шарды и бекапить кусками, думая, как при этом сохранить консистентность данных.

А почему бездействует? Простой бекап на уровне файловой системы не требует остановки базы:
Perform the backup, using any convenient file-system-backup tool such as tar or cpio (not pg_dump or pg_dumpall). It is neither necessary nor desirable to stop normal operation of the database while you do this.

www.postgresql.org/docs/9.2/static/continuous-archiving.html#BACKUP-BASE-BACKUP
Цитата из PostgreSQL: Documentation: File System Level Backup:
There are two restrictions, however, which make this method impractical, or at least inferior to the pg_dump method:
  • The database server must be shut down in order to get a usable backup. Half-way measures such as disallowing all connections will not work (in part because tar and similar tools do not take an atomic snapshot of the state of the file system, but also because of internal buffering within the server). Information about stopping the server can be found in Section 17.5. Needless to say, you also need to shut down the server before restoring the data.
  • If you have dug into the details of the file system layout of the database, you might be tempted to try to back up or restore only certain individual tables or databases from their respective files or directories. This will not work because the information contained in these files is not usable without the commit log files, pg_clog/*, which contain the commit status of all transactions. A table file is only usable with this information. Of course it is also impossible to restore only a table and the associated pg_clog data because that would render all other tables in the database cluster useless. So file system backups only work for complete backup and restoration of an entire database cluster.

А вообще, видел уже бэкапы, сделаные как вы написали, которые потом никакими средствами не хотели больше заводится.
Ну вообще делать бекап на уровне файловой системы — моветон. Хотя репликация в постгрессе, учитывая исторические корни тоже получается моветоном… Но обычный pgdump ничего не лочит и транзакционен. правда в случае чего вы потеряете все данные начиная с момента начала дампа.
В нашем случае он может и не лочит, но обеспечивает такую нагрузку на БД, что состояние практически эквивалентно простою проекта, хоть какие-то операции может быть и проходят :)
правда в случае чего вы потеряете все данные начиная с момента начала дампа.

Поясните, пожалуйста, этот момент. Работает БД, начинаем делать бекап. Бекап обламывается (хех, место на диске закончилось, было). Получаем битые бекап (можно сделать снова) и целую нормальную базу. Или я что-то не понял, или потерь нет.
Речь идет о том, что в бекап попадут только данные, которые были в базе на момент старта дампа.
А, ну это понятно. Я бы не стал называть это потерями :)
правильный бекап это инкрементальный. Типо пока дампим всю базу… потом дампим дельту что за это время пришла… потом еще чуть чуть… и практически к у нас получается что к окончанию бекапа мы видим базу на момент окончания бекапа и начинаем следующий :)
Почему сразу моветон, абсолютно нормальный метод, MVCC тому способствует.
Разумеется, надо не втупую архивировать живую базу, а сначала запустить pg_start_backup('label');

Заодно нагрузку на чтение можно контролировать напрямую, через пайп-тротлинг например.
а сначала запустить pg_start_backup('label');
И на реально больших базах ждать ждать ..., пока он покроет чек-пойнтами базу.
Но работать же не мешает. Подождать, пока закончится pg_start_backup — всяко лучше, чем ложить весь сервер чтобы получить дамп.
А чем вас WAL-то не устраивает?
ну дефакто это работа с внутренним бинарным механизма БД, в опять таки бинарном формате. И где гарантия хоть какой то обратной совместимости? В теории даже минорный апдейт билда сервера может сломать возможность восстановить БД. Это скорее не бекап, а некая реплика + HotStandBy. К сожалению репликация и бекапы у постгресса не настолько «взрослые» как, например, его SQL.
> В теории даже минорный апдейт билда сервера может сломать возможность восстановить БД.

Минорный точно не сломает. Мажорные апдейты ломали совместимость до 9.3 (но гарантии нет, что опять будут менять). Но минорный на то и минорный, что только баг-фиксы (если что то ломается — например баг в тех же Wal-логах — будет писаться большими буквами в релизе)
Лично меня устраивает и pg_dump — база кешируется в ram и дампится быстро, а заархивированный дамп занимает минимум места.
WAL — тоже отличный метод, в частности, можно организовать слейв специально под бекап и снимать с него дамп через pg_dump не беспокоясь о нагрузке (только следить чтобы репликация сильно не отстала).
Поэтому бекапы, как ни крути, нужны. Большая база бекапится у нас около часа. Практически всё это время проект бездействует. Тут, откровенно говоря, посоветовать ничего не могу. Наоборот, буду рад выслушать, кто и как с ситуацией справляется. Единственная мысль — бить на шарды и бекапить кусками, думая, как при этом сохранить консистентность данных.

Используйте непрерывное резервное копирование. Если делать просто бэкапы — есть огромная возможность просто потерять данные за какое то время (ведь бэкапы делаются раз в N дней/недель). Мы использовали WAL-E и Barman. Обе утилиты отлично справляются. Восстанавливать можно до какого то лога или временной метки (если кто то запустил DELETE/TRUNCATE/DROP на весь кластер). Больше можно почитать тут.
Добавлю в копилку, может кому-то пригодится.

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

— таблицы пока что нельзя full vaccum-ить на ходу, но индексы можно. Нужно построить новый такой же (с другим именем, разумеется), и старый после этого удалить. Для неблокирующей постройки индекса нужно использовать опцию CONCURRENTLY.
Мне кажется, однозначно ответить на вопрос, что лучше — разбивать большие апдейты или нет, может ответить только тот, кто знает логику работы конкретного приложения. В моём представлении, транзакции призваны обеспечить целостность записанных данных. Т.е., грубо говоря, если в моём приложении все, скажем, 10'000 записей должны или обновиться, или НЕ обновляться (а промежуточные варианты меня, допустим, не устраивают), я бы пихнул всё в одну транзакцию, хоть оно, типа, и нежелательно. Другое дело, что ситуаций с таким выбором лучше не допускать при продумывании архитектуры )
Само собой, что, если вопрос стоит, что либо все, либо ничего, то надо использовать транзакции, в конце концов — они для этого и придуманы.
Просто в сети гуляют рекомендации, что много последовательных апдейтов стоит заключать в одну транзакцию, для того чтобы зря не нагружать индекс перестроениями. От таких рекомендаций в итоге больше вреда, чем пользы.
Я могу привести контр пример. При большом апдейте помечаются свободными целые страницы. Соответственно фрагментация уменьшается. Тут все спорно. Но опять последовательные апдейты в одной транзакции увеличивают шансы дедлока.
Лично я за апдейты вне транзакций если они логически не связаны. Хотя апдейты зло просто по факту своего наличия в версионнике.
Насчет распухания таблиц при апдейтах в одной транзакции — не уверен. Мне кажется там скорее будет проблема из за длительной транзакции… Вот это реально огромное зло.
Рассматриваю возможность переход на Постгри с Мускула прежде всего из-за функциональных индексов (очень много сортировок по частям поля, по конкатенации, по кастингу и т. п.). Как они, не сильно тормозят на вставку по сравнению с обычными?
Зависит от самого выражения (или функции). Например замеры создания индекса по lower(field) на поле 128 символов, не показали практически никакой разницы, чем то-же самое по такому же полю изначально в lower case. Вероятно просто относительно создания самого индекса временем исполнения lower можно принебречь.
Скажу так — мы не упирались в тормоза функциональных индексов. И я как-то даже не вижу причин, почему они могут быть медленнее. Точных сравнительных тестов показать не могу — не делали.
Сильно зависит от конкретного выражения. Но альтернатива ему — вычисление этого выражения на клиенте перед отправкой на вставку + поле в БД. Так что по суммарному CPU клиент+сервер разница врядли будет, а вот по нагрузке на диск будет точно.
Ибо это увеличение размера записи, следовательно чтение с диска почем зря, вымывание дискового кеша и замедление всех запросов даже для не связанных в этим полем вещей.

Вообще следует помнить что БД в первую очередь упираются в диск, во вторую в память и только потом уже CPU. Как правило современное серверное железо имеет излишек CPU с точки зрения БД.
Альтернатива может быть и типа t1 JOIN t2 ON LOWER(t1.name) = LOWER(t2.name). Ну и при относительно небольшой базе многое зависит от памяти и индексов: можем использовать индексы в запросах — получаем скачок производительности, помещаются часто используемые индексы в память — ещё скачок.
Позвольте, задам вопрос.
Стою перед выбором БД для задачи хранения текущих транзакций. Запросы примерно в таком порядке: insert, 5x update, delete. Select относительно редко и характерен для нештатных ситуаций — в нормальном случае транзакции хранятся в памяти приложений и из базы выгребаются либо на старте, либо при разгребании очереди. Никакой логики в БД (даже вероятно, без использования foreign key), только критически надежное хранение данных, размер записи низкий — до единиц килобайт, в таблице не более 100000 записей. Поток запросов — до 10000 в минуту, относительно низкий. Сам люблю psql, но в данном случае не уверен, что он будет лучшим выбором. nosql как-то для финансовых транзакций рассматриваю с опаской (возможно, стереотипы). Пока предварительно решил использовать mysql.
Что касается postgres, смущает vacuum, насколько помню, он может сильно подтормаживать выполнение текущих запросов в БД. Если я в чем-то заблуждаюсь, прошу меня поправить.
Что касается postgres, смущает vacuum, насколько помню, он может сильно подтормаживать выполнение текущих запросов в БД. Если я в чем-то заблуждаюсь, прошу меня поправить.


Ну если раз в день вручную его делать, то может быть. А так все настраивается достаточно тонко:
www.postgresql.org/docs/9.2/static/runtime-config-resource.html#GUC-VACUUM-COST-DELAY
www.postgresql.org/docs/9.2/static/runtime-config-autovacuum.html
Проблема с бекапами решается регулярными бекапами реплики (слейва).
Когда начнёте щупать 9.3, обратите внимание на новую опцию у pg_dump --jobs:
--jobs=njobs
Run the dump in parallel by dumping njobs tables simultaneously. This option reduces the time of the dump but it also increases the load on the database server. You can only use this option with the directory output format because this is the only output format where multiple processes can write their data at the same time.
Да, обращал. Также видел (сейчас, правда, найти сходу не смог) тесты, показывающие, что некий заметный прирост скорости дампа был только при двух потоках, а при трёх и более — уже несерьезно. Правда там уже, вероятно, всё в винты упирается.
как такие тексты попадают на хабр и воообще в инет!?
хабр, не позорься, нужна модерация!

человек автар, почитай доку и сходи хотя бы на sql ru.

Postgres — субд с более чем 10 летним уже опытом промышленного применения (это я взял от версии 7.4).
Ваши 100ГБ при 10КTPS — да это смех!

Михаил Тюрин
DBA
avito.ru
Извините, Михаил, но я не понимаю зачем вообще такие комментарии тут нужны.
Вы бы лучше по делу написали — посоветовали бы почитать что-то конкретное или сами выдали рекомендации.
Я думаю хабр как раз для этого.
Я безмерно рад за вас, тёзка. И сожалею, что еще не обладаю вашим опытом.
оставлю и я свои 5 копеек… бэкапы начиная с 9.1 можно делать через pg_basebackup это в разы удобнее чем pg_start/stop_backup+rsync. Очень и очень удобная вещь, к примеру выше есть коментарий, так вот есть такой ключ как -c fast и чекпоинт будет делаться asap, независимо от того что выставлено в checkpoint_timeout и checkpoint_completion_target. А в 9.4 уже есть коммит который реализует тротлинг… так что rsync будет не нужен)).
Вы так про версии расказываете, а легко в postgresql переходить с версии на версию (н-р 9.0 -> 9.2)? (чисто из практического интереса у нас 9.2 думаю в будущем обновить)
Да, довольно легко. Но тем не менее, зависит от того что внутри базы. Простой пример двухдневной давности, провел обновление с 8.4 на 9.3 с pg_upgrade. Обновление прошло гладко, а все потому что там не было ничего кастомного (специфические контрибы, репликации в виде slony/londiste, системы очередей типа pgqd). Вот, поэтому перед обновлением нужно обратить внимание на такие вещи и где-нибудь в сторонке развернуть бэкап и проиграть сценарий обновления.
Sign up to leave a comment.

Articles