fix(maria): lacat intre reindexari, scriere atomica a indexului, XML invalid raportat
Descoperit la prima sincronizare reala din Drive: `maria-sync.timer` a pornit peste rularea manuala si doua procese faceau embeddings in paralel pe acelasi Ollama, ambele urmand sa scrie acelasi rag_index.json. Embedding-ul a incetinit de la ~7s la ~20s din concurenta, iar ultimul care termina ar fi suprascris munca celuilalt. - config.exclusive(): lacat `flock` intre procese, luat la intrarea in sync.py si indexer.py. Nu asteapta — a doua rulare iese curat cu "o reindexare e deja in curs", fiindca ar reface exact acelasi lucru. Verificat pe procese reale. - indexer scrie indexul atomic (tmp + os.replace): consumer-ul reciteste fisierul la 30s si putea prinde un JSON pe jumatate scris. - build() intoarce `warnings` pentru XML-urile care nu se pot parsa, iar rularea din linia de comanda le scrie in stderr. Pana acum, un XML invalid se indexa tacut ca text simplu, cu o singura linie pierduta in log. Context de performanta, masurat pe LXC 171 fara alta incarcare: un embedding `nomic-embed-text` ia ~7,3s, deci o reindexare completa a celor 173 de chunk-uri dureaza ~21 de minute — mai mult decat intervalul timer-ului. Nu e o problema practica (amprenta reindexeaza doar la schimbare, iar lacatul opreste suprapunerea), dar explica de ce prima rulare pare blocata. docs/rclone-google-drive-headless.md: procedura de conectare a unui container headless la Drive prin `rclone authorize`, cu transcriptul rularii reale de pe Windows, capcanele (sync e distructiv pe destinatie, connection string in loc de cale pe nume, unde stau secretele) si de ce nu contul de serviciu. Indexata in CLAUDE.md. 6 teste noi (lacat, eliberare la exceptie, scriere atomica, avertismente). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
This commit is contained in:
@@ -13,6 +13,7 @@ Doua strategii de taiere in chunk-uri:
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
@@ -132,19 +133,36 @@ def embed(text: str) -> list[float]:
|
||||
|
||||
def build() -> dict:
|
||||
entries = []
|
||||
warnings: list[str] = []
|
||||
docs = store.documents_for_index()
|
||||
for doc in docs:
|
||||
text = store.read_document(doc["name"])
|
||||
if doc["name"].endswith(".xml") and chunk_xml(text) is None:
|
||||
warnings.append(f"{doc['name']}: XML invalid, indexat ca text simplu")
|
||||
for i, chunk in enumerate(chunk_document(doc["name"], text)):
|
||||
vec = embed(chunk)
|
||||
entries.append({"source": doc["name"], "chunk": i, "text": chunk, "embedding": vec})
|
||||
config.STATE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
config.INDEX_FILE.write_text(json.dumps(entries, ensure_ascii=False), encoding="utf-8")
|
||||
return {"documents": len(docs), "chunks": len(entries)}
|
||||
# Scriere atomica: consumer-ul reciteste fisierul la 30s si ar putea prinde
|
||||
# un JSON pe jumatate scris daca am scrie direct peste el.
|
||||
tmp = config.INDEX_FILE.with_suffix(".json.tmp")
|
||||
tmp.write_text(json.dumps(entries, ensure_ascii=False), encoding="utf-8")
|
||||
os.replace(tmp, config.INDEX_FILE)
|
||||
out = {"documents": len(docs), "chunks": len(entries)}
|
||||
if warnings:
|
||||
out["warnings"] = warnings
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = build()
|
||||
try:
|
||||
with config.exclusive():
|
||||
result = build()
|
||||
except config.Busy as exc:
|
||||
print(f"[indexer] {exc}, ies fara sa fac nimic", file=sys.stderr)
|
||||
raise SystemExit(0)
|
||||
for w in result.get("warnings", []):
|
||||
print(f"[indexer] ATENTIE {w}", file=sys.stderr)
|
||||
print(
|
||||
f"[indexer] {result['documents']} documente, {result['chunks']} chunk-uri -> {config.INDEX_FILE}",
|
||||
file=sys.stderr,
|
||||
|
||||
Reference in New Issue
Block a user