fastly-kv-concurrent-write-retry

Par divinevideo · divine-mobile

Corrigez les erreurs 500 intermittentes avec "Failed to store" dans le KV Store Fastly lors d'écritures concurrentes. À utiliser quand : (1) les opérations par lot échouent avec un taux d'erreur de ~10 à 20 %, (2) l'erreur contient "Failed to store list" ou des échecs d'écriture KV similaires, (3) plusieurs requêtes mettent à jour la même clé KV simultanément, (4) le pattern lecture-modification-écriture est utilisé sans verrouillage. La solution consiste à ajouter une boucle de réessai qui relit les données avant chaque tentative d'écriture.

npx skills add https://github.com/divinevideo/divine-mobile --skill fastly-kv-concurrent-write-retry

Fastly KV Store Race Condition sur Écritures Concurrentes

Problème

Quand plusieurs requêtes concurrentes effectuent des opérations lecture-modification-écriture sur la même clé Fastly KV Store, les écritures peuvent échouer avec des erreurs 500 intermittentes. Cela se produit couramment lors de chargements en lot où chaque requête met à jour une liste ou un compteur partagé.

Contexte / Conditions de Déclenchement

  • Le message d'erreur contient « Failed to store list: » ou une erreur KV write similaire
  • Taux d'échec de ~10-20% lors d'opérations concurrentes
  • Plusieurs requêtes du même utilisateur/session mettant à jour l'état partagé
  • Modèle : read key → modify data → write key sans aucun verrouillage

Exemple d'erreur :

{"error":"Failed to store list:"}

Solution

Ajoutez une boucle de retry qui relit les données avant chaque tentative d'écriture :

/// Ajouter un élément à la liste avec retry pour les conflits d'écriture concurrente
pub fn add_to_list(key: &str, item: &str) -> Result<()> {
    // Retry jusqu'à 5 fois pour les conflits d'écriture concurrente
    for attempt in 0..5 {
        // Relire l'état courant à chaque tentative
        let mut items = get_list(key)?;

        if items.contains(&item.to_string()) {
            return Ok(()); // Existe déjà, terminé
        }

        items.push(item.to_string());

        match put_list(key, &items) {
            Ok(()) => return Ok(()),
            Err(e) if attempt < 4 => {
                eprintln!("[KV] Retry {} for list update: {}", attempt + 1, e);
                // La relecture capte les écritures concurrentes
                continue;
            }
            Err(e) => return Err(e),
        }
    }

    Err(Error::new("Max retries exceeded for list update"))
}

Points clés :

  1. Relire à chaque retry - La lecture fraîche capte les changements des écritures concurrentes
  2. Vérifier les doublons - Éviter d'ajouter deux fois le même élément
  3. Logger les retries - Aide à déboguer si les problèmes persistent
  4. Limiter les retries - 5 tentatives suffisent généralement

Vérification

  • Les opérations en lot qui échouaient précédemment ~16% doivent maintenant réussir ~100%
  • Les messages de log de retry ([KV] Retry N for...) indiquent que le mécanisme fonctionne
  • En cas d'échec après 5 retries, il peut y avoir un problème plus profond

Exemple

Avant (race condition) :

pub fn add_to_user_list(pubkey: &str, hash: &str) -> Result<()> {
    let mut hashes = get_user_blobs(pubkey)?;  // Read
    if !hashes.contains(&hash) {
        hashes.push(hash.to_string());         // Modify
        put_user_list(pubkey, &hashes)?;       // Write - CONFLICT!
    }
    Ok(())
}

Après (avec retry) :

pub fn add_to_user_list(pubkey: &str, hash: &str) -> Result<()> {
    for attempt in 0..5 {
        let mut hashes = get_user_blobs(pubkey)?;
        if hashes.contains(&hash) {
            return Ok(());
        }
        hashes.push(hash.to_string());
        match put_user_list(pubkey, &hashes) {
            Ok(()) => return Ok(()),
            Err(e) if attempt < 4 => continue,
            Err(e) => return Err(e),
        }
    }
    Err(Error::new("Max retries exceeded"))
}

Notes

  • Fastly Compute n'a pas de sleep(), donc les retries se font immédiatement
  • La relecture fournit le « délai » en capturant les changements concurrents
  • Envisagez des opérations atomiques si disponibles (Fastly KV ne supporte pas CAS)
  • Pour les scénarios de forte contention, envisagez le sharding de l'espace de clés
  • Ce modèle s'applique aussi aux opérations de suppression (read-filter-write)

Références

Skills similaires