SPB Git

spb/trouve-ka Public

Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com

Python 76.8% TypeScript 15.7% SQL 3.9% Shell 1.4% CSS 1.3% Dockerfile 0.7%
3.7 KB

# Trouve-KA — Décisions d'architecture

Author: Simon-Pierre Boucher — Contact: contact@spboucher.ai

# D1 — Backend de recherche : OpenSearch 2.x

Candidats évalués : OpenSearch, Elasticsearch, Typesense, Meilisearch, Vespa, Tantivy, Quickwit (CLAUDE.md §3).

Choix : OpenSearch 2.17, parce que :

  • BM25 réel et paramétrable par champ (multi_match, boosts), critère n°1 du MVP.
  • Indexation incrémentale near-real-time : refresh_interval: 1s → une page indexée est cherchable en ~1 s (principe cardinal §0.3).
  • function_score : la fonction de ranking Québec-first custom (§9) s'exprime nativement, poids ajustables sans réindexation.
  • Analyzers FR/EN + synonym_graph search-time (bilinguisme sans traduction à l'ingestion).
  • Highlighting natif pour les snippets, facettes pour les filtres, k-NN natif pour l'hybride sémantique futur (§17.7) sans changer de moteur.
  • Licence Apache 2.0, image ARM64, tourne en 2 Go de heap sur un seul node m2m32.

Rejetés : Meilisearch/Typesense (ranking custom trop rigide pour Score(d,q) pondéré; c'était le choix « facile » interdit par la spec), Tantivy (librairie Rust, il faudrait construire le serveur), Quickwit (orienté logs append-only, pas de mise à jour de documents), Vespa (excellent mais complexité opérationnelle démesurée pour un node unique), Elasticsearch (équivalent fonctionnel d'OpenSearch, licence moins permissive).

# D2 — Backend Python unique (FastAPI + pipeline)

Crawler, parser, classifier, indexer, ranking, scheduler, enrichment et API partagent un seul langage (Python 3.12) et un seul packaging (pyproject.toml racine, namespace trouveka.* mappé sur packages/ et services/). Réduit la complexité (§3 : « le crawler peut rester Python même si l'API est TS » — ici tout le backend est Python, seul le web est TS).

# D3 — Frontier dans Postgres, coordination dans Redis

  • Frontier = Postgres : état relationnel (priorités, retries, scheduling), claims multi-workers via FOR UPDATE SKIP LOCKED — N workers sans coordinateur.
  • Redis : politesse par hôte (SET NX PX, un fetch par hôte par fenêtre, tous workers confondus), drapeau pause, Redis Streams pour l'enrichissement asynchrone (consumer groups, ack explicite).
  • Pas de Celery/RQ : la file d'enrichissement est un stream nu, observable (XLEN), sans dépendance lourde.

# D4 — Pipeline inline (fetch→parse→score→index) dans le crawler-worker

Le chemin rapide est exécuté inline par le worker : c'est la garantie la plus simple du « cherchable en secondes ». La scalabilité passe par le nombre de workers (--scale crawler-worker=N), pas par une séparation prématurée fetch/parse/index (§13 : pas de complexité distribuée prématurée). Les étapes 2-3 (§4) passent par le stream d'enrichissement et ne bloquent jamais.

# D5 — apps/admin fusionné dans apps/web (route /admin)

Un seul runtime Next.js sur m2m32 au lieu de deux (~150 Mo RSS économisés), même design system, même proxy API. La séparation reste possible plus tard (le dashboard est un groupe de composants isolés sous components/admin/). Justification prévue par §2 (« Claude peut améliorer cette structure sur justification technique solide »).

# D6 — Graphe de liens au niveau domaine d'abord

domain_links agrégé (from, to, count) plutôt que page→page : suffisant pour l'autorité de domaine et la découverte au MVP, volume contrôlé. Le graphe page→page viendra quand le ranking l'exigera (§8).

# D7 — Réseau : le web proxifie /api

Next.js rewrite /api/* → API interne. Derrière ngrok (www.trouve-ka.com), un seul port exposé (3000), pas d'URL absolues côté client, pas de CORS en prod.