En su apogeo alrededor de 2003, Limewire estaba instalado en aproximadamente un tercio de las computadoras de escritorio en todo el mundo, con la red Gnutella sosteniendo millones de nodos concurrentes. La red logró una escalabilidad masiva mientras permanecía completamente descentralizada y sin índices de archivos globales ni servidores de coordinación.
Las primeras búsquedas de Gnutella utilizaban enrutamiento por inundación: una consulta viajaba a cada par vecino, que la reenviaba a sus vecinos, continuando hasta que el contador TTL expiraba o se encontraba una coincidencia. La elegancia de este enfoque – cabe completamente en la mente de un programador – permitió el rápido crecimiento del ecosistema e implementaciones diversas de clientes. Sin embargo, creó una trampa de escalabilidad.
Con cada par manteniendo típicamente cuatro conexiones, el tráfico de consultas se expandió geométricamente: un par enviando a cuatro vecinos, cada uno reenviando a tres nuevos pares (excluyendo la fuente), resultando en 4, luego 12, luego 36, luego 108 copias reenviadas. La mayoría de los receptores no tenía nada relevante que ofrecer pero soportaban el costo total de recibir, analizar, inspeccionar, deduplicar y reenviar el mensaje. La red desperdiciaba la mayoría del ancho de banda demostrando que los pares no tenían el archivo solicitado. Esto seguía siendo tolerable para miles de máquinas pero se volvió paralizante con millones de usuarios simultáneos.
Para 2003, las limitaciones eran innegables. Los clientes Gnutella hicieron la transición al Protocolo de enrutamiento de consultas (QRP), que dividía la red en dos roles: hojas (pares ordinarios con ancho de banda, tiempo de actividad o conectividad limitados) y pares ultra (nodos bien aprovisionados con DSL o mejor, suficiente memoria y descriptores de archivo, y alta disponibilidad). Los clientes podían autopromovionarse en función de recursos medidos.
Bajo QRP, las hojas enviaban periódicamente a cada par ultra conectado un resumen compacto y aproximado de términos de búsqueda en su biblioteca de archivos – no una lista de archivos literal, sino una representación con pérdida respondiendo una sola pregunta: ¿podría este par tener archivos que coincidan con estos términos de búsqueda? La respuesta era binaria: quizás o definitivamente no.
Este resumen, llamado tabla QRP, se implementó utilizando estructuras similares a filtros de Bloom. Un cliente canonicalizaba nombres de archivos compartidos en tokens (por ejemplo, "Ubuntu Server 2024.iso" producía términos como "ubuntu", "server", "2024", "iso" y potencialmente "ubunt", "ubun" para coincidencia de prefijo). Cada término se transformaba mediante hash a una posición de tabla. En lugar de almacenar el texto original, la ranura solo registraba la presencia como un bit. Una tabla de 65.536 ranuras se comprimía a 8 KiB – una fracción de los 365 KiB requeridos para almacenar 6.300 nombres de archivo en texto plano.
Cuando una consulta llegaba a un par ultra, verificaba los términos canonicalizados de la consulta contra la tabla QRP de cada hoja conectada. Si todos los términos coincidían (o excedían un umbral de coincidencia difusa), la consulta se reenviaba; si algún término estaba definitivamente ausente, se suprimía. La mayoría de las consultas evitaban viajar a hojas donde no podía existir coincidencia, recuperando ancho de banda sustancial.
Las tablas QRP se transmitían mediante dos tipos de mensajes. Un mensaje RESET indicaba a un par que descartara su tabla actual e inicializara una nueva de tamaño especificado. Los mensajes PATCH actualizaban luego la tabla incrementalmente, representando entradas sin cambios como valores sin operación que se comprimían bien en la red. Esto permitía a las hojas mantener los pares ultra sincronizados a medida que cambiaban las colecciones de archivos compartidos, sin retransmisiones completas costosas.
La arquitectura aceptaba falsos positivos (consultar una hoja que carecía del archivo, desperdiciando algo de ancho de banda) para evitar falsos negativos (suprimir una consulta que habría encontrado un resultado). La construcción de la tabla QRP era por lo tanto conservadora, optimizando la exhaustividad sobre la precisión.
Las tablas QRP enrutaban principalmente consultas de hoja a par ultra. Algunos pares ultra admitían "QRP de último salto", fusionando tablas de hojas directamente conectadas y propagando el resumen combinado a pares ultra vecinos. Esta optimización permitía que las consultas de un solo salto se filtraran en el límite del par cuando no era posible un reenvío adicional. Las consultas de múltiples saltos aún dependían del mejor criterio de los pares ultra, ya que los pares ultra vecinos podrían reenviar más lejos en la red troncal.
Gnutella2 adoptó QRP, y el mecanismo permanece incrustado en los clientes Gnutella supervivientes hoy. El protocolo equilibraba simplicidad con eficiencia: no requería tabla de hash distribuida, sin índice global y sin autoridad central, pero se escalaba de miles a millones de nodos enrutando consultas selectivamente a pares con probabilidad estadística de responderlas.