À son apogée vers 2003, Limewire était installé sur environ un tiers des ordinateurs de bureau du monde, le réseau Gnutella supportant des millions de nœuds simultanés. Le réseau a atteint une mise à l'échelle massive tout en restant entièrement décentralisé et sans index de fichiers globaux ni serveurs de coordination.

Les premières recherches Gnutella utilisaient le routage par inondation : une requête se propageait à chaque pair voisin, qui la transmettait à ses voisins, continuant jusqu'à l'expiration du compteur TTL ou la découverte d'une correspondance. L'élégance de cette approche – tenant entièrement dans l'esprit d'un programmeur – a permis la croissance rapide de l'écosystème et les implémentations de clients diversifiées. Elle a cependant créé un piège d'évolutivité.

Avec chaque pair maintenant généralement quatre connexions, le trafic des requêtes s'est développé géométriquement : un pair envoyant à quatre voisins, chacun transmettant à trois pairs nouveaux (excluant la source), donnant 4, puis 12, puis 36, puis 108 copies transmises. La plupart des destinataires n'avaient rien de pertinent à offrir mais ont supporté le coût total de réception, d'analyse, d'inspection, de déduplication et de transmission. Le réseau a gaspillé la plupart de la bande passante à prouver que les pairs n'avaient pas le fichier demandé. Cela restait tolérable pour des milliers de machines mais est devenu paralysant avec des millions d'utilisateurs simultanés.

D'ici 2003, les limites étaient indéniables. Les clients Gnutella sont passés au Query Routing Protocol (QRP), qui divisait le réseau en deux rôles : les feuilles (pairs ordinaires avec bande passante, disponibilité ou connectivité limitées) et les pairs ultra (nœuds bien approvisionnés avec DSL ou mieux, mémoire suffisante et descripteurs de fichiers, ainsi que haute disponibilité). Les clients pouvaient s'autopromouvoir en fonction des ressources mesurées.

Sous QRP, les feuilles envoyaient régulièrement à chaque pair ultra connecté un résumé compact et approximatif des termes recherchables dans leur bibliothèque de fichiers – pas une liste de fichiers littérale, mais une représentation avec perte répondant à une seule question : ce pair pourrait-il avoir des fichiers correspondant à ces termes de recherche ? La réponse était binaire : peut-être ou définitivement non.

Ce résumé, appelé table QRP, a été implémenté en utilisant des structures ressemblant à des filtres de Bloom. Un client canonicalisait les noms de fichiers partagés en jetons (par exemple, "Ubuntu Server 2024.iso" produisait des termes comme "ubuntu", "server", "2024", "iso" et potentiellement "ubunt", "ubun" pour la correspondance de préfixe). Chaque terme était haché en position de table. Plutôt que de stocker le texte original, l'emplacement enregistrait simplement la présence sous forme de bit. Une table à 65 536 emplacements se comprimait à 8 KiB – une fraction des 365 KiB requis pour stocker 6 300 noms de fichiers en texte brut.

Quand une requête arrivait à un pair ultra, il vérifiait les termes canonicalisés de la requête par rapport à la table QRP de chaque feuille connectée. Si tous les termes correspondaient (ou dépassaient un seuil de correspondance floue), la requête était transmise ; si un terme était définitivement absent, elle était supprimée. La plupart des requêtes évitaient donc de voyager vers les feuilles où aucune correspondance n'était possible, récupérant une bande passante substantielle.

Les tables QRP ont été transmises via deux types de messages. Un message RESET instruisait un pair à rejeter sa table actuelle et initialiser une nouvelle d'une taille spécifiée. Les messages PATCH mettaient ensuite à jour la table de manière incrémentale, représentant les entrées inchangées comme des valeurs sans opération qui se comprimaient bien sur le fil. Cela a permis aux feuilles de maintenir les pairs ultra synchronisés à mesure que les collections de fichiers partagés changeaient, sans réémissions coûteuses et complètes.

L'architecture a accepté les faux positifs (interrogation d'une feuille qui manquait du fichier, gaspillant de la bande passante) pour éviter les faux négatifs (suppression d'une requête qui aurait trouvé un résultat). La construction de la table QRP était donc conservatrice, optimisant le rappel plutôt que la précision.

Les tables QRP ont principalement acheminé les requêtes feuille-vers-pair-ultra. Certains pairs ultra ont soutenu "QRP de dernier saut", fusionnant les tables des feuilles directement connectées et propageant le résumé combiné aux pairs ultra voisins. Cette optimisation a permis aux requêtes d'un seul saut d'être filtrées à la limite du pair quand aucune transmission supplémentaire n'était possible. Les requêtes multi-saut se sont toujours appuyées sur le meilleur jugement des pairs ultra, car les pairs ultra voisins auraient pu transmettre plus loin dans l'épine dorsale.

Gnutella2 a adopté QRP, et le mécanisme reste intégré dans les clients Gnutella survivants aujourd'hui. Le protocole a équilibré la simplicité avec l'efficacité : il ne nécessitait pas de table de hachage distribuée, pas d'index global et pas d'autorité centrale, mais s'adaptait de milliers à des millions de nœuds en routant les requêtes de manière sélective vers les pairs ayant une chance statistique de les répondre.