ndk-batch-event-queries

Par divinevideo · divine-mobile

Optimise les requêtes vers les relais Nostr grâce au batch fetching de NDK. À utiliser dans les cas suivants : (1) la vérification de l'existence de nombreux événements un par un est lente, (2) une boucle avec des appels `fetchEvents` individuels provoque un problème de requête N+1, (3) besoin de vérifier l'existence de plusieurs événements adressables (Kind 30000+). La méthode `fetchEvents` de NDK accepte des tableaux pour les filtres de tags (`#d`, `#p`, `#e`, `authors`), ce qui permet des requêtes en batch et réduit des centaines d'allers-retours à une seule requête.

npx skills add https://github.com/divinevideo/divine-mobile --skill ndk-batch-event-queries

Requêtes NDK par lot d'événements

Problème

Lors de la vérification de l'existence de nombreux événements Nostr (par exemple, 300 vidéos pour un utilisateur), les requêtes une par une causent des centaines d'allers-retours séquentiels avec les relais, rendant l'opération extrêmement lente (des minutes au lieu de secondes).

Contexte / Conditions de déclenchement

  • Boucle avec await fetchEvents() à l'intérieur, vérification des événements individuellement
  • Le traitement prend des minutes alors qu'il devrait prendre des secondes
  • Vérification de l'existence d'événements adressables (Kind 30000-39999) par d-tag
  • Besoin de déterminer quels éléments d'une liste existent déjà sur un relais

Solution

Le filtre fetchEvents de NDK accepte des tableaux pour la plupart des champs. Au lieu de :

// LENT : 300 requêtes séquentielles
for (const id of vineIds) {
  const exists = await ndk.fetchEvents({
    kinds: [34236],
    authors: [pubkey],
    "#d": [id],  // Valeur unique
  });
}

Utilisez une requête par lot :

// RAPIDE : 1-3 requêtes (segmentées si nécessaire)
const CHUNK_SIZE = 100;  // Les relais peuvent limiter la taille des requêtes
const existingIds = new Set<string>();

for (let i = 0; i < vineIds.length; i += CHUNK_SIZE) {
  const chunk = vineIds.slice(i, i + CHUNK_SIZE);
  const events = await ndk.fetchEvents({
    kinds: [34236],
    authors: pubkeys,  // Peut aussi être un tableau
    "#d": chunk,       // Tableau de valeurs d-tag
  });

  for (const event of events) {
    const dTag = event.tags.find(t => t[0] === "d");
    if (dTag?.[1]) existingIds.add(dTag[1]);
  }
}

// Recherche O(1) dans la boucle de traitement
for (const id of vineIds) {
  if (existingIds.has(id)) continue;  // Ignorer les existants
  // Traiter les nouveaux éléments...
}

Points clés

  1. Filtres de tableau : #d, #p, #e, authors acceptent tous des tableaux
  2. Taille des chunks : Utilisez 50-100 éléments par requête pour éviter les limites des relais
  3. Plusieurs auteurs : Passez un tableau de pubkeys si vous vérifiez entre utilisateurs
  4. Extraire les résultats : Analysez le d-tag des événements retournés pour construire un Set

Vérification

  • Le temps de traitement passe de minutes à secondes
  • Le nombre total de connexions aux relais diminue considérablement
  • Mêmes résultats que les requêtes individuelles (vérifié par comparaison)

Exemple

Application réelle - vérification de 294 vidéos sur 2 pubkeys :

async videosExistBatch(pubkeys: string[], vineIds: string[]): Promise<Set<string>> {
  await this.connect();
  const existingIds = new Set<string>();
  const CHUNK_SIZE = 100;

  for (let i = 0; i < vineIds.length; i += CHUNK_SIZE) {
    const chunk = vineIds.slice(i, i + CHUNK_SIZE);
    const events = await this.ndk.fetchEvents({
      kinds: [34236],
      authors: pubkeys,
      "#d": chunk,
    });

    for (const event of events) {
      const dTag = event.tags.find((t) => t[0] === "d");
      if (dTag && dTag[1]) {
        existingIds.add(dTag[1]);
      }
    }
  }

  return existingIds;
}

Utilisation :

const vineIds = vines.map(v => v.vine_id);
const existingVineIds = await relay.videosExistBatch([pubkey, oldPubkey], vineIds);
console.log(`Found ${existingVineIds.size}/${vineIds.length} already on relay`);

Notes

  • Certains relais peuvent avoir des limites plus strictes sur la taille des requêtes ; ajustez CHUNK_SIZE en conséquence
  • La fonctionnalité de requêtes mises en buffer dans NDK peut également aider au regroupement au niveau des composants
  • Pour des ensembles très volumineux, envisagez des requêtes de chunks parallèles avec Promise.all
  • Ce pattern fonctionne pour toute recherche basée sur des tags, pas seulement les d-tags

Références

Skills similaires