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 Python | Causa probable | Primera cosa a verificar |
|---|---|---|
HTTP 429 con Retry-After o cabeceras de límite de tasa | Limitación del proveedor | Solicitudes por segundo y tamaño de ráfaga |
Error JSON-RPC -32600 / -32700 | JSON mal formado o batch incorrecto | Salida de json.dumps y tipo de contenido |
| Timeouts, reseteos de conexión, 5xx | Problema de endpoint o red | URL del endpoint, DNS, estado del proveedor |
| 429 solo bajo concurrencia | Clave o IP compartida entre workers | Cuántos procesos comparten un endpoint |
429 después de una ráfaga de getProgramAccounts | Costo elevado del método | Mezcla 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,getBalanceygetAccountInfoque 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,accountSubscribeylogsSubscribeenvían actualizaciones a través dewss://solana.api.onfinality.io/public-wsy 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
getProgramAccountssin límites. Son costosas. Filtra condataSliceyfilterspara 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ón | Endpoint público compartido | Nodo dedicado |
|---|---|---|
| Prototipado, bajo volumen de solicitudes | Normalmente está bien | Excesivo |
| Tráfico de producción constante con ráfagas | Puede limitar bajo carga | Capacidad predecible |
Consultas pesadas de getProgramAccounts o de archivo | A menudo limitado | Mejor adaptado |
| Necesidades estrictas de latencia o aislamiento | Recursos compartidos | Recursos aislados |
| Varios equipos en una clave | Riesgo de contención | Separació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
getProgramAccountsfiltrado. - 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.