CV laden
Zurück zu Business Cases

PostgreSQL

Ein GIN, der die Listenquery nie sah

WHERE title->>'ru' ILIKE scannte fünfzigtausend JSONB-Zeilen. Der GIN war für Tags. Der Katalog zahlte den Extract.

Der Katalog lag auf PostgreSQL, weil JSONB und GIN wie ein kostenloser Suchindex wirkten. Das öffentliche Raster filterte publizierte SKUs und suchte den russischen Titel mit title->>'ru' ILIKE. EXPLAIN sagte Seq Scan, rows 50000. CPU saß auf Extract. Der GIN auf payload kam nicht in den Plan.

GIN trifft Containment. Die Liste wollte Text. Vue SSR bekam trotzdem das volle JSONB und wählte die Locale in PHP. Der Buffer Cache hielt Dokumente, die die Seite wegwarf.

Das Problem war, GIN als Title-Index zu behandeln. Besucher zahlten einen Seq Scan über fünfzigtausend JSONB-Zeilen. Ich brauchte eine gespeicherte Textspalte, einen Btree auf dem Raster und Extract vom Hot Path weg.

title->> im WHERE nutzt keinen GIN auf dem Dokument. Eine gespeicherte Textspalte kann einen Btree nutzen.
title->> im WHERE nutzt keinen GIN auf dem Dokument. Eine gespeicherte Textspalte kann einen Btree nutzen.

Die generierte Spalte

title_ru ist eine stored generated Spalte. Das Listen-SELECT ist id, slug, price, title_ru. Ein Btree auf (is_published, sort_order, id) deckt das Raster. Titelsuche, wenn es sie gibt, nutzt diese Textspalte, keinen Extract im WHERE. Tags behalten GIN und @>.

Dieselben vierzig Kacheln. Der Plan geht von Seq Scan über JSONB zu Index Scan auf typisierten Spalten.
Dieselben vierzig Kacheln. Der Plan geht von Seq Scan über JSONB zu Index Scan auf typisierten Spalten.

Was PHP ließ

json_decode verließ die Liste. Der Worker schickt einen Locale-String an Vue SSR. Admin editiert weiter JSONB. Der öffentliche Pfad sieht aus wie der MySQL-Shop auf dieser Site: kompakte Zeilen, Covering-Index, JSON vom Hot Path weg.

ILIKE auf einer generated Textspalte kann einen Trigram-Index nutzen, wenn Suche bleibt. Der Listenfilter tut das nicht. Published plus sort_order reicht für das Raster. Suche ist eine zweite Query, kein Anhang an jeder Seite.

Was ich daraus mitnehme

GIN ist Containment. title->> ILIKE ist Seq Scan, bis der Text in einer eigenen Spalte lebt.

JSONB ist ein Schreibformat. Das öffentliche Raster sind typisierte Spalten. Der MySQL-Filesort-Fall auf dieser Site ist dieselbe Lektion in einer anderen Engine.

EXPLAIN ist billiger als Cache. Der GIN war gesund und ungenutzt. Die Query fragte nie nach ihm.

Zurück zu Business Cases