Coste de una pasada
Medido el 2026-09-11 con el binario real contra la API viva, con todas las
familias encendidas y cada petición registrada con las cabeceras
x-ratelimit-resource y x-ratelimit-used de su propia respuesta. Frío es
la primera pasada de una instalación nueva o de un servicio reiniciado: caché de
ETag vacía, un mes de ejecuciones de workflows. Estable es la tercera pasada
del mismo proceso unos minutos después, cuando cuatro quintas partes de sus
peticiones se respondieron con 304 y no costaron nada; la segunda pasada es la
que paga el relleno que describe la fila de actions. Un 304 es gratis, y solo
existe para REST: GraphQL no lleva ETag, así que su columna es la misma en las
dos pasadas.
Las cifras son por repositorio donde la familia pregunta por repositorio, y por
pasada donde pregunta por la cuenta. core es el cubo de REST, pt un punto
de GraphQL. Las familias con alguna petición que no es core dicen a qué cubo
fue: search (30 por minuto), webhook_deliveries (500 por minuto) y
dependency_sbom (100 por minuto) tienen cada uno el suyo. Una fila que la
ronda del 2026-09-11 cambió describe las peticiones que el código hace ahora y
dice qué midieron las dos pasadas donde difiere. No hay fila de total a
propósito: un total pertenece a una cuenta y envejece con ella, así que
multiplica las filas por repositorio por los repositorios que imprime -list y
tendrás el tuyo.
Por familia
Sección titulada «Por familia»| Familia | Cadencia | Frío | Estable | Notas |
|---|---|---|---|---|
| account | 12h | 1 pt | 1 pt | una consulta: el calendario, los totales, los pins, el bloque de sponsors y las listas de estrellas; 66 KB, nunca comprimida, nunca condicional |
| achievements | 24h | 1 página fuera de presupuesto, 1 pt, más el recorrido de coautoría | lo mismo | las insignias salen de la página pública del perfil, leída sin el token y sin cargarse a ningún cubo. Las filas de progreso de al lado sí salen de la API: una consulta de cuentas, y un recorrido por las pull requests fusionadas cuyos commits en coautoría no expone ninguna cuenta. La búsqueda se detiene a los mil resultados, así que el recorrido pregunta por la vida entera de la cuenta y parte en dos cualquier rango que se pase, un punto por página y uno por partición, que son decenas de puntos en una cuenta antigua y uno en una nueva. Todo ello una vez al día |
| totals | 12h | 1 core, 1 search, 3 pt | 0 core, 1 search, 3 pt | el perfil es un 304 desde la segunda pasada; diez de los once contadores son una consulta GraphQL de diez alias, 406 bytes a coste 1, medida idéntica a las respuestas REST del mismo minuto; solo el contador de commits sigue siendo una búsqueda, cobrada cada vez; una consulta de contadores rechazada se repite contador a contador, diez puntos solo en esa pasada, así que una búsqueda fallida pierde un número, como en REST |
| ratelimit | 15m | 1 pt | 1 pt | GET /rate_limit es gratis; el punto es la mitad GraphQL de la misma pregunta |
| events | 30m | 3 core | 1 core | antes de la ronda: tres páginas de un feed que cambia cada vez, así que If-None-Match nunca acierta. Ahora: el recorrido se detiene en la página que lleva el evento más reciente de la pasada anterior, recordado como last_event en el fichero de estado, que suele ser la primera página: 1 core por pasada, 96 al día menos; una primera pasada y un backfill siguen leyendo las tres |
| notifs | 30m | 20 core | 1 core | antes de la ronda: veinte páginas de cincuenta; una notificación nueva desplaza todas las páginas, así que el ETag nunca acierta, para seis o siete filas nuevas. Ahora: since= el updated_at más reciente visto menos dos cadencias (last_notified en el fichero de estado), que es una página para una ventana de dos horas: 1 core por pasada, y las veinte una vez al día (last_full), en un backfill y en la primera pasada tras una actualización, hilos leídos incluidos (all=true): eso acota a un día lo que el filtro de GitHub haya dejado fuera de las lecturas con ventana, y es la lectura que cierra un hilo, porque una pasada lista solo los hilos no leídos y un hilo leído sin respuesta desaparece de ese listado en vez de volver con su etiqueta unread cambiada. 912 core al día menos |
| billing | 6h | 2 core | 1 core | una por mes recorrido; el mes en curso cambia, el anterior es un 304 |
| profile | 12h | 4 core más 1 por paquete | 0 core | el perfil, las cuentas sociales, los gists, los paquetes y una página de versiones por paquete; todo 304 tras la primera pasada |
| outbound | 12h | 8 pt | 8 pt | todo GraphQL: la lista de estrellas dadas es una consulta de cien (13,5 KB frente a 617 KB descomprimidos por REST), las cinco búsquedas un punto cada una (7 KB frente a 100 KB), los dos recorridos de comentarios uno cada uno; nada de esto lleva ETag por ninguna de las dos vías, así que las dos columnas son iguales |
| history | apagada | 1 pt por año pasado | igual | el calendario entero de la cuenta, una vez, a un punto por año que lleve existiendo |
| keys | 24h | 2 core | 0 core | las claves SSH y GPG |
| traffic | 6h | 4 core por repo | 0 core | visitas, clones, referrers, rutas; la ventana de catorce días es un 304 hasta que se mueve |
| repo | 1h | 3 core por repo, 2 pt por cada 10 repos | 0 core en un repo que no cambió, 2 pt por cada 10 repos | el repositorio, su perfil de comunidad y una página de releases cada uno; lenguajes, temas, rulesets y protección de rama viajan en una consulta GraphQL por cada diez repositorios |
| branches | 24h | 1 pt por cada 14 repos | 1 pt por cada 14 repos | una consulta por cada catorce repositorios |
| stars | 6h | 1 core por repo | 1 pt por cada 10 repos | el recorrido completo por REST la primera vez que se ve un repositorio, y después las cien estrellas más nuevas de cada repositorio en una consulta GraphQL por cada diez, alrededor de un kilobyte por repositorio; un reinicio con fichero de estado empieza en esa consulta. Antes de la ronda se pedía la última página de cada repositorio en cada pasada, todas menos una con 304 |
| issues | 1h | hasta 8 pt por repo | 1 a 2 pt por repo | antes de la ronda: una página de cincuenta con diez hilos de revisión cada uno, 8 puntos, que el gateway rechazaba con un 502 una vez por pasada en el repositorio más activo. Ahora: 2 puntos de GraphQL por repo para lo que cambió en dos cadencias, de diez en diez (1 punto donde el repositorio tiene cinco o menos), y una vez por día UTC una página completa dimensionada al repositorio desde los últimos totales (5, 10, 20 o 50 cuestan 1, 2, 3 u 8). La pasada que lleva la página diaria es la cara, y los repositorios más activos siguen sacando el 502 o 504 del gateway a cincuenta una vez al día y se reintentan a veinticinco |
| issueevents | 1h | varios pt por repo para el mes, más 1 core por pull request en pila | 1 pt por repo, 0 a 1 core | antes de la ronda: una página de cien eventos cada uno, hasta un megabyte por repositorio porque cada evento incrusta su issue entera; un 304 en todos menos el repositorio que se movió. Ahora: una consulta GraphQL por repositorio, la cronología de las diez issues y diez pull requests actualizadas más recientemente para los eventos de las dos últimas cadencias (1 pt, de 2 a 6 KB, una segunda página solo cuando se movieron más de diez), más un core por pull request en una pila, cuyo evento added_to_stack la cronología no sabe nombrar; treinta días en una primera pasada, una vez, que son minutos y no segundos donde las pull requests están casi todas en pila, porque casi todas las actualizadas en el mes van por la vía por ítem a por su added_to_stack; una pasada estable es un punto por repositorio y como mucho una lectura de esas. Y desde una cadencia antes de la última ejecución tras un hueco, para que un proceso parado no deje sus horas fuera de la serie. Medido contra la lista durante una semana de dos repositorios: todos los eventos de todos los tipos coinciden, campo a campo, salvo un commit que referencia una issue que nadie ha tocado, 3 de 2.217, que no mueve la issue y por eso no se pregunta. Un backfill recorre /issues/{n}/events por ítem en vez de la lista, 1,4 KB comprimidos por doce eventos frente a 45 KB por evento |
| actions | 15m | un mes de ejecuciones por repo, más 1 core por ejecución | 1 core por repo que tuvo una ejecución, más 1 por ejecución terminada desde entonces | antes de la ronda: páginas de cien ejecuciones hasta un mes atrás, un listado de jobs por ejecución, y las cachés y workflows de cada repositorio, que eran tres quintas partes de los bytes de la pasada fría, y esos listados de jobs otra vez en cada pasada, casi todos 304. Ahora: páginas de 30, una en un repositorio tranquilo y hasta 7 mientras vengan llenas de ejecuciones más nuevas que la ventana, más una por ejecución aún sin expandir, jobs listados una vez por intento, como mucho 20 ejecuciones nuevas por pasada; páginas de 100 en la primera pasada y en un backfill. Medido en tres pasadas de un mismo proceso, el segundo es el que paga el relleno, porque la página de treinta es una URL nueva para cada repositorio y trae ejecuciones que las veinte del primero no cubrieron; desde ahí una pasada cuesta una página por repositorio que tuvo una ejecución y un listado de jobs por ejecución terminada desde entonces, así que lo que cuesta es cuántas ejecuciones termina la cuenta |
| artifacts | 1h | hasta 5 core por repo | 0 a 5 core por repo | un repositorio activo llena sus páginas, y las cinco se cobran otra vez cada vez que se añadió o caducó un artefacto: 5 en una pasada, 0 en el siguiente |
| security | 1h | 2 core por repo | 0 core | alertas de Dependabot y de code scanning; la mayoría son el 403 de un repositorio con Dependabot apagado o el 404 de uno sin code scanning, y un rechazo no lleva ETag, así que cada uno se volvía a cobrar en cada pasada antes de que la ronda los recordara. Ahora: un rechazo se contesta de memoria durante un día, por familia, repositorio y endpoint, así que la cifra estable es 0 core en la segunda pasada y una petición por rechazo una vez al día. El resto son peticiones condicionales, todas 304 |
| stats | 12h | 3 core por repo | 0 core | participación y punch card; GitHub los recalcula despacio y responde 304 |
| discussions | 2h | 2 pt por repo con foro | igual | antes de la ronda: cincuenta hilos con veinte comentarios y veinte respuestas cada uno, 11 puntos, pedidos a todos los repositorios, incluidos todos los que no tienen foro. Ahora: 2 puntos por repo con foro, los diez hilos actualizados más recientemente con los mismos veinte comentarios y veinte respuestas cada uno (los comentarios llegan del más antiguo al más nuevo, así que una página más corta ahí dejaría de registrar el undécimo comentario de un hilo); un repositorio con el foro apagado no se consulta |
| commits | 1h | 1 pt por repo | 1 pt por repo | las dos últimas cadencias de la rama por defecto; un mes atrás en la primera pasada, que son unos cientos de kilobytes para un repositorio activo |
| activity | 30m | 2 core por repo | 0 a 2 core por repo | el log del repositorio, cien entradas por página; se cobra solo en un repositorio cuyo log se movió, 2 en una pasada y 0 en el siguiente |
| analyses | 6h | 1 core por repo | 0 core | la mayoría son el 403 y el 404 de repositorios sin code scanning, cobrados otra vez en cada pasada antes de la ronda; ahora recordados un día, así que la pasada estable son peticiones condicionales y 0 core |
| forks | 12h | 1 core por repo | 1 pt por cada 10 repos | una página cada uno por REST en la primera pasada de una instalación nueva y en un backfill, y después los cien forks más nuevos de cada repositorio en una consulta GraphQL por cada diez, menos de un kilobyte por repositorio; un repositorio del que el lote informa que tiene más de cien forks se recorre también por REST, porque las estrellas y days_since_push de una fila de fork cambian y el lote no puede refrescar las filas más allá de su página. Antes de la ronda: una petición por repositorio y pasada, todas 304 |
| planning | 6h | 1 pt por repo | 1 pt por repo | etiquetas e hitos |
| joblogs | apagada | 1 core por repo, 1 blob por job fallido | 0 core | las ejecuciones fallidas de la última hora por repositorio, pedidas a una lista filtrada al mes al que puede llegar una reejecución, y un blob por job fallido desde el almacenamiento de objetos, fuera de la cuota de la API; la URL del filtro cambia una vez al día, así que un día cuesta una página cobrada por repositorio y el resto son 304 |
| settings | 6h | 2 core por repo, 1 webhook_deliveries por hook | 0 core, 1 webhook_deliveries por hook que se movió | webhooks, entornos y claves de despliegue; las entregas de cada hook se cobran a su propio cubo |
| rulesets | 24h | 1 core por repo más 1 por ruleset | 0 core | un listado por repositorio y un historial por ruleset, los dos con ETag; nada de eso se cobra un día en que nadie editó un ruleset, cuando la pasada estable hace las mismas preguntas y todas son condicionales |
| inventory | 24h | 4 core por repo | 0 core | la política del token de workflow, los secretos, la configuración de code scanning; los rechazos eran el 403 de los repositorios sin ella, ahora recordados un día, que a esta cadencia es la pasada siguiente de todos modos; el resto son peticiones condicionales, todas 304 |
| deployments | 1h | 1 pt por cada 5 repos | 1 pt por cada 5 repos | una consulta por cada cinco repositorios, los cien despliegues más recientes de cada uno |
| policyfiles | 24h | 1 pt por cada 5 repos | 1 pt por cada 5 repos | una consulta por cada cinco repositorios, el historial de cuatro rutas de cada uno |
| deps | apagada | 1 core y 1 dependency_sbom por repo | 0 core, 1 dependency_sbom por repo que recibió un commit | una lectura de commit y un SBOM por repositorio; el SBOM tiene su propio cubo y la mayoría fueron 404, ahora recordados un día. GitHub regenera el SBOM en cada lectura y su ETag nunca acierta, así que la fotografía solo se toma cuando la cabeza se movió desde la última pasada (la cabeza en sí es un 304 gratis cuando no lo hizo): 0 dependency_sbom en un repositorio sin commit, uno por repositorio que lo recibió, y una de las lecturas de SBOM expiró en el lado de GitHub en cada uno de las tres pasadas medidas |
Lo que pesaba antes de la ronda no era REST sino GraphQL: issues y
discussions eran el 88 % de los puntos, porque las dos consultas estaban
dimensionadas para un backfill y se pedían en cada pasada. La única búsqueda
es el contador de commits de totals, una por pasada contra un presupuesto de
treinta por minuto.
La ronda del 2026-09-11 rebajó varias de estas filas, y la primera medida es
la base contra la que se mide: los listados de jobs de una
ejecución ya expandida no se vuelven a pedir, la página de ejecuciones es de
treinta en una pasada ordinaria, discussions solo se pregunta a los
repositorios con foro y por diez hilos, pulls se dimensiona al repositorio y
se acota a dos cadencias, las notificaciones y el feed de eventos se detienen
en lo que vio la pasada anterior, un 403 o 404 se recuerda un día en vez de
cobrarse otra vez cada hora, y las estrellas y forks más nuevos, la lista de
estrellas dadas, las búsquedas de outbound y diez de los once contadores de
totals pasaron de REST a GraphQL, las mismas filas por un punto cada una en
vez de una petición cada una. Medido otra vez después de la ronda, tres
pasadas de un mismo proceso la tarde del mismo día, la pasada estable cobra
alrededor de un tercio del core y un tercio de los puntos de GraphQL que
cobraba antes, mueve la mitad de los bytes por el cable y responde con 304 unas
cuatro quintas partes de sus peticiones. Proyectado a un día a las cadencias de
fábrica, eso es más o menos una octava parte de las llamadas REST y un tercio
de los puntos, más un listado de jobs por ejecución de workflow terminada, que
es el único término que crece con lo activos que sean los repositorios y no con
cuántos haya. Dos cosas no bajaron. La pasada fría cobra más core que antes,
porque la primera pasada de eventos de issue lee ahora el mes entero por la
cronología y, donde las pull requests están en pila, una lista por ítem para
casi cada pull request actualizada en él: minutos, una vez por proceso. Y un
pasada que ejecuta todas las familias a la vez sigue tardando minutos y no
segundos, porque sus peticiones condicionales cuestan un tercio de segundo cada
una y sus consultas GraphQL nueve décimas, una tras otra; las pasadas que hace
producción son el de 15 minutos y el de cada hora, cada uno una fracción del
total.
GraphQL es el barato, por mucho
Sección titulada «GraphQL es el barato, por mucho»Una consulta devuelve el calendario de contribuciones completo de 366 días, todos los totales de contribución, el desglose de commits por repositorio y las cuentas sociales, por un punto de un presupuesto de cinco mil. Lo mismo por REST serían docenas de llamadas y no incluiría el calendario en absoluto, porque el calendario no existe en ningún otro sitio.
Por eso la familia de cuenta corre con una cadencia de doce horas y aun así no cuesta casi nada, y por eso las familias caras son las de REST que crecen con lo activos que estén los repositorios.
Dos familias crecen con la actividad, no con el tamaño
Sección titulada «Dos familias crecen con la actividad, no con el tamaño»Todo lo demás cuesta un número de llamadas más o menos fijo por repositorio. Dos no:
actionscuesta una página de treinta ejecuciones, hasta siete mientras vengan llenas de ejecuciones más nuevas que la ventana, más una petición por cada ejecución cuyos jobs este proceso aún no ha escrito, como mucho veinte por pasada. Un repositorio con integración continua en cada push genera ejecuciones sin parar; uno tranquilo cuesta esa página, contestada con 304, y nada más.artifactsrecorre hasta cinco páginas por repositorio, y un repositorio activo las llena.
Apagar una familia
Sección titulada «Apagar una familia»Pon su intervalo a 0.
every: families: artifacts: 0 joblogs: 0Una familia apagada no escribe nada y no cuesta nada. Sus paneles del dashboard se quedan vacíos, que es la lectura honesta. Ver cadencias.
Hacer la cuenta para tu propia cuenta
Sección titulada «Hacer la cuenta para tu propia cuenta»La primera pasada es la cara: el recorrido completo de estrellas, un mes de
ejecuciones de workflows y, con every.history puesto, el calendario de
contribuciones de cada año pasado. Después, multiplica las filas por repositorio
de arriba por el número de repositorios que imprime -list, y divide el
presupuesto por hora entre la cadencia.
Una tarjeta se paga como una pasada en frío digan lo que digan las cadencias: la pasada recoge todas las familias, porque cada número que dibuja sale de esa única pasada, y su proceso arranca con la caché de ETags vacía. Así que N tarjetas son N pasadas, y un workflow que dibuja tres paga tres.
La señal de que la suma salió mal es un aviso, no una conjetura:
level=WARN msg="rate limit reserve reached, family skipped" family=actionsDe vez en cuando está bien. En cada pasada significa que las cadencias son demasiado rápidas para el número de repositorios.