Auf seinem Höhepunkt um 2003 war Limewire auf etwa einem Drittel der Desktop-Computer weltweit installiert, wobei das Gnutella-Netzwerk Millionen gleichzeitiger Knoten unterstützte. Das Netzwerk erreichte massive Skalierung, während es vollständig dezentralisiert blieb und ohne globale Dateindizes oder Koordinierungsserver auskam.

Frühe Gnutella-Suchen nutzten Flood Routing: eine Query reiste zu jedem Nachbar-Peer, der sie an seine Nachbarn weiterleitete, bis der TTL-Zähler ablief oder eine Übereinstimmung gefunden wurde. Die Eleganz dieses Ansatzes – vollständig im Kopf eines Programmierers unterzubringen – ermöglichte schnelles Ökosystem-Wachstum und diverse Client-Implementierungen. Es schuf jedoch eine Skalierungsfalle.

Bei typischerweise vier Verbindungen pro Peer expandierte Query-Verkehr geometrisch: ein Peer sendet an vier Nachbarn, jeder leitet an drei neue Peers weiter (die Quelle ausgeschlossen), ergibt 4, dann 12, dann 36, dann 108 weitergeleitet Kopien. Die meisten Empfänger hatten nichts Relevantes anzubieten, trugen aber die volle Last des Empfangs, Parsens, Inspizierens, Deduplizierens und Weiterleitens. Das Netzwerk verschwendete den meisten Bandbreitenverschleiß darauf zu beweisen, dass Peers die angeforderte Datei nicht hatten. Dies blieb für Tausende Maschinen tolerierbar, wurde aber bei Millionen gleichzeitiger Benutzer lähmend.

Bis 2003 wurden die Grenzen unleugbar. Gnutella-Clients wechselten zum Query Routing Protocol (QRP), das das Netzwerk in zwei Rollen aufteilte: Leaves (gewöhnliche Peers mit begrenzter Bandbreite, Verfügbarkeit oder Konnektivität) und Ultrapeers (gut ausgestattete Knoten mit DSL oder besser, ausreichend Speicher und Dateideskriptoren sowie hoher Verfügbarkeit). Clients konnten sich selbst basierend auf gemessenen Ressourcen heraufstufen.

Unter QRP sendeten Leaves regelmäßig an jeden verbundenen Ultrapeer eine kompakte, ungefähre Zusammenfassung durchsuchbarer Begriffe in ihrer Dateibibliothek – keine buchstäbliche Dateiliste, sondern eine verlustbehaftete Darstellung, die eine einzelne Frage beantwortet: könnte dieser Peer Dateien mit diesen Suchbegriffen haben? Die Antwort war binär: vielleicht oder definitiv nicht.

Diese Zusammenfassung, genannt QRP-Tabelle, wurde mit Strukturen implementiert, die Bloom-Filtern ähnelten. Ein Client vereinheitlichte freigegebene Dateinamen in Tokens (z.B. "Ubuntu Server 2024.iso" ergab Begriffe wie "ubuntu", "server", "2024", "iso" und potenziell "ubunt", "ubun" für Präfix-Matching). Jeder Begriff wurde in eine Tabellenposition gehasht. Statt des ursprünglichen Textes speicherte der Slot nur Präsenz als Bit. Eine Tabelle mit 65.536 Slots komprimierte auf 8 KiB – einen Bruchteil der 365 KiB, die zum Speichern von 6.300 Dateinamen als Klartext erforderlich waren.

Wenn eine Query bei einem Ultrapeer ankam, prüfte er die kanonisierten Begriffe der Query gegen die QRP-Tabelle jedes verbundenen Leafs. Wenn alle Begriffe übereinstimmten (oder einen unscharfen Matching-Schwellwert überschritten), wurde die Query weitergeleitet; wenn ein Begriff definitiv abwesend war, wurde sie unterdrückt. Die meisten Queries vermieden daher Reisen zu Leafs, wo keine Übereinstimmung möglich war, und gewannen erhebliche Bandbreite zurück.

QRP-Tabellen wurden über zwei Nachrichtentypen übertragen. Eine RESET-Nachricht wies einen Peer an, seine aktuelle Tabelle zu verwerfen und eine neue in einer bestimmten Größe zu initialisieren. PATCH-Nachrichten aktualisierten die Tabelle dann inkrementell, wobei unveränderliche Einträge als No-Op-Werte dargestellt wurden, die über die Leitung gut komprimierten. Dies ermöglichte Leafs, Ultrapeers synchron zu halten, wenn sich gemeinsame Dateikollektionen änderten, ohne teure vollständige Neuübertragungen.

Die Architektur akzeptierte falsch positive Ergebnisse (Abfragen an einen Leaf ohne die Datei, was Bandbreite verschwendete), um falsch negative Ergebnisse zu vermeiden (Unterdrückung einer Query, die ein Ergebnis gefunden hätte). QRP-Tabellenkonstruktion war daher konservativ und optimierte für Vollständigkeit über Genauigkeit.

QRP-Tabellen leiteten hauptsächlich Leaf-zu-Ultrapeer-Queries. Einige Ultrapeers unterstützten "Last-Hop-QRP", das Tabellen von direkt verbundenen Leafs zusammenführte und die kombinierte Zusammenfassung an benachbarte Ultrapeers weitergab. Diese Optimierung erlaubte Single-Hop-Queries, an der Grenze des Peers gefiltert zu werden, wenn keine weitere Weitergabe möglich war. Multi-Hop-Queries verliefen sich noch auf das beste Urteil der Ultrapeers, da benachbarte Ultrapeers möglicherweise weiter in das Backbone weiterleiteten.

Gnutella2 übernahm QRP, und der Mechanismus bleibt in überlebenden Gnutella-Clients heute eingebettet. Das Protokoll balancierte Einfachheit mit Effizienz: es erforderte keine verteilte Hash-Tabelle, keinen globalen Index und keine zentrale Autorität, skalierte aber von Tausenden auf Millionen Knoten, indem es Queries selektiv an Peers mit statistischer Chance, sie zu beantworten, leitete.