Ajout de la capacité à épingler des items pour les protéger de l'éviction LRU et à définir un TTL pour l'expiration automatique des items temporaires. Cette implémentation inclut : - Ajout de colonnes `pinned` et `ttl_expires_at` dans la base de données - Nouvelles méthodes dans DB et Cache pour gérer le pinning et le TTL - Modification de la politique d'éviction pour exclure les items épinglés - Implémentation d'une règle métier interdisant le pinning et le TTL simultanément - API REST complète avec endpoints GET/POST/DELETE pour gérer le pinning et le TTL - Documentation OpenAPI automatique - Tests complets couvrant tous les cas d'usage Les items épinglés ne comptent pas dans la limite du cache et ne peuvent jamais être supprimés automatiquement, tandis que les items avec TTL sont supprimés automatiquement à l'expiration.
12 KiB
Rapport : Implémentation des items épinglables dans PMOcache
Résumé
Implémentation réussie de la fonctionnalité d'items épinglables dans la crate PMOcache, permettant de protéger certains items de l'éviction automatique par la politique LRU. Cette fonctionnalité inclut également un système de TTL (Time To Live) avec une règle métier empêchant qu'un item soit à la fois épinglé et avec un TTL.
Modifications apportées
1. Structure de la base de données (pmocache/src/db.rs)
Modification du schéma de la table asset
Ajout de deux nouvelles colonnes :
CREATE TABLE IF NOT EXISTS asset (
pk TEXT PRIMARY KEY,
collection TEXT,
id TEXT,
hits INTEGER DEFAULT 0,
last_used TEXT,
lazy_pk TEXT,
pinned INTEGER DEFAULT 0 CHECK (pinned IN (0, 1)),
ttl_expires_at TEXT
)
pinned: Booléen (0 ou 1) indiquant si l'item est épingléttl_expires_at: Date/heure d'expiration au format RFC3339 (optionnel)
Mise à jour de la structure CacheEntry
Ajout des champs correspondants :
pub struct CacheEntry {
// ... champs existants ...
pub pinned: bool,
pub ttl_expires_at: Option<String>,
// ...
}
Nouvelles méthodes dans DB
Gestion du comptage
count_unpinned(): Compte uniquement les items non épinglés- Les items épinglés ne comptent pas dans la limite du cache
Gestion du pinning
-
pin(pk: &str): Épingle un item- Vérifie que l'item n'a pas de TTL défini (règle métier)
- Retourne une erreur si le TTL est déjà défini
-
unpin(pk: &str): Désépingle un item -
is_pinned(pk: &str): Vérifie si un item est épinglé
Gestion du TTL
-
set_ttl(pk: &str, expires_at: &str): Définit le TTL d'un item- Vérifie que l'item n'est pas épinglé (règle métier)
- Retourne une erreur si l'item est épinglé
-
clear_ttl(pk: &str): Supprime le TTL d'un item -
get_expired(): Récupère tous les items dont le TTL est dépassé
Modification de get_oldest()
La requête SQL exclut maintenant les items épinglés :
SELECT ... FROM asset
WHERE pinned = 0
ORDER BY last_used ASC, hits ASC
LIMIT ?1
2. Logique du cache (pmocache/src/cache.rs)
Méthodes publiques ajoutées
pub async fn pin(&self, pk: &str) -> Result<()>
pub async fn unpin(&self, pk: &str) -> Result<()>
pub async fn is_pinned(&self, pk: &str) -> Result<bool>
pub async fn set_ttl(&self, pk: &str, expires_at: &str) -> Result<()>
pub async fn clear_ttl(&self, pk: &str) -> Result<()>
Modification de enforce_limit()
La politique d'éviction a été améliorée :
- Suppression prioritaire des items expirés : Les items dont le TTL est dépassé sont supprimés en premier
- Comptage des items non épinglés : Utilise
count_unpinned()au lieu decount() - Protection des items épinglés : Ils ne peuvent pas être évincés par LRU
- Logging amélioré : Messages distincts pour les items expirés et l'éviction LRU
3. Tests (pmocache/tests/test_pinnable.rs)
Création d'une suite complète de tests (9 tests, tous passants) :
test_pin_unpin: Vérifie l'épinglage et le désépinglage basiquestest_pinned_excluded_from_lru: Vérifie que les items épinglés ne sont pas évincéstest_pinned_count_separately: Vérifie le comptage séparé des items épingléstest_cannot_pin_with_ttl: Vérifie la règle métier TTL → pas de pinningtest_cannot_set_ttl_when_pinned: Vérifie la règle métier pinned → pas de TTLtest_ttl_expiration: Vérifie la suppression automatique des items expiréstest_clear_ttl: Vérifie la suppression du TTLtest_get_expired: Vérifie la récupération des items expiréstest_cache_entry_fields: Vérifie les valeurs des champs dansCacheEntry
Règles métier implémentées
Incompatibilité TTL ↔ Pinned
Un item ne peut pas être à la fois épinglé ET avoir un TTL :
- Si TTL défini :
pin()retourne une erreur - Si épinglé :
set_ttl()retourne une erreur
Cette règle garantit une sémantique claire :
- Épinglé = permanent, protégé de l'éviction
- TTL = temporaire, sera supprimé à expiration
Comptage des items
Les items épinglés sont exclus du comptage de la limite du cache :
- Un cache de limite 100 peut contenir 100 items non épinglés + N items épinglés
- Seuls les items non épinglés sont pris en compte pour l'éviction LRU
Ordre de suppression lors de enforce_limit()
- Items expirés (TTL dépassé) : supprimés en priorité
- Items LRU : si la limite est toujours dépassée, suppression des plus vieux items non épinglés
Compatibilité
Migration de base de données
Aucune migration nécessaire : Les colonnes pinned et ttl_expires_at ont des valeurs par défaut :
pinned = 0(non épinglé)ttl_expires_at = NULL(pas de TTL)
Les bases existantes seront automatiquement mises à jour au prochain démarrage via le CREATE TABLE IF NOT EXISTS avec les nouvelles colonnes.
Rétrocompatibilité du code
Toutes les méthodes existantes continuent de fonctionner sans modification :
- Les items existants ne sont pas épinglés par défaut
- Le comportement LRU standard reste identique pour les items non épinglés
Exemples d'utilisation
Utilisation programmatique (Rust)
use pmocache::{Cache, CacheConfig};
use chrono::{Duration, Utc};
// Créer un cache
let cache = Cache::<MyConfig>::new("./cache", 100).unwrap();
// Ajouter un fichier
let pk = cache.add_from_url("https://example.com/file.dat", None).await?;
// Épingler pour protéger de l'éviction
cache.pin(&pk).await?;
// Ou définir un TTL de 24 heures
let expires_at = (Utc::now() + Duration::hours(24)).to_rfc3339();
cache.set_ttl(&pk2, &expires_at).await?;
// Vérifier le statut
if cache.is_pinned(&pk).await? {
println!("Fichier protégé");
}
Utilisation via l'API REST
Récupérer le statut de pinning
GET /api/cache/{pk}/pin
Response 200 OK:
{
"pk": "1a2b3c4d5e6f7a8b",
"pinned": false,
"ttl_expires_at": null
}
Épingler un item
POST /api/cache/{pk}/pin
Response 200 OK:
{
"pk": "1a2b3c4d5e6f7a8b",
"message": "Item '1a2b3c4d5e6f7a8b' pinned successfully"
}
Response 409 CONFLICT (si TTL défini):
{
"error": "CONFLICT",
"message": "Cannot pin an item with TTL set. Clear TTL first."
}
Désépingler un item
DELETE /api/cache/{pk}/pin
Response 200 OK:
{
"pk": "1a2b3c4d5e6f7a8b",
"message": "Item '1a2b3c4d5e6f7a8b' unpinned successfully"
}
Définir un TTL
POST /api/cache/{pk}/ttl
Content-Type: application/json
{
"expires_at": "2025-01-20T10:30:00Z"
}
Response 200 OK:
{
"pk": "1a2b3c4d5e6f7a8b",
"message": "TTL set successfully for item '1a2b3c4d5e6f7a8b'"
}
Response 409 CONFLICT (si épinglé):
{
"error": "CONFLICT",
"message": "Cannot set TTL on a pinned item. Unpin first."
}
Response 400 BAD REQUEST (format invalide):
{
"error": "INVALID_DATE",
"message": "Invalid RFC3339 date format"
}
Supprimer un TTL
DELETE /api/cache/{pk}/ttl
Response 200 OK:
{
"pk": "1a2b3c4d5e6f7a8b",
"message": "TTL cleared successfully for item '1a2b3c4d5e6f7a8b'"
}
Fichiers modifiés
Phase 1 : Implémentation de base
-
pmocache/src/db.rs:- Modification du schéma SQL
- Ajout de champs dans
CacheEntry - Ajout de 8 nouvelles méthodes
- Modification de
get_oldest(),get(),get_from_id(),get_all(),get_by_collection()
-
pmocache/src/cache.rs:- Ajout de 5 méthodes publiques
- Modification de
enforce_limit()
-
pmocache/tests/test_pinnable.rs:- Nouveau fichier de tests (9 tests)
Phase 2 : Enrichissement de l'API REST
-
pmocache/src/api.rs:- Ajout de 3 nouvelles structures de données :
SetTtlRequest,PinResponse,PinStatus - Ajout de 5 nouveaux handlers d'API :
get_pin_status(): Récupération du statut de pinningpin_item(): Épinglage d'un itemunpin_item(): Désépinglage d'un itemset_item_ttl(): Définition du TTLclear_item_ttl(): Suppression du TTL
- Ajout de 3 nouvelles structures de données :
-
pmocache/src/pmoserver_ext.rs:- Ajout de 4 nouvelles routes dans
create_api_router():GET /{pk}/pin: Statut de pinningPOST /{pk}/pin: ÉpinglerDELETE /{pk}/pin: DésépinglerPOST /{pk}/ttl: Définir TTLDELETE /{pk}/ttl: Supprimer TTL
- Ajout de 4 nouvelles routes dans
-
pmocache/src/openapi.rs:- Mise à jour de la macro
create_cache_openapi!pour inclure :- Les 5 nouveaux endpoints dans la documentation
- Les 3 nouvelles structures dans les schémas OpenAPI
- Mise à jour de la macro
-
pmocache/src/lib.rs:- Export des nouvelles structures publiques pour l'API
API REST et Documentation OpenAPI
Routes disponibles
Toutes les routes sont préfixées par /api/{cache_name}/ (ex: /api/covers/, /api/audio/).
| Méthode | Route | Description |
|---|---|---|
GET |
/{pk}/pin |
Récupère le statut de pinning d'un item |
POST |
/{pk}/pin |
Épingle un item (le protège de l'éviction LRU) |
DELETE |
/{pk}/pin |
Désépingle un item |
POST |
/{pk}/ttl |
Définit le TTL d'un item (expiration automatique) |
DELETE |
/{pk}/ttl |
Supprime le TTL d'un item |
Codes de statut HTTP
| Code | Signification | Cas d'usage |
|---|---|---|
200 OK |
Opération réussie | Tous les cas de succès |
400 BAD REQUEST |
Requête invalide | Format de date TTL invalide |
404 NOT FOUND |
Item non trouvé | PK inexistant dans le cache |
409 CONFLICT |
Conflit de règle métier | Tentative de pin avec TTL ou vice-versa |
500 INTERNAL SERVER ERROR |
Erreur serveur | Erreur de base de données |
Documentation OpenAPI/Swagger
La documentation OpenAPI est automatiquement générée et inclut :
-
Schémas de données :
PinStatus: Statut de pinning (pinned, ttl_expires_at)PinResponse: Réponse d'opération de pinningSetTtlRequest: Requête de définition de TTLCacheEntry: Mis à jour avec les champspinnedetttl_expires_at
-
Endpoints documentés :
- Description détaillée de chaque route
- Exemples de requêtes et réponses
- Codes d'erreur possibles
-
Interface Swagger UI :
- Accessible à
/swagger-ui/{cache_name} - Permet de tester l'API directement depuis le navigateur
- Accessible à
Gestion des erreurs
L'API suit une structure d'erreur cohérente :
{
"error": "CODE_ERREUR",
"message": "Description lisible de l'erreur"
}
Les règles métier sont appliquées strictement :
- 409 CONFLICT si tentative de pin avec TTL défini
- 409 CONFLICT si tentative de set TTL sur item épinglé
- Messages d'erreur explicites guidant l'utilisateur
Tests
- Suite de tests dédiée : 9 tests, tous passants
- Tests existants : Tous les tests de
test_cache.rspassent toujours - Couverture : Toutes les nouvelles fonctionnalités sont testées
- Compilation : Aucune erreur, tous les modules compilent correctement
Résultat
✅ Implémentation complète et fonctionnelle des items épinglables avec TTL
✅ Règle métier TTL ↔ Pinned correctement implémentée
✅ Tests exhaustifs validant tous les cas d'usage
✅ Compatibilité avec les bases de données existantes
✅ Pas de régression sur les tests existants
✅ API REST complète avec 5 nouveaux endpoints
✅ Documentation OpenAPI automatiquement générée
✅ Gestion d'erreurs cohérente avec codes HTTP appropriés