stale-cache-external-service-recovery

Par divinevideo · divine-mobile

Gérer un cache local obsolète lorsqu'un service externe perd ou réinitialise ses données. À utiliser quand : (1) le rafraîchissement d'un token échoue avec une erreur "not found" ou 404, (2) des identifiants en cache (pubkeys, IDs utilisateur) n'existent plus sur le service externe, (3) le service externe a été réinitialisé/migré mais le cache local contient d'anciennes valeurs. Pattern : détecter spécifiquement le 404, supprimer l'entrée de cache obsolète, recréer sur le service externe, mettre à jour le cache avec les nouvelles valeurs. Complète le skill `local-cache-idempotency-fallback`.

npx skills add https://github.com/divinevideo/divine-mobile --skill stale-cache-external-service-recovery

Récupération du Cache Obsolète Quand le Service Externe Perd des Données

Problème

Quand vous utilisez votre base de données comme couche de cache/idempotence (voir local-cache-idempotency-fallback), un nouveau mode de défaillance apparaît : le service externe perd ou réinitialise ses données, mais votre cache conserve les anciennes valeurs. Les opérations échouent avec des erreurs trompeuses parce que les identifiants en cache n'existent plus sur le service externe.

Contexte / Conditions de Déclenchement

  • L'actualisation du token échoue avec 404 « User with pubkey/id not found »
  • Les opérations échouent avec « Invalid or expired token » mais le vrai problème est l'utilisateur manquant
  • Le service externe a été réinitialisé, migré ou a perdu des données
  • Les identifiants en cache (pubkeys, API keys, user IDs) sont valides en cache mais pas sur le service
  • Le message d'erreur est trompeur - suggère un problème de token alors que c'est vraiment une ressource manquante

Solution

Détectez le cas spécifique « not found » et gérez-le séparément des autres erreurs :

if (cached) {
  pubkey = cached.pubkey;
  token = cached.token;

  try {
    // Try to refresh/validate the cached credentials
    const freshToken = await externalApi.getTokenByPubkey(pubkey);
    token = freshToken;
    console.log(`Pubkey: ${pubkey} (token refreshed)`);
  } catch (error) {
    const errMsg = error instanceof Error ? error.message : String(error);

    // CRITICAL: Detect "not found" specifically - cache is stale
    if (errMsg.includes("not found") || response.status === 404) {
      console.log(`Cached identifier not on external service, recreating...`);

      // 1. Delete stale cache entry
      await db.deleteUser(userId);

      // 2. Create fresh resource on external service
      const result = await externalApi.createUser(userId, username);
      pubkey = result.pubkey;
      token = result.token;
      console.log(`Pubkey: ${pubkey} (recreated)`);

      // 3. Cache new values
      await db.saveUser({ userId, pubkey, token });
    } else {
      // Other error (e.g., user claimed account, rate limit) - can't proceed
      console.log(`Refresh failed: ${errMsg}`);
      continue; // or throw
    }
  }
}

Motif Clé

  1. Tentez d'abord l'actualisation normale : essayez de valider/actualiser les identifiants en cache
  2. Détectez 404 spécifiquement : vérifiez « not found » dans le message d'erreur ou le statut 404
  3. Supprimez l'entrée obsolète : supprimez l'entrée de cache invalide avant de recréer
  4. Recréez sur le service externe : créez une ressource fraîche avec les mêmes identifiants d'entrée
  5. Mettez à jour le cache : stockez les nouvelles valeurs pour les exécutions futures
  6. Distinguez des autres erreurs : gérez uniquement le 404 ; les autres erreurs peuvent nécessiter un traitement différent

Vérification

  1. Simulez une perte de données du service externe (ou videz sa base de données)
  2. Lancez le script avec un cache local obsolète
  3. Devrait afficher le message « recreating », pas une panne avec 401/404
  4. Nouvelles valeurs en cache et utilisées pour les opérations ultérieures
  5. Script se termine avec succès

Exemple

Application du monde réel - Récupération du cache pubkey Keycast :

const cached = await pgDb.getImportedUser(creator.user_id);
let pubkey: string;
let token: string;

if (cached) {
  pubkey = cached.pubkey;
  token = cached.token;

  try {
    const freshToken = await keycast.getTokenByPubkey(pubkey);
    token = freshToken;
    await pgDb.saveImportedUser({ ...cached, token: freshToken });
    console.log(`Pubkey: ${pubkey} (token refreshed)`);
  } catch (refreshError) {
    const errMsg = refreshError instanceof Error ? refreshError.message : String(refreshError);

    if (errMsg.includes("not found")) {
      // Keycast doesn't have this pubkey - cache is stale
      console.log(`Cached pubkey not on Keycast, recreating user...`);

      // Delete stale entry
      await pgDb.deleteImportedUser(creator.user_id);

      // Create fresh user
      const result = await keycast.createPreloadedUser(
        creator.user_id,
        username,
        displayName
      );
      pubkey = result.pubkey;
      token = result.token;
      console.log(`Pubkey: ${pubkey} (recreated)`);

      // Cache new account
      await pgDb.saveImportedUser({
        vine_user_id: creator.user_id,
        username: creator.username,
        pubkey,
        token,
        events_published: 0,
      });
    } else {
      // Other error - user may have claimed account, can't proceed
      console.log(`Token refresh failed: ${errMsg}`);
      console.log(`Skipping ${creator.username} - cannot sign events`);
      continue;
    }
  }
}

Notes

  • Ceci complète local-cache-idempotency-fallback - utilisez les deux ensemble
  • La chaîne d'erreur trompeuse : 404 « not found » → continuer avec token obsolète → 401 « invalid token »
  • Supprimez toujours avant de recréer pour éviter d'accumuler des entrées de cache orphelines
  • Envisagez de logger les entrées qui étaient obsolètes pour le débogage/monitoring
  • Si le service externe perd fréquemment des données, enquêtez sur la cause racine
  • La nouvelle pubkey sera différente de l'ancienne - les systèmes aval peuvent nécessiter des mises à jour

Compétences Connexes

  • local-cache-idempotency-fallback : le motif de base que cette compétence étend
  • Logique de retry de base de données pour les réinitialisations de connexion (préoccupation distincte)

Skills similaires