perf: cache résultats de recherche par requête, invalidé sur create/update/delete

This commit is contained in:
Cedric Abonnel
2026-05-12 23:34:51 +02:00
parent 25faa6ac4f
commit fb14d7c842
6 changed files with 97 additions and 26 deletions
+1 -1
View File
@@ -8,6 +8,6 @@ vendor/
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Fichiers uploadés et cache (propriété www-data) # Fichiers uploadés et cache générés (propriété www-data)
data/*/files/ data/*/files/
data/_cache/ data/_cache/
@@ -6,10 +6,15 @@
"published": true, "published": true,
"published_at": "2026-06-08 07:00", "published_at": "2026-06-08 07:00",
"created_at": "2026-05-12 23:01:34", "created_at": "2026-05-12 23:01:34",
"updated_at": "2026-05-12 23:18:32", "updated_at": "2026-05-12 23:29:17",
"revisions": [], "revisions": [],
"cover": "", "cover": "cover.jpg",
"files_meta": [], "files_meta": {
"cover.jpg": {
"author": "",
"source_url": ""
}
},
"external_links": [ "external_links": [
{ {
"url": "https://varlog.a5l.fr/post/post-install", "url": "https://varlog.a5l.fr/post/post-install",
@@ -24,6 +29,6 @@
], ],
"seo_title": "", "seo_title": "",
"seo_description": "", "seo_description": "",
"og_image": "", "og_image": "https://varlog.a5l.fr/file?uuid=0bba1ad7-e4cb-49a6-9467-fcfac2e09a93&name=cover.jpg",
"category": "informatique" "category": "informatique"
} }
@@ -15,8 +15,19 @@
"title": "Premiers pas DevOps : préparer un système Debian fraîchement installé" "title": "Premiers pas DevOps : préparer un système Debian fraîchement installé"
} }
], ],
"cover": "", "cover": "cover.jpg",
"files_meta": [], "files_meta": {
"cover.jpg": {
"author": "",
"source_url": "https://3.bp.blogspot.com/-WER6d6fmIXU/WD1K9pOVoQI/AAAAAAAAGt4/47YWFQ7r7HQs2HTlkoz9KRt-1SmBXXaWwCLcB/s320/debian-logo.jpg",
"title": "Logo Debian GNU Linux",
"meta": {
"mime": "image/jpeg",
"size": 12823,
"width": 320
}
}
},
"external_links": [], "external_links": [],
"seo_title": "", "seo_title": "",
"seo_description": "", "seo_description": "",
File diff suppressed because one or more lines are too long
+10 -2
View File
@@ -1854,9 +1854,13 @@ switch ($action) {
$searchQuery = trim($_GET['q'] ?? ''); $searchQuery = trim($_GET['q'] ?? '');
$searchResults = []; $searchResults = [];
if ($searchQuery !== '') { if ($searchQuery !== '') {
$isAnonSearch = !isLoggedIn();
// Lecture du cache pour les visiteurs anonymes
if ($isAnonSearch) {
$searchResults = $articles->getSearchCache($searchQuery) ?? [];
}
if (empty($searchResults)) {
$privateCats = $articles->getPrivateCategories(); $privateCats = $articles->getPrivateCategories();
// Utilise l'index pré-construit si disponible (lecture d'un seul fichier JSON)
// Sinon fallback sur getAll() qui scanne tous les répertoires
$rawPool = $articles->getSearchIndex() ?? $articles->getAll(true); $rawPool = $articles->getSearchIndex() ?? $articles->getAll(true);
$searchPool = array_values(array_filter($rawPool, static function (array $a) use ($privateCats): bool { $searchPool = array_values(array_filter($rawPool, static function (array $a) use ($privateCats): bool {
if (!($a['published'] ?? false)) { if (!($a['published'] ?? false)) {
@@ -1872,6 +1876,10 @@ switch ($action) {
return true; return true;
})); }));
$searchResults = (new SearchEngine())->search($searchQuery, $searchPool); $searchResults = (new SearchEngine())->search($searchQuery, $searchPool);
if ($isAnonSearch && !empty($searchResults)) {
$articles->setSearchCache($searchQuery, $searchResults);
}
}
} }
include BASE_PATH . '/templates/search.php'; include BASE_PATH . '/templates/search.php';
break; break;
+47
View File
@@ -649,6 +649,53 @@ class ArticleManager
$this->dataDir . '/search_index.json', $this->dataDir . '/search_index.json',
json_encode($index, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) json_encode($index, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
); );
$this->clearSearchResultsCache();
}
/** Vide le cache des résultats de recherche (appelé après chaque modification). */
public function clearSearchResultsCache(): void
{
$dir = $this->dataDir . '/_cache/search';
if (!is_dir($dir)) {
return;
}
foreach (scandir($dir) as $f) {
if (str_ends_with($f, '.json')) {
@unlink($dir . '/' . $f);
}
}
}
/**
* Retourne les résultats mis en cache pour une requête anonyme, ou null si absent.
* @return array<array>|null
*/
public function getSearchCache(string $query): ?array
{
$path = $this->searchCachePath($query);
if (!file_exists($path)) {
return null;
}
$data = json_decode((string) file_get_contents($path), true);
return is_array($data) ? $data : null;
}
/** Persiste les résultats d'une requête anonyme dans le cache. */
public function setSearchCache(string $query, array $results): void
{
$dir = $this->dataDir . '/_cache/search';
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents(
$this->searchCachePath($query),
json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
}
private function searchCachePath(string $query): string
{
return $this->dataDir . '/_cache/search/' . md5(mb_strtolower(trim($query))) . '.json';
} }
/** Retourne l'index pré-construit, ou null s'il n'existe pas encore. */ /** Retourne l'index pré-construit, ou null s'il n'existe pas encore. */