Files
pmomusic/Blackboard/Report/Pinnable_cache_item.md
Eric Coissac 2cdc7109d1 Implémentation des fonctionnalités d'items épinglables et TTL dans PMOcache
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.
2026-01-15 22:18:43 +01:00

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 :

  1. Suppression prioritaire des items expirés : Les items dont le TTL est dépassé sont supprimés en premier
  2. Comptage des items non épinglés : Utilise count_unpinned() au lieu de count()
  3. Protection des items épinglés : Ils ne peuvent pas être évincés par LRU
  4. 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) :

  1. test_pin_unpin : Vérifie l'épinglage et le désépinglage basiques
  2. test_pinned_excluded_from_lru : Vérifie que les items épinglés ne sont pas évincés
  3. test_pinned_count_separately : Vérifie le comptage séparé des items épinglés
  4. test_cannot_pin_with_ttl : Vérifie la règle métier TTL → pas de pinning
  5. test_cannot_set_ttl_when_pinned : Vérifie la règle métier pinned → pas de TTL
  6. test_ttl_expiration : Vérifie la suppression automatique des items expirés
  7. test_clear_ttl : Vérifie la suppression du TTL
  8. test_get_expired : Vérifie la récupération des items expirés
  9. test_cache_entry_fields : Vérifie les valeurs des champs dans CacheEntry

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()

  1. Items expirés (TTL dépassé) : supprimés en priorité
  2. 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

  1. 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()
  2. pmocache/src/cache.rs :

    • Ajout de 5 méthodes publiques
    • Modification de enforce_limit()
  3. pmocache/tests/test_pinnable.rs :

    • Nouveau fichier de tests (9 tests)

Phase 2 : Enrichissement de l'API REST

  1. 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 pinning
      • pin_item() : Épinglage d'un item
      • unpin_item() : Désépinglage d'un item
      • set_item_ttl() : Définition du TTL
      • clear_item_ttl() : Suppression du TTL
  2. pmocache/src/pmoserver_ext.rs :

    • Ajout de 4 nouvelles routes dans create_api_router() :
      • GET /{pk}/pin : Statut de pinning
      • POST /{pk}/pin : Épingler
      • DELETE /{pk}/pin : Désépingler
      • POST /{pk}/ttl : Définir TTL
      • DELETE /{pk}/ttl : Supprimer TTL
  3. 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
  4. 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 pinning
    • SetTtlRequest : Requête de définition de TTL
    • CacheEntry : Mis à jour avec les champs pinned et ttl_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

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.rs passent 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