Скачать CV
Назад к бизнес-кейсам

PostgreSQL

GIN, который не видел запрос листинга

WHERE title->>'ru' ILIKE сканировал пятьдесят тысяч строк JSONB. GIN был для тегов. Каталог платил за extract.

Каталог жил на PostgreSQL, потому что JSONB и GIN казались бесплатным индексом поиска. Публичная сетка фильтровала опубликованные SKU и искала русский title через title->>'ru' ILIKE. EXPLAIN сказал Seq Scan, rows 50000. CPU сидел на extract. GIN на payload в план не входил.

GIN закрывает containment. Листинг просил текст. Vue SSR всё равно получал весь JSONB и выбирал локаль в PHP. Кэш буферов держал документы, которые страница выбрасывала.

Проблема была в том, что GIN приняли за индекс title. Посетители платили seq scan по пятидесяти тысячам строк JSONB. Нужна stored text-колонка, btree на сетке и extract вне горячего пути.

title->> в WHERE не использует GIN на документе. Stored text может жить на btree.
title->> в WHERE не использует GIN на документе. Stored text может жить на btree.

Сгенерированная колонка

title_ru это stored generated-колонка. SELECT листинга это id, slug, price, title_ru. Btree (is_published, sort_order, id) покрывает сетку. Поиск по title, когда он есть, идёт по этой текстовой колонке, не через extract в WHERE. Теги держат GIN и @>.

Те же сорок плиток. План из seq scan по JSONB становится index scan по типизированным колонкам.
Те же сорок плиток. План из seq scan по JSONB становится index scan по типизированным колонкам.

Что PHP перестал делать

json_decode ушёл с листинга. Воркер отдаёт одну строку локали в Vue SSR. Админка по-прежнему правит JSONB. Публичный путь как магазин на MySQL на этом сайте: компактные строки, покрывающий индекс, JSON вне горячего пути.

ILIKE по generated text может взять trigram-индекс, если поиск остаётся. Фильтр листинга его не берёт. Published плюс sort_order хватает для сетки. Поиск это второй запрос, не нагрузка на каждую страницу.

Какой опыт из этого

GIN это containment. title->> ILIKE это seq scan, пока текст не живёт в своей колонке.

JSONB это формат записи. Публичная сетка это типизированные колонки. Кейс filesort в MySQL на этом сайте это тот же урок в другом движке.

EXPLAIN дешевле нового кэша. GIN был здоров и не использовался. Запрос его не просил.

Назад к бизнес-кейсам