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
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/_cache/
@@ -6,10 +6,15 @@
"published": true,
"published_at": "2026-06-08 07:00",
"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": [],
"cover": "",
"files_meta": [],
"cover": "cover.jpg",
"files_meta": {
"cover.jpg": {
"author": "",
"source_url": ""
}
},
"external_links": [
{
"url": "https://varlog.a5l.fr/post/post-install",
@@ -24,6 +29,6 @@
],
"seo_title": "",
"seo_description": "",
"og_image": "",
"og_image": "https://varlog.a5l.fr/file?uuid=0bba1ad7-e4cb-49a6-9467-fcfac2e09a93&name=cover.jpg",
"category": "informatique"
}
@@ -15,8 +15,19 @@
"title": "Premiers pas DevOps : préparer un système Debian fraîchement installé"
}
],
"cover": "",
"files_meta": [],
"cover": "cover.jpg",
"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": [],
"seo_title": "",
"seo_description": "",
File diff suppressed because one or more lines are too long
+26 -18
View File
@@ -1851,27 +1851,35 @@ switch ($action) {
case 'search':
require_once BASE_PATH . '/src/SearchEngine.php';
$searchQuery = trim($_GET['q'] ?? '');
$searchQuery = trim($_GET['q'] ?? '');
$searchResults = [];
if ($searchQuery !== '') {
$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);
$searchPool = array_values(array_filter($rawPool, static function (array $a) use ($privateCats): bool {
if (!($a['published'] ?? false)) {
return false;
$isAnonSearch = !isLoggedIn();
// Lecture du cache pour les visiteurs anonymes
if ($isAnonSearch) {
$searchResults = $articles->getSearchCache($searchQuery) ?? [];
}
if (empty($searchResults)) {
$privateCats = $articles->getPrivateCategories();
$rawPool = $articles->getSearchIndex() ?? $articles->getAll(true);
$searchPool = array_values(array_filter($rawPool, static function (array $a) use ($privateCats): bool {
if (!($a['published'] ?? false)) {
return false;
}
$cat = trim($a['category'] ?? '');
if ($cat !== '' && in_array($cat, $privateCats, true) && !isLoggedIn()) {
return false;
}
if (strtotime((string)($a['published_at'] ?? '')) > time() && !hasCapability('view_previews')) {
return false;
}
return true;
}));
$searchResults = (new SearchEngine())->search($searchQuery, $searchPool);
if ($isAnonSearch && !empty($searchResults)) {
$articles->setSearchCache($searchQuery, $searchResults);
}
$cat = trim($a['category'] ?? '');
if ($cat !== '' && in_array($cat, $privateCats, true) && !isLoggedIn()) {
return false;
}
if (strtotime((string)($a['published_at'] ?? '')) > time() && !hasCapability('view_previews')) {
return false;
}
return true;
}));
$searchResults = (new SearchEngine())->search($searchQuery, $searchPool);
}
}
include BASE_PATH . '/templates/search.php';
break;
+47
View File
@@ -649,6 +649,53 @@ class ArticleManager
$this->dataDir . '/search_index.json',
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. */