Ir al contenido

Benchmarking

El comando de benchmark mide operaciones de la API de RouterOS contra un router real. Está pensado para desarrollo y planificación de capacidad, no para despliegues normales.

Compila y ejecuta el benchmark:

Ventana de terminal
go run ./cmd/benchmark

La ruta de configuración es opcional y por defecto es config/test.yaml:

Ventana de terminal
go run ./cmd/benchmark config/test.yaml

El benchmark lee la sección de MikroTik del archivo YAML seleccionado, se conecta a RouterOS y después ejecuta operaciones a nivel de API.

  • Adiciones/eliminaciones individuales de direcciones IPv4 e IPv6 en address-list.
  • Llamadas de búsqueda y listado de address-list.
  • Llamadas de creación, búsqueda y eliminación de reglas de firewall.
  • Tiempos de adición/listado/búsqueda/eliminación secuenciales de lotes IPv4.

El benchmark usa direcciones de prueba de prefijos de documentación como 198.51.100.0/24 y 2001:db8::/32, pero aun así escribe en las address lists y los menús de firewall de RouterOS configurados.

Connected to: lab-router
=== SINGLE OPERATION BENCHMARKS (RouterOS API) ===
Add single IPv4 35ms OK
Find IPv4 (1 entry) 12ms OK
Remove IPv4 by .id 18ms OK
=== BATCH ADD BENCHMARKS (sequential via API) ===
Add 100 IPv4 (sequential) 4s (40ms/ip, failures=0)

Usa los números para comparar versiones de RouterOS, modelos de hardware, API con TLS frente a texto plano y latencia de red.

Para el rendimiento real de arranque del bouncer, prefiere las pruebas funcionales de CAPI, porque la reconciliación usa adiciones masivas basadas en scripts y eliminaciones basadas en pool, no solo llamadas secuenciales a la API.

Estas cifras provienen de la suite de pruebas funcionales de CAPI (tests/functional/) ejecutada contra un MikroTik RB5009, no de la herramienta de operaciones individuales cmd/benchmark anterior. Consulta Arquitectura: Pool de conexiones y Adición masiva basada en scripts para conocer los mecanismos detrás de estos números.

EscenarioResultado
Adición optimista cache-first (la dirección aún no está en el router)~1–3 ms por operación
Adición check-first (listar entradas antes de añadir, evitado por la caché)~400 ms por IP
Adición masiva basada en scripts frente a llamadas secuenciales a la API~97× más rápida para lotes grandes
Bulk-add completo en RB5009, ~22.000 entradas de origen CAPI~29 s de bulk, ~34 s desde el arranque

La diferencia entre cache-first (~1-3 ms) y check-first (~400 ms) es la razón por la que existe la caché de direcciones en memoria descrita en Arquitectura: permite al bouncer saltarse el viaje de ida y vuelta listar-y-añadir de RouterOS para las direcciones que ya conoce.

Para despliegues a escala de CAPI (decenas de miles de entradas), consulta también Blocklists de CAPI y Ajuste de rendimiento para orientación sobre el dimensionado de pool_size.

El ciclo de vida completo, muestreado cada 100 ms

Sección titulada «El ciclo de vida completo, muestreado cada 100 ms»

La gráfica siguiente es una única captura continua de 70 segundos del RB5009 de producción, tomada con un muestreador a 10 Hz que lee /proc/stat en el propio router (ver la nota sobre el método al final de esta página). Cubre los tres estados con los que un operador se encuentra de verdad: el router ocioso con el bouncer parado y la lista de direcciones vacía, la primera reconciliación importando ~22.000 entradas, y el estado tranquilo posterior con la lista completa cargada. La CPU se lee en el eje izquierdo, la RAM en el derecho, y los marcadores discontinuos salen de los propios timestamps del log del bouncer. Cada punto es la media de cinco muestras de 100 ms; los picos sub-segundo alcanzan el 57% en rebanadas sueltas de 100 ms durante el import.

Gráfica de líneas de CPU en porcentaje y RAM en megabytes del router durante 70 segundos: CPU casi ociosa antes de arrancar el bouncer, una meseta sostenida en torno al 31% entre los marcadores de inicio de escrituras y reconciliación completa, y vuelta al estado ocioso; la RAM sube unos 19 MiB al conectar el bouncer y se mantiene plana durante todo el import.Gráfica de líneas de CPU en porcentaje y RAM en megabytes del router durante 70 segundos: CPU casi ociosa antes de arrancar el bouncer, una meseta sostenida en torno al 31% entre los marcadores de inicio de escrituras y reconciliación completa, y vuelta al estado ocioso; la RAM sube unos 19 MiB al conectar el bouncer y se mantiene plana durante todo el import.
Primera reconciliación en un RB5009 con 22.000 entradas. Se regenera con go run ./cmd/perfmon plot desde el dataset versionado.

El import es la meseta: aproximadamente un 31% sostenido durante ~26 segundos, repartido entre los cuatro cores con el pool de conexiones rotando picos de un solo core del 50–100%. La RAM es la parte tranquila de la historia, con una sorpresa en el cuándo: el escalón de ~19 MiB cae al conectar, antes de importar una sola entrada — y 22.000 entradas después no se ha movido más. Mantener la lista le cuesta al router casi nada; la memoria no es donde muerde esta carga, la CPU durante las lecturas de la lista, sí. El tramo ocioso también enseña los propios blips de fondo del router — un dispositivo real nunca es una línea plana.

La serie de 100 ms procede de un contenedor muestreador que corre en el propio router, leyendo /proc/stat y /proc/meminfo del kernel compartido a 10 Hz y empujando lotes directamente a Loki — el cpu-load propio de RouterOS se actualiza solo una vez por segundo, así que los scripts no pueden ver por debajo de eso. El protocolo de captura: parar el bouncer, borrar ambas listas de direcciones, dejar asentar el router, y arrancar el bouncer grabando a través de la primera reconciliación hasta el régimen estable.

Toda la instrumentación se despliega con un comando contra cualquier RouterOS con el paquete de contenedores habilitado:

Ventana de terminal
go run ./cmd/perfmon install -router admin@192.168.88.1 -port 22 \
-loki http://tu-loki:3100

Compila el muestreador en cruzado, empaqueta la imagen del contenedor sin necesitar Docker, crea el veth, el NAT y las pertenencias a listas del firewall de forma idempotente, arranca el contenedor y verifica que las muestras llegan a Loki. uninstall elimina exactamente lo que creó; capture extrae cualquier ventana como CSV; plot redibuja la gráfica de arriba desde el dataset versionado. Cualquier verbo con -h muestra la lista completa de flags.

Dos costumbres separan un número accionable de uno que engaña, y ambas se aprendieron por las malas afinando este bouncer.

Intercala las variantes. Un router no es una máquina en silencio: enruta tráfico, ejecuta su propio trabajo de fondo y deriva a lo largo de los minutos. Medir la variante A un rato y luego la B otro rato atribuye esa deriva al cambio. Altérnalas — A, B, A, B — dentro de la misma conexión y el mismo minuto, y compara muestras emparejadas. La primera comparación in situ de la reducción de proplist se hizo en dos ventanas separadas y dio −2%; la versión intercalada dio −11,2%, con las 48 parejas favoreciendo el mismo lado. El mismo código y el mismo router; solo cambió el calendario.

Desconfía de una curva que no es monótona. Barrer el tamaño de bloque una vez por valor, en secuencia, produjo esto:

bloque 50 6,926s
bloque 100 4,679s
bloque 250 9,760s <- el doble que sus vecinos por ambos lados
bloque 500 5,395s
bloque 1000 6,194s

Ningún modelo de coste produce esa forma.

Reporta una dispersión, no un solo número. El arreglo evidente — intercalar los tamaños entre rondas y quedarse con el mejor tiempo de cada uno — sigue siendo incorrecto, solo que de forma menos evidente. El mínimo es un estimador defendible cuando el ruido es unilateral, pero tira justo la información que decide si la comparación significa algo. Repetido guardando todas las muestras, el mismo barrido dice:

bloquemedianamediaIQR
505,78 s6,86 s1,76 s
1005,06 s5,31 s0,22 s
2505,31 s6,39 s2,47 s
5005,50 s5,77 s0,84 s
10004,36 s5,75 s2,55 s

La dispersión dentro de un mismo tamaño llega a 2,55 s, mientras que la distancia entre el mejor y el peor tamaño es de 1,43 s. El ruido de una variante es mayor que la diferencia entre variantes, así que el barrido no resuelve nada — y, en efecto, qué tamaño “gana” depende del estimador: mediana y mínimo eligen 1.000, la media elige 100. Reportar solo el mejor de tres rondas habría escondido todo eso tras una tabla de aspecto impecable.

La conclusión sobrevivió, porque nunca dependió del ranking: insertar una fila domina, así que ningún tamaño de bloque en este rango mueve el import de forma medible. Pero la primera redacción enunciaba un modelo de coste de dos términos con dos cifras significativas que los datos no sostienen.

Un reflejo útil: antes de creerte un resultado, pregunta qué más cambió entre las dos mediciones. Casi siempre es el tiempo.

  • Confirma que el destino es un router de laboratorio.
  • Usa address lists de prueba dedicadas cuando sea posible.
  • Mantén command_timeout lo bastante alto para dispositivos lentos.
  • Revisa el router tras una interrupción y elimina cualquier comentario benchmark-* si una ejecución se detiene a medias.