commit local changes
This commit is contained in:
+90
-19
@@ -112,6 +112,19 @@ RANK_NORMALIZATION = int(os.getenv("JONASSEARCH_RANK_NORMALIZATION", "8"))
|
||||
# Ranking dann innerhalb dieses Fensters (Antwortflag ranking_capped).
|
||||
MAX_RANK_CANDIDATES = int(os.getenv("JONASSEARCH_MAX_RANK_CANDIDATES", "5000"))
|
||||
|
||||
# Notfall-Stufe: selbst das Kandidatenfenster ist zu groß (extrem häufige
|
||||
# Begriffe). Dann wird in Etappen immer weiter reduziert, bis die Antwort
|
||||
# innerhalb des Zeitlimits kommt. Der Nutzer bekommt trotzdem Ergebnis-
|
||||
# seiten statt einer Fehlermeldung – bei reduzierter Trefferqualität
|
||||
# (Antwortflag ranking_degraded).
|
||||
RANK_CAP_RETRIES = int(os.getenv("JONASSEARCH_RANK_CAP_RETRIES", "3"))
|
||||
|
||||
# Sortierung "relevance" fällt bei Überschreitung aller Ranking-Timeouts
|
||||
# auf diese Basisreihenfolge zurück (kein ts_rank_cd nötig).
|
||||
FALLBACK_SORT_SQL = (
|
||||
"COALESCE(p.crawled_at, p.discovered_at) DESC NULLS LAST, p.id ASC"
|
||||
)
|
||||
|
||||
# ts_rank_cd-Gewichte. Reihenfolge im DB-Trigger: Titel = A,
|
||||
# Überschriften = B, Inhalt = C, (D = ungewichteter Rest).
|
||||
# Format: "A=1.0,B=0.6,C=0.3,D=0.1"
|
||||
@@ -572,6 +585,30 @@ def _headline_sql():
|
||||
"""
|
||||
|
||||
|
||||
def _emergency_search_sql(vector_col, filter_sql):
|
||||
"""
|
||||
Notfall-Query ganz ohne ts_rank_cd: Kandidaten werden in fester
|
||||
Basisreihenfolge (neueste zuerst) geliefert. Auch bei extrem häufigen
|
||||
Begriffen garantiert schnell, weil kein Ranking über die Treffermenge
|
||||
nötig ist.
|
||||
"""
|
||||
return f"""
|
||||
SELECT p.id, p.url, p.title, p.domain, p.tld,
|
||||
p.http_status, p.content_type,
|
||||
COALESCE(p.crawled_at, p.discovered_at) AS crawled_at,
|
||||
0.0 AS text_rank,
|
||||
1.0 AS freshness,
|
||||
0.0 AS score
|
||||
FROM pages p
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT websearch_to_tsquery(%(ts_config)s, %(query)s) AS tsq
|
||||
) q
|
||||
WHERE p.{vector_col} @@ q.tsq{filter_sql}
|
||||
ORDER BY {FALLBACK_SORT_SQL}
|
||||
LIMIT %(limit)s OFFSET %(offset)s
|
||||
"""
|
||||
|
||||
|
||||
def _count_sql(vector_col, filter_sql):
|
||||
return f"""
|
||||
SELECT count(*) AS n
|
||||
@@ -600,6 +637,50 @@ def _round(value, digits=6):
|
||||
# ÖFFENTLICHE API
|
||||
# ============================================================
|
||||
|
||||
def _fallback_search(cur, vector_col, sort, filter_sql, params):
|
||||
"""
|
||||
Mehrstufiger Fallback, wenn das volle Ranking das Zeitlimit
|
||||
überschritten hat. Liefert so gut wie immer Ergebnis-Seiten statt
|
||||
eines harten Timeout-Fehlers:
|
||||
|
||||
1. Kandidatenfenster MAX_RANK_CANDIDATES (gs. 5000):
|
||||
normales, aber begrenztes Ranking.
|
||||
2. Danach in Etappen verkleinern (z. B. 2000 → 800 → 320 …), bis
|
||||
die Antwort innerhalb des Zeitlimits kommt.
|
||||
3. Letzte Stufe: ganz ohne ts_rank_cd-Ranking – nur Index-Kandidaten
|
||||
in definierter Reihenfolge (z. B. neueste zuerst).
|
||||
|
||||
Rückgabe: (ranking_capped, ranking_degraded, rows).
|
||||
"""
|
||||
cap = int(params.get("rank_cap") or MAX_RANK_CANDIDATES)
|
||||
capped = True
|
||||
degraded = False
|
||||
|
||||
# Stufe 2: Fenster in Etappen verkleinern.
|
||||
for _ in range(max(0, RANK_CAP_RETRIES)):
|
||||
try:
|
||||
cur.execute(_search_sql(vector_col, sort, filter_sql, capped=True),
|
||||
params)
|
||||
return capped, degraded, cur.fetchall()
|
||||
except psycopg2.errors.QueryCanceled:
|
||||
cap = max(50, int(cap * 0.4))
|
||||
params["rank_cap"] = cap
|
||||
degraded = True
|
||||
|
||||
# Stufe 3: Notfall ohne Ranking – Kandidaten in Basisreihenfolge.
|
||||
try:
|
||||
cur.execute(
|
||||
_emergency_search_sql(vector_col, filter_sql), params,
|
||||
)
|
||||
return capped, True, cur.fetchall()
|
||||
except psycopg2.errors.QueryCanceled:
|
||||
raise SearchTimeout(
|
||||
"Die Suche dauert ungewöhnlich lange – probiere es nochmal "
|
||||
"oder nutze einen Filter wie site=<domain>, um schneller zu "
|
||||
"Ergebnissen zu kommen."
|
||||
)
|
||||
|
||||
|
||||
def search(query, *, page=1, per_page=None, sites=None, tlds=None,
|
||||
after=None, before=None, sort="relevance",
|
||||
include_errors=False, include_non_html=False,
|
||||
@@ -665,6 +746,7 @@ def search(query, *, page=1, per_page=None, sites=None, tlds=None,
|
||||
snippets = {}
|
||||
total = None
|
||||
ranking_capped = False
|
||||
ranking_degraded = False
|
||||
|
||||
if not empty_tsq:
|
||||
_set_statement_timeout(cur, STATEMENT_TIMEOUT_MS)
|
||||
@@ -673,27 +755,15 @@ def search(query, *, page=1, per_page=None, sites=None, tlds=None,
|
||||
params)
|
||||
rows = cur.fetchall()
|
||||
except psycopg2.errors.QueryCanceled:
|
||||
# Sehr häufiger Begriff: volles Ranking über alle
|
||||
# Kandidaten ist zu langsam. Zweiter Versuch über
|
||||
# ein begrenztes Kandidatenfenster (bounded latency).
|
||||
ranking_capped, ranking_degraded, rows = _fallback_search(
|
||||
cur, vector_col, sort, filter_sql, params,
|
||||
)
|
||||
log.info(
|
||||
"Volles Ranking hat das Zeitlimit überschritten; "
|
||||
"Fallback auf %d Kandidaten.", MAX_RANK_CANDIDATES
|
||||
"Volles Ranking übersprungen (Timeout); "
|
||||
"Fallback: %d Kandidaten%s.",
|
||||
params["rank_cap"],
|
||||
", degradiert" if ranking_degraded else "",
|
||||
)
|
||||
ranking_capped = True
|
||||
try:
|
||||
cur.execute(
|
||||
_search_sql(vector_col, sort, filter_sql,
|
||||
capped=True),
|
||||
params,
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
except psycopg2.errors.QueryCanceled:
|
||||
raise SearchTimeout(
|
||||
"Suchanfrage hat das Zeitlimit überschritten – "
|
||||
"bitte weiter eingrenzen (z. B. site=, after= "
|
||||
"oder zusätzliche Begriffe)."
|
||||
)
|
||||
|
||||
if with_snippets and rows:
|
||||
ids = [r["id"] for r in rows[:per_page]]
|
||||
@@ -744,6 +814,7 @@ def search(query, *, page=1, per_page=None, sites=None, tlds=None,
|
||||
"per_page": per_page,
|
||||
"sort": sort,
|
||||
"ranking_capped": ranking_capped,
|
||||
"ranking_degraded": ranking_degraded,
|
||||
"total": total,
|
||||
"has_more": has_more,
|
||||
"results": results,
|
||||
|
||||
Reference in New Issue
Block a user