Skip to content

Stale knowledge index

RAG degrades not with a bang but with silence: the reindex stalled a week ago, yet the system keeps answering — just from stale data. No errors, no 500s. Catch two independent freshness signals: when the index was last built and how old the newest document in it is. The second matters more — it’s about the SLA, not the volume.

Every successful reindex run writes a timestamp. Once an hour a cloud function compares it against the SLA:

import os, time, requests
STAMP = "/var/lib/rag/last-reindex.ts"
SLA_HOURS = 24 # the reindex should run once a day
def handler(event, context):
if not os.path.exists(STAMP):
notify("🕰️ Индекс не собирался", "Нет отметки о переиндексации.", 8)
return {"statusCode": 200}
age_h = (time.time() - float(open(STAMP).read())) / 3600
if age_h > SLA_HOURS:
notify("🕰️ Индекс устарел",
f"Последняя переиндексация: {age_h:.1f} ч назад "
f"(SLA {SLA_HOURS} ч).\n"
"RAG отвечает по старым данным — проверьте cron/джобу.",
priority=8)
return {"statusCode": 200}
def notify(title, message, priority):
requests.post(f"{os.environ['NOTIFLY_URL']}/message",
params={"token": os.environ["NOTIFLY_TOKEN"]},
json={"title": title, "message": message, "priority": priority},
timeout=5)

And at the end of a successful run the reindex job simply does:

open("/var/lib/rag/last-reindex.ts", "w").write(str(time.time()))

2. Freshness by the newest document in the index

Section titled “2. Freshness by the newest document in the index”

The index may have been built regularly — but from a dead source (the crawler broke, the folder stopped filling up). Look at the freshest document in the collection:

FRESHNESS_HOURS = 48 # the freshest document shouldn't be older than 2 days
def check_newest_doc():
# take the max updated_at across chunk metadata
newest = vector_db_max_metadata("updated_at") # your client → epoch seconds
if newest is None:
notify("🕰️ В индексе нет документов", "Коллекция пуста.", 9)
return
age_h = (time.time() - newest) / 3600
if age_h > FRESHNESS_HOURS:
notify("🕰️ Источники устарели",
f"Свежайший документ в индексе: {age_h:.1f} ч назад "
f"(SLA {FRESHNESS_HOURS} ч).\n"
"Индекс собирается, но источник перестал обновляться.",
priority=7)

The two signals catch different failures: the first is “the job doesn’t run”, the second is “the job runs but no data arrives”.

If you don’t want a separate cloud function, a heartbeat with intervalSec slightly larger than your cron period is enough. Ping it at the end of a successful reindex; a missed ping produces the push “index didn’t update” on its own:

Окно терминала
python -m rag.reindex && curl -fsS "$REINDEX_PING_URL" -o /dev/null

The heartbeat covers signal #1 for free; signal #2 still needs its own metadata check.

  • the age of the last reindex and the SLA;
  • the age of the freshest document;
  • the collection name;
  • the expected cron period — so you can instantly tell “one run missed” from “stalled entirely”.