« On nous a dit qu'il fallait une base vectorielle. » Pour quelques milliers de documents, MySQL et un peu de PHP font le travail sans nouvelle infra à opérer. La base vectorielle se justifie à grande échelle, pas avant.

Le stockage

CREATE TABLE doc_embedding (
    id INT PRIMARY KEY AUTO_INCREMENT,
    doc_id INT NOT NULL,
    vector JSON NOT NULL,        -- [0.012, -0.08, ...]
    INDEX (doc_id)
);

La similarité cosinus en PHP

function cosine(array $a, array $b): float {
    $dot = $na = $nb = 0.0;
    foreach ($a as $i => $v) {
        $dot += $v * $b[$i];
        $na  += $v * $v;
        $nb  += $b[$i] * $b[$i];
    }
    return $dot / (sqrt($na) * sqrt($nb) + 1e-9);
}

$q = embed($query);                       // vecteur requête
$rows = $pdo->query('SELECT doc_id, vector FROM doc_embedding')->fetchAll();
usort($rows, fn($x, $y) =>
    cosine($q, json_decode($y['vector'], true))
    <=> cosine($q, json_decode($x['vector'], true)));
$top = array_slice($rows, 0, 5);

Jusqu'où ça tient

VolumeApprocheLatence recherche
< 10k vecteursMySQL + cosine PHP en RAMquelques dizaines de ms
10k – 100kMySQL + pré-filtre SQL + cosine100–500 ms
> 100kBase vectorielle dédiéelà c'est justifié
Le point de bascule réel est autour de 100k vecteurs, pas 1k.

L'optimisation gratuite

Charger 10k vecteurs à chaque requête est inutile : les garder en cache mémoire (APCu, ou un singleton de process worker) et ne recalculer qu'à l'ajout de documents. La recherche devient un simple parcours en RAM. Simple, debuggable, zéro nouvelle dépendance à opérer.