¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Límites de uso y reintentos seguros

Después de esta lección: Puedo reintentar una llamada sin causar una tormenta.

Lección de 70 min6 comandosLaboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

Cómo reintentar sin una tormenta

Un límite de uso es una regla por la que un servicio solo acepta cierto número de llamadas en poco tiempo. Un reintento es un segundo intento tras una llamada rechazada. Un reintento descuidado puede empeorar una sobrecarga.

El módulo sobre coste, trazas y fiabilidad reintenta una vez y luego pasa a una alternativa. Esta lección va más allá. Añade esperas que crecen y una dispersión, para que los clientes no se muevan juntos. Añade también un tope de intentos y un reintento que se puede repetir sin riesgo.

Al final de esta lección

Nota

Puedo reintentar una llamada sin causar una tormenta.

Palabras clave

PalabraQué significaEjemplo
Límite de usoel máximo de llamadas que un servicio acepta en una ventana de tiempo
Espera crecienteuna espera que crece tras cada intento fallido400, 800, 1600
Dispersiónuna pequeña espera distinta para cada cliente
Tormentamuchos clientes que reintentan en el mismo instante
Idempotenteseguro de repetir: hacerlo dos veces tiene el efecto de una
Reloj simuladoun número que movemos a mano en vez de esperar

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Cómo cuenta un límite de uso

El servicio corta el tiempo en ventanas de 1000 milisegundos. En cada ventana acepta un número fijo de llamadas, aquí 2. Rechaza el resto hasta que empieza la ventana siguiente.

El tiempo aquí es un número que movemos a mano. Nada espera de verdad, así que cada ejecución da la misma respuesta.

Ejemplo resuelto: 2 llamadas por ventana

Hora (ms)VentanaLlamadas ya aceptadasResultado
000Aceptada
001Aceptada
002Rechazada, la ventana está llena
40002Rechazada otra vez
120010Aceptada, ventana nueva

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Un error frecuente

Advertencia

Mucha gente cree que reintentar al instante es lo más rápido para conseguir una respuesta. Si el servicio está sobrecargado, cada reintento inmediato añade carga y el servicio empeora para todos. Espera primero, y espera más cada vez.

Espera creciente y dispersión

Una espera creciente se duplica. Con una base de 400, las esperas son 400, 800, 1600. Ponle un tope, aquí 2000, o las esperas crecen sin fin.

Si seis clientes fallan juntos y todos esperan 400, todos vuelven juntos. Una dispersión suma un pequeño número distinto a cada cliente, y así vuelven uno a uno.

Ejemplo resuelto: base 400, tope 2000

Intento fallidoEsperaDispersión de la petición 3Siguiente intento en (desde 0)
140034434
2800471281 (desde 434)
31600102891 (desde 1281)
42000, el topeNo se usaSe rinde si los intentos son 4

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Un reintento debe poder repetirse sin riesgo

A veces una llamada funcionó pero la respuesta se perdió por el camino. El cliente cree que falló y lo intenta otra vez. Si el reintento cobra o envía algo de nuevo, lo has hecho dos veces.

Dale una clave a cada acción. El servidor recuerda las claves que ha visto. Una repetición con una clave ya vista no hace nada nuevo.

Escribe el ayudante del modelo, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribe el ayudante del modelo, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"estado \" + res.status);' '    const d = await res.json();' '    return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribe el script de reintentos, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'function delay(attempt, base, cap) { return Math.min(base * Math.pow(2, attempt - 1), cap); }' 'function spread(id, attempt) { return (id * 7 + attempt * 13) % 50; }' 'function run(count, limit, window, base, cap, tries, spreadOn) {' '  const pending = [];' '  const history = {};' '  const counts = {};' '  for (let id = 1; id <= count; id++) { pending.push([0, id, 1]); history[id] = []; }' > retry.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribe el script de reintentos, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  while (pending.length > 0) {' '    pending.sort(function (a, b) { return a[0] - b[0] || a[1] - b[1] || a[2] - b[2]; });' '    const item = pending.shift();' '    const slot = Math.floor(item[0] / window);' '    history[item[1]].push(item[0]);' '    counts[slot] = counts[slot] || 0;' '    if (counts[slot] < limit) { counts[slot] = counts[slot] + 1; continue; }' '    if (item[2] < tries) {' '      const extra = spreadOn ? spread(item[1], item[2]) : 0;' '      pending.push([item[0] + delay(item[2], base, cap) + extra, item[1], item[2] + 1]);' '    }' '  }' '  return history;' '}' >> retry.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribe el script de reintentos, parte 3

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  for (const on of [false, true]) {' '    const h = run(6, 2, 1000, 400, 2000, 4, on);' '    const seen = {};' '    let worst = 0;' '    for (const id in h) h[id].slice(1).forEach(function (t) { seen[t] = (seen[t] || 0) + 1; worst = Math.max(worst, seen[t]); });' '    console.log((on ? \"con dispersión\" : \"sin dispersión\") + \": máximo de reintentos en un milisegundo \" + worst);' '    console.log(\"  la petición 3 probó en \" + h[3].join(\" \") + \", la 5 en \" + h[5].join(\" \"));' '  }' '  const reply = await ask([{ role: \"user\", content: \"En una frase corta, ¿por qué añadir una espera aleatoria a un reintento?\" }], \"Para que muchos clientes no reintenten a la vez y no vuelvan a sobrecargar el servicio.\", 60);' '  console.log(\"el modelo dijo: \" + reply.text);' '}' 'main();' >> retry.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Ejecuta los reintentos

Comando
docker exec -w /home/node/project agent node retry.js

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Lo que imprime el Run

LíneaQué muestra
sin dispersión: máximo de reintentos en un milisegundo 4Cuatro clientes volvieron en el mismo instante
la petición 3 probó en 0 400 1200, la 5 en 0 400 1200 2800Sin dispersión las horas son ordenadas e iguales
con dispersión: máximo de reintentos en un milisegundo 1Ningún par de reintentos comparte instante
la petición 3 probó en 0 434 1281 2891, la 5 en 0 448 1259 2883Cada cliente espera algo distinto
el modelo dijo: ...Una frase del laboratorio. Una muestra grabada si ningún modelo responde

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Sobre este laboratorio y su código: Laboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.