13. července 2026

Drahá práce nepatří na kritickou cestu odpovědi

Existuje jednoduché rozdělení, které rozhoduje, jestli bude chatbot nad velkou databází rychlý, nebo pomalý. Na jedné straně je práce drahá a předvídatelná - embeddingy dokumentů, geocoding měst, reindexování stránek, sumarizace uzlů. Na straně druhé je práce levná a závislá na otázce - pár SQL lookupů přes index, skládání odpovědi. Tým, kterému jde o nízkou latenci, ví, kde je která, a nikdy je nespojí do jedné cesty.

Princip zní: drahá práce, která se mění zřídka, se nedělá v requestu. Odsuňte ji do offline pipeline, výsledky uložte a nechte request, ať jen čte. Na kritické cestě odpovědi zůstane jen to, co je levné a indexované.

Embeddingy jsou práce na pozadí

Vezměte ten nejjasnější případ. Vektorové vyhledávání potřebuje embeddingy - vektorovou reprezentaci každého dokumentu, entity, chunku. Výpočet embeddingu je poměrně drahý: síťový call do modelu, tokenizace, práce modelu. Když tohle děláte v requestu - embedovat otázku ano, ale proč embedovat dokumenty až v tu chvíli? - každý dotaz platí celou tou cenou. A přitom embedding dokumentu se nemění s otázkou. Je to vlastnost dokumentu.

Správný tvar: embedujte dokumenty offline, v incrementální pipeline, a uložte vektory vedle nich. Request pak embeduje jen otázku (jedno volání) a dělá levný nearest-neighbor lookup v indexu. Klíčová vlastnost té offline pipeline je incrementalita: pro každý řádek si ukládejte content hash a verzi modelu. Nezměněný řádek přeskočte. Bump modelu vynutí re-embed. Editace jedné entity tak stojí jeden embed call, ne re-embed celé databáze. Bez toho offline pipeline zasekne každý update a tým ji přestane pouštět - a pak se embeddingy odsynchronizují a vyhledávání zhorší.

Geocoding: jedna tabulka, žádné API

Tady je hezký příklad, jak daleko ten princip dojde. Agent odpovídá na dotazy typu „kde můžu nejblíž najít X" - potřebuje přeložit město na souřadnice a spočítat vzdálenost ke všem entitám. Naivní verze zavolá geocoding API na každý takový dotaz. To je drahé (kvóta, latence, peníze) a křehké (API může být dole).

Lepší verze si všimne, že katalogová místa se mění zřídka. Geocodujte je jednorázově, backfill skriptem, a souřadnice uložte do tabulky. Většina dotazů pak jen přečte souřadnice z databáze - žádný externí call. Geocoding API zůstane jen pro případ, kdy uživatel napíše vesnici nebo adresu, která v katalogu není. A i tam si zaslouží cache: pozitivní výsledky i ZERO_RESULTS se cachují (špatně napsané místo nesmí trávit kvótu každý turn), ale transitní chyby se cachovat nesmí - retry může uspět. Z toho, co mělo být drahým API callem, se stane úzká výjimka.

Jeden zdroj pravdy, žádná synchronizace

Tady je strukturální rozhodnutí, které se tváří jako architektura, ale je to vlastně latenční trik. Velká část „latence" v systémech s vyhledávačem není inference - je to synchronizace. Upravíte entitu v databázi, vyhledávač ji musí reindexovat, a do té doby vrací stará data. Tým, který stráví čas laděním této synchronizace, nikdy nedosáhne „editováno → hned konzistentní".

Řešení, když je to možné: běhové vyhledávání běží čistě nad databází, která je zdrojem pravdy. Žádný samostatný vyhledávač, který by se synchronizoval. Hybridní vyhledávání - full-text, trigramy, vektory - běží jako SQL dotazy přes indexy nad jednou databází. Editovaná entita je v dalším dotazu konzistentní, protože je to tatáž databáze. Žádná replika, žádná prodleva, žádná nekončící synchronizační práce.

Fúze bez ladění

Když spustíte více vyhledávacích strategií najednou - lexikální, trigram, vektor - musíte jejich skóre slijt. Instinkt je vážený součet, což vás vtáhne do nekonečného ladění vah mezi nesrovnatelnými skóre (cosine vs. ts_rank). Lepší volba je Reciprocal Rank Fusion: skóre se nesčítá, kombinuje se přes pozice - každá strategie přispívá 1 / (k + rank). Není co ladit. Funguje napříč strategiemi out-of-the-box. Tým, který ladí váhy hybridního vyhledávače, obvykle ladí špatnou věc - a ztrácí čas, který mohl věnovat něčemu, co latenci opravdu hýbe.

Posuňte výpočet do databáze

Poslední tah. Když odpověď vyžaduje výpočet nad výsledky - seřaď entity podle vzdálenosti od bodu X - instinkt je vytáhnout řádky do aplikace a počítat v interpreteru. To je drahé: taháte data, co nepotřebujete, a děláte práci, kterou databáze umí sama. Lepší cesta: napište ten výpočet jako SQL výraz a pusťte ho v dotazu, přes index. Vzdálenost, ranking, agregace - to vše jde vyjádřit v SQL a běží souběžně s vyhledáváním. Jeden dotaz, jeden round-trip, výsledek hotový. A statement_timeout chrání před dotazem, který se zvrhl - radši rychlá chyba než visící request.

Co si vzít s sebou

Nízká latence nad velkou databází není o rychlejším modelu. Je o tom, co nepustíte na kritickou cestu odpovědi. Embeddingy, geocoding, sumarizace, reindexování - drahé a předvídatelné - patří do offline pipeline, incrementální, ukládající hash i verzi modelu. Request by měl dělat jen levné indexované lookupy nad jednou databází, která je zdrojem pravdy. Fúze skóre by měla být bezladící. Výpočet nad výsledky by měl běžet v SQL, ne v aplikaci. Tým, který tohle dodrží, zjistí, že jeho „rychlost" už dávno není o inference - inference je teď ta nejlevnější část cesty.


Váš chatbot platí latencí za práci, která se mění jednou týdně? Většina zisku na velkých RAG databázích přijde z toho, co odsunete z requestu offline. Pojďme se podívat, co váš bot počítá za běhu, i když se to od včerejška nezměnilo.