Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
RPC Assistant

¿Por qué mi llamada JSON-RPC de Solana en Python devuelve 429 Too Many Requests?

Resumen

Un 429 de un endpoint JSON-RPC de Solana significa que el proveedor está limitando tu tasa de solicitudes, no que tu payload JSON esté mal formado. Los clientes de Python suelen encontrarse con esto cuando hacen polling en bucles ajustados, agrupan demasiado agresivamente o comparten un endpoint público entre muchos workers. La solución suele ser una combinación de backoff en el lado del cliente, modelado de solicitudes y migrar a un endpoint con capacidad que coincida con tu carga de trabajo. Este artículo muestra cómo leer el error, aplicar reintentos y jitter en Python, reducir el volumen de solicitudes y decidir cuándo un endpoint compartido o dedicado es el siguiente paso correcto.

Una respuesta 429 de un endpoint JSON-RPC de Solana es una señal de límite de tasa del proveedor, no un problema con tu cuerpo JSON. El servidor entendió tu solicitud y eligió rechazarla porque excediste un límite asociado a tu clave API, IP o plan. En Python esto suele aparecer como requests.exceptions.HTTPError: 429 Client Error: Too Many Requests o como un objeto de error JSON-RPC con código -32429 en algunos proveedores. La pregunta práctica no es "qué significa 429" sino "qué debo cambiar a continuación": backoff, modelado de solicitudes o un endpoint diferente.

Diagnostica primero: ¿es limitación de tasa, JSON incorrecto o un endpoint caído?

Antes de reescribir código, confirma qué estás encontrando realmente. Los tres fallos se ven similares en los logs pero necesitan soluciones diferentes.

Síntoma en PythonCausa probablePrimera cosa a verificar
HTTP 429 con Retry-After o cabeceras de límite de tasaLimitación del proveedorSolicitudes por segundo y tamaño de ráfaga
Error JSON-RPC -32600 / -32700JSON mal formado o batch incorrectoSalida de json.dumps y tipo de contenido
Timeouts, reseteos de conexión, 5xxProblema de endpoint o redURL del endpoint, DNS, estado del proveedor
429 solo bajo concurrenciaClave o IP compartida entre workersCuántos procesos comparten un endpoint
429 después de una ráfaga de getProgramAccountsCosto elevado del métodoMezcla de métodos, no solo el conteo de solicitudes

Una prueba rápida con curl aísla el endpoint de tu código Python:

curl -s -o /dev/null -w "%{http_code}\n" \
  -X POST https://solana.api.onfinality.io/public \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}'

Si una sola solicitud devuelve 200 pero tu script devuelve 429, el problema es tu patrón de solicitudes, no el endpoint. Si incluso una sola solicitud devuelve 429, estás compartiendo una clave o IP limitada con otro tráfico.

Leer correctamente el cuerpo de la respuesta 429

Muchos proveedores de Solana devuelven un objeto de error JSON-RPC en lugar de un error HTTP simple. Analiza ambas capas en Python para no ocultar la causa real.

import requests

ENDPOINT = "https://solana.api.onfinality.io/public"
payload = {"jsonrpc": "2.0", "id": 1, "method": "getSlot"}

resp = requests.post(ENDPOINT, json=payload, timeout=10)

if resp.status_code == 429:
    retry_after = resp.headers.get("Retry-After")
    print("Throttled. Retry-After:", retry_after)
    print("Body:", resp.text[:300])
else:
    data = resp.json()
    if "error" in data:
        print("JSON-RPC error:", data["error"])
    else:
        print("Slot:", data["result"])

Registra el código de estado, la cabecera Retry-After cuando esté presente y el código de error JSON-RPC. Esa combinación te dice si el límite es por segundo, por minuto o ponderado por método.

Solución 1: backoff exponencial con jitter en Python

Reintentar inmediatamente empeora la limitación. Usa backoff exponencial con jitter y limita el número de intentos para que un 429 persistente falle ruidosamente en lugar de colgarse.

import random
import time
import requests

ENDPOINT = "https://solana.api.onfinality.io/public"

def rpc_call(method, params=None, max_attempts=5):
    payload = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params or []}
    for attempt in range(max_attempts):
        resp = requests.post(ENDPOINT, json=payload, timeout=10)
        if resp.status_code == 429:
            retry_after = resp.headers.get("Retry-After")
            wait = float(retry_after) if retry_after else min(2 ** attempt, 30)
            wait += random.uniform(0, 0.5)  # jitter
            time.sleep(wait)
            continue
        resp.raise_for_status()
        data = resp.json()
        if "error" in data:
            raise RuntimeError(data["error"])
        return data["result"]
    raise RuntimeError("Exceeded retry attempts after repeated 429s")

El jitter importa cuando muchos workers reintentan a la vez. Sin él, tus procesos se sincronizan y vuelven a activar el mismo límite.

Solución 2: reduce el volumen de solicitudes antes de cambiar de proveedor

La limitación suele ser un síntoma de polling ineficiente. Antes de actualizar la infraestructura, verifica si puedes enviar menos solicitudes y mejores.

  • Agrupa llamadas independientes. Solana JSON-RPC acepta arrays de solicitudes. Agrupa llamadas getSlot, getBalance y getAccountInfo que no dependan entre sí. Mantén los lotes modestos: los lotes muy grandes pueden ser rechazados o agotar el tiempo de espera.
  • Usa suscripciones WebSocket en lugar de polling. slotSubscribe, accountSubscribe y logsSubscribe envían actualizaciones a través de wss://solana.api.onfinality.io/public-ws y eliminan la necesidad de bucles de polling ajustados.
  • Cachea datos estables. Los metadatos de tokens, los IDs de programas y la información de epoch cambian lentamente. Cachealos en lugar de volver a obtenerlos en cada solicitud.
  • Evita getProgramAccounts sin límites. Son costosas. Filtra con dataSlice y filters para que el nodo haga menos trabajo por llamada.
  • Escalona las ráfagas de inicio. Los servicios que disparan cientos de llamadas al arrancar parecen un ataque para un limitador de tasa. Agrega un pequeño retardo de rampa.

Aquí hay un patrón de llamada por lotes:

batch = [
    {"jsonrpc": "2.0", "id": 1, "method": "getSlot"},
    {"jsonrpc": "2.0", "id": 2, "method": "getVersion"},
    {"jsonrpc": "2.0", "id": 3, "method": "getHealth"},
]
resp = requests.post(ENDPOINT, json=batch, timeout=10)
for item in resp.json():
    print(item.get("id"), item.get("result") or item.get("error"))

Solución 3: separa claves y workers

Si varios servicios, contenedores o trabajos de CI comparten una clave API o una IP pública, compiten por el mismo límite. Asigna a cada carga de trabajo su propia clave cuando el proveedor lo permita y evita ejecutar suites de pruebas contra el mismo endpoint que el tráfico de producción. Un solo bucle descontrolado puede limitar todo lo que esté detrás de la misma clave.

Cuándo migrar de un endpoint público compartido

Las soluciones del lado del cliente tienen límites. Si ya has aplicado batching, caché y backoff, y aún recibes 429 durante el tráfico normal, la capacidad del endpoint ya no coincide con tu carga de trabajo. En ese punto la decisión es sobre el tipo de endpoint, no sobre el código.

SituaciónEndpoint público compartidoNodo dedicado
Prototipado, bajo volumen de solicitudesNormalmente está bienExcesivo
Tráfico de producción constante con ráfagasPuede limitar bajo cargaCapacidad predecible
Consultas pesadas de getProgramAccounts o de archivoA menudo limitadoMejor adaptado
Necesidades estrictas de latencia o aislamientoRecursos compartidosRecursos aislados
Varios equipos en una claveRiesgo de contenciónSeparación más limpia

OnFinality proporciona Solana RPC tanto a través de un servicio RPC API compartido como de nodos dedicados cuando necesitas capacidad aislada. Puedes revisar la página de la red Solana para transportes compatibles y detalles del endpoint, y comparar planes en Precios RPC. Si aún estás evaluando opciones, la guía de selección de proveedor cubre los criterios que importan para cargas de trabajo en producción.

Un cliente Python mínimo listo para producción

Combina backoff, batching y una sesión con pooling de conexiones. Reutiliza la sesión para no abrir una nueva conexión TCP por llamada.

import random
import time
import requests

class SolanaRPC:
    def __init__(self, endpoint, max_attempts=5):
        self.endpoint = endpoint
        self.max_attempts = max_attempts
        self.session = requests.Session()

    def call(self, method, params=None):
        payload = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params or []}
        for attempt in range(self.max_attempts):
            resp = self.session.post(self.endpoint, json=payload, timeout=10)
            if resp.status_code == 429:
                wait = float(resp.headers.get("Retry-After", min(2 ** attempt, 30)))
                time.sleep(wait + random.uniform(0, 0.5))
                continue
            resp.raise_for_status()
            data = resp.json()
            if "error" in data:
                raise RuntimeError(data["error"])
            return data["result"]
        raise RuntimeError("Repeated 429 responses")

rpc = SolanaRPC("https://solana.api.onfinality.io/public")
print(rpc.call("getSlot"))

Monitoreo para que los 429 no te sorprendan

Rastrea la tasa de limitación como una métrica de primer nivel, no solo como un conteo de errores. Señales útiles:

  • Respuestas 429 por minuto, divididas por método.
  • Intentos de reintento por llamada exitosa.
  • Latencia p95 y p99 para los métodos más pesados.
  • Frecuencia de reconexión de WebSocket si usas suscripciones.

Alerta cuando los 429 aumenten durante el tráfico normal, no solo durante picos conocidos. Una tendencia ascendente lenta suele significar que tu carga de trabajo superó el límite del endpoint.

Puntos clave

  • Un 429 de Solana es una señal de límite de tasa del proveedor, no un error de formato JSON.
  • Analiza tanto el estado HTTP como el cuerpo de error JSON-RPC en Python.
  • Usa backoff exponencial con jitter y un límite de reintentos.
  • Reduce el volumen con batching, suscripciones WebSocket, caché y getProgramAccounts filtrado.
  • Separa claves API y workers para que un bucle no pueda limitar todo.
  • Si la limitación persiste después de las soluciones del lado del cliente, evalúa un endpoint compartido o dedicado que coincida con tu carga de trabajo.

Preguntas frecuentes

¿Un 429 significa que mi JSON es inválido?

No. Un JSON inválido normalmente devuelve un error de análisis como -32700. Un 429 significa que la solicitud se entendió pero se rechazó debido a límites de tasa.

¿Cuánto tiempo debo esperar antes de reintentar?

Respeta la cabecera Retry-After cuando esté presente. De lo contrario, comienza con un retardo corto y auméntalo exponencialmente, con un máximo razonable y jitter aleatorio.

¿El batching puede empeorar los 429?

Los lotes grandes pueden contar como muchas solicitudes o exceder los límites de payload. Mantén los lotes modestos y divide los métodos pesados en llamadas separadas.

¿Debería cambiar de proveedor en el momento en que veo un 429?

No. Primero confirma que no es un problema de patrón del lado del cliente. Si la limitación continúa bajo tráfico normal y optimizado, entonces compara la capacidad del endpoint y considera un nodo dedicado.

¿Las suscripciones WebSocket evitan los 429?

Reducen el polling, lo que disminuye el volumen de solicitudes, pero tienen sus propios límites de conexión. Úsalas junto con el backoff, no en lugar de él.

Base de conocimiento RPC

Detalles RPC relacionados

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar