Lefthook: git hooks m anager AnyCable: Polyglot replacem ent for ActionCable server PostCS S : A tool for transform ing CS S with JavaS cript Im gproxy: Fast and secure standalone server for resizing and converting rem ote im ages Logux: Client-server com m unication fram ework based on Optim istic UI, CRDT, and log Overm ind: Process m anager for Procfile-based applications and tm ux И многие другие на evilmartians.com/oss
приложений Традиционные СУБД плохо масштабируются горизонтально. Так сложилось, что разработчики на Ruby on Rails разрабатывают фичи целиком (включая модель данных) И не всегда есть DBA под рукой Полезно понимать, как оно работает и что может, чтобы не положить продакшен раньше времени. А главное — нафига оно всё Ruby-разработчику?
влияет на результат выполнения запросов*, но может ускорить его выполнение. Ещё индексы поддерживают ограничения целостности (первичные и уникальные ключи). * Наличие индекса может поменять порядок возвращаемых данных, если в запросе не указан ORDER BY, а планировщик решил использовать индекс.
замедление вставок и обновлений write amplification полезны не всегда планировщик может не захотеть их использовать новый индекс может оказаться медленнее старого
и она не будет расти Или если запрос выполняется редко и скорость его выполнения не критична Если большинство строк отбрасываются другими условиями (мультитенантные таблицы) * Локально приходится использовать SET enable_seqscan = off; , чтобы заставить СУБД использовать индекс. ` `
от похода в индекс 1. Когда надо выбрать небольшое количество данных от размера таблицы 2. Результат нужно отсортировать Когда индекс ещё помещается в оперативную память, а таблица — уже нет Когда ожидается, что количество данных в таблице будет расти Или это внешний ключ, по которому будут выполняться JOINы
А как СУБД это понимает? Статистика! СУБД знает о каждой колонке: количество данных их упорядоченность (корреляция) количество разных значений их количественное распределение (гистограмма) И использует это при планировании запросов. Статистика безумно важна!
табличку на диске, страница за страницей index only scan - достать все данные только из индекса index scan - сходить в индекс, сходить в страницы на диске bitmap index scan + heap scan — сходить в индекс, построить битовую карту интересных страниц, загрузить их все, отфильтровать… Как выбирает? Планировщик перебирает варианты выполнения считает примерную стоимость каждого (с учётом статистики) выбирает самый дешёвый (как ему кажется)
лежат на диске, идём на диск, повторяем. Логарифмическое время, чтобы найти данные в индексе, линейное — чтобы считать. В случае стандартного BTree-индекса результат будет уже отсортирован.
планировщика по умолчанию заточены под работу на HDD: seq_page_cost (floating point) Sets the planner’s estimate of the cost of a disk page fetch that is part of a series of sequential fetches. The default is 1.0. random_page_cost (floating point) Sets the planner’s estimate of the cost of a non-sequentially-fetched disk page. The default is 4.0. Это значит, что планировщик будет чаще выбирать seq scan при живом-то индексе! Используйте PgTune! ` ` ` ` pgtune.leopar d.in.ua
по одному разу. Источник: Postgres Professional: курс «Оптимизация запросов», тема №5 «Сканирование по битовой карте». Подробнее: Индексы в PostgreSQL — 4
Порядок столбцов важен! CREATE INDEX ON users(account_id, age) SELECT * FROM users WHERE account_id = 1 AND age < 30 SELECT * FROM users WHERE account_id = 1 CREATE INDEX ON users(account_id) -- не нужен! CREATE INDEX ON users(age) -- нужен!
пришлось ходить в таблицу, а всё сразу достать из индекса* PostgreSQL 11+ добавил поддержку INCLUDE , которая позволяет добавлять доп. данные в уникальные индексы: Особенно полезно включать первичный ключ, что позволяет ускорять множественные JOIN’ы. В ActiveRecord фичу не добавили, а вот отдельный гем уже есть: activerecord-covering-index . ` ` CREATE UNIQUE INDEX index_fk_on_listings ON listings (`product_id`, site_id) INCLUDE (`id`); SELECT * FROM products JOIN listings ON products.id = `listings.product_id` JOIN listing_variations ON `listings.id` = listing_variations.listing_id ` `
с условием WHERE item_id IS NOT NULL (в таблице заполнено примерно 10% строк): И… 🥁 class Listing < ActiveRecord::Base scope :published, -> { where.not(item_id: nil) } end ` ` CREATE INDEX index_listings_on_item_id ON listings (item_id); CREATE INDEX index_listings_on_item_id_not_null ON listings (item_id) WHERE item_id IS NOT NULL;
в SELECT, WHERE или ORDER BY позволяют как бы «закэшировать» производное значение есть своя отдельная статистика «дорогое» обслуживание можно использовать самописные функции (они должны быть IMMUTABLE ) CREATE UNIQUE INDEX username_constraints ON users (`LOWER(username)`); ` `
проиндексированным колонкам, то планировщик может построить несколько битовых карт и логически их объединить. habr.com/ru/company/postgrespro/blog/326096 CREATE INDEX on t(a); CREATE INDEX on t(b); ANALYZE t; EXPLAIN SELECT * FROM t WHERE a <= 100 AND b = 'a'; Recheck Cond: ((a <= 100) AND (b = 'a'::text)) -> BitmapAnd -> Bitmap Index Scan on t_a_idx Index Cond: (a <= 100) -> Bitmap Index Scan on t_b_idx Index Cond: (b = 'a'::text) habr .com/r u/company/po…
, >= , > , IN , BETWEEN , IS NULL , IS NOT NULL , LIKE 'smth%' , ~ '^smth' Бонус: ускоряет ORDER BY , поддерживает уникальность Для первичных ключей лучше работает в случае последовательных id и хуже — в случае случайных UUID. habr.com/ru/company/postgrespro/blog/330544 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` habr .com/r u/company/postgr espr o/b…
но быстрее только на очень больших таблицах и на выборках единичных значений. Не поддерживает уникальность :-( Осторожно! В бою можно использовать только начиная с PostgreSQL 10 (но это самая старая версия из поддерживаемых) Подробнее: habr.com/ru/company/postgrespro/blog/328280 ` `
и операторов (B-trees, R-trees) Геометрически данные (box, circle, point, polygon), полнотекстовый поиск (tsquery, tsvector) Не включает в себя данные, но включает в себя предикат, которому данные удовлетворяют Operations: << , >> , ~= , <^ , >^ , <-> , <@ , @> Для поддержки = , < , > нужно включать расширение btree_gist Поддерживает exclusion constraint (как unique, но на интервалах) habr.com/ru/company/postgrespro/blog/333878/ ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` habr .com/r u/company/postgr espr o/b…
деревья и прочие естественно непересекающиеся штуки Может быть значительно компактнее BTree для префиксного поиска Внутри себя хранит метки и префиксы, по которым идёт поиск PG 9.2+ Подробнее: habr.com/ru/company/postgrespro/blog/337502 habr .com/r u/company/postgr espr o/b…
MIN и MAX значений колонки в каждой странице начиная с PG 14 хранит несколько интервалов Очень компактный и дешёвый в обслуживании PG 9.5+ Подробнее: habr.com/ru/company/postgrespro/blog/346460 habr .com/r u/company/postgr espr o/b…
с нестабильной производительностью Индекс даёт false positives и лишние чтения с диска Поддерживается только = , но можно искать по многим колонкам Больше, чем BRIN, но компактнее чем Hash Компромиссное решение для очень больших таблиц Нужно подбирать параметры PG 9.6+ Подробнее: habr.com/ru/company/postgrespro/blog/349224 ` ` habr .com/r u/company/postgr espr o/b…
например, расстояние между словами Можно считать релевантность прямо в индексе и делать фразовый поиск (когда порядок слов важен) Нет в стандартной поставке — нужно ставить отдельно. PG 9.6+ Исходники: github.com/postgrespro/rum Подробнее: https://habr.com/ru/company/postgrespro/blog/343488 habr .com/r u/company/postgr espr o/b…
zombodb; CREATE INDEX idxproducts ON products USING zombodb ((products.*)) WITH (url='localhost:9200/'); SELECT * FROM products WHERE products ==> '(keywords:sports keywords:OR keywords:box OR long_description:"wooden away"~5) AN github.com/zombodb/zombodb
Для JSONB/hstore/arrays/полнотекстового поиска GiST — Для геометрии/географии/exclusion constraints/полнотекстового поиска SP-GiST — Для геометрии/географии/exclusion constraints/префиксного поиска Hash — Для = , но стоит ли? BRIN — для больших естественно-отсортированных (write-only) таблиц Bloom — для поиска по многим колонкам в огромных таблицах RUM — для фразового полнотекстового поиска ` `
PG блокирует таблицу на запись целиком. Запросы, которые хотят записать в эту же таблицу встают в очередь за блокировкой… Пока все пулы соединений не исчерпаются. Вуаля! Мы лежим! 🤡 Транзакционный DDL — благо, но блокировки отпускаются только в конце транзакции.
конкурентно. disable_ddl_transaction! add_index :big_table, %i[column], algorithm: :concurrently class AddMissingIndexes < ActiveRecord::Migration[6.1] def change end end
берёт блокировку всей таблицы, чтобы её тут же отпустить, но если в таблицу активно пишут другие долгие транзакции, то следом за ждущим блокировки CREATE INDEX скопятся очередь из других запросов и… 💥 Хак решение Снимайте нагрузку перед накаткой любых миграций на нагруженные таблицы — останавливайте Sidekiq, … И избегайте долгих транзакций! Гем isolator вам в помощь! ` ` ` ` gem isolator
миграции и подскажет, когда они могут быть опасны на продакшене. github.com/ankane/strong_migrations gem "strong_migrations" === Dangerous operation detected #strong_migrations === Adding an index non-concurrently blocks writes. Instead, use: class AddMissingIndexes < ActiveRecord::Migration[6.1] disable_ddl_transaction! def change add_index :table, :column, algorithm: :concurrently end end gem str ong_migr ations
command: bundle exec rails db:create db:migrate db-linters: steps: - attach_workspace: at: . - run: name: Backup db/structure.sql to compare for changes later command: cp -f db/structure.sql{,.bak} - run: name: Check that migrations are not broken - run: name: Check that db/structure.sql is up to date (there should be no changes) command: git diff --exit-code --no-index -- db/structure.sql{.bak,} - run: name: Ensure seeds are valid command: bundle exec rails db:seed
Хорошая новость: add_reference в миграциях уже их добавляет Для таблиц, используемых в JOIN’ах «посередине», добавляйте первичный ключ в индекс для index only scan. 2. Делайте частичные индексы, если большинство строк в таблице ожидаются пустыми. 3. Не добавляйте индексы для OLAP-запросов (построение отчётов по всем данных и т.п.) 4. Удаляйте дублирующие и неиспользуемые индексы * * wiki.postgresql.org/wiki/Index_Maintenance ` ` PG Index Maintenance
the Index, Luke: use-the-index-luke.com 3. Postgres Professional: курс «Оптимизация запросов» postgrespro.ru/education/courses/QPT 4. Цикл статей «Индексы в PostgreSQL» habr.com/ru/company/postgrespro/blog/326096 1. The Ar t of Postgr eSQL 2. Use the Index, Luke 3. Оптим изация запросов 4. Ин дексы в Postgr eSQL
вас насильно впихнуть под давлением на наших фирменных курсах Brainwashing. В последний раз я рассказывал про базы данных 4 (четыре) часа подряд. Проведём весной, если эпидемиологическая ситуация позволит 🤞 Записывайтесь в добровольцы на brainwashing.pro/rails. br ainwashing.pr o/r ails