Prueba de carga de Orbitra: 480 req/s, cero errores, p99 de 182 ms
Un tracker vive bajo carga: cada clic es una escritura, una comprobación de unicidad y una redirección. Por eso, antes de etiquetar v1.6.3, reconstruimos el pipeline de unicidad sobre una base a escala real y la sometimos a carga escalonada con k6 en nuestro banco de pruebas. Estos son los números.
Por qué hicimos la prueba
v1.6.3 hizo honesto el filtro de duplicados frente al NAT móvil: la clave de unicidad pasó a ser IP + user agent en lugar de solo IP. Tras un mismo NAT de operador, dos personas distintas colisionaban: el segundo visitante recibía un subid sin fila de clic en la base, y su conversión no tenía dónde engancharse.
El cambio exigía una nueva columna ua_hash y un índice compuesto sobre una base que ya llevaba volumen real. Es el tipo de migración más delicado — así que antes de etiquetar repetimos exactamente el camino que recorre una instalación de usuario, con datos a escala de producción.
El banco de pruebas
- Contenedor en la nube: 2 vCPU / 7 GB de RAM, Ubuntu 24.04. nginx 1.24 + PHP-FPM 8.3 con la configuración de fábrica que deja
install.sh— sin ajustes manuales en las ejecuciones base. - SQLite en modo WAL. k6 corre en la misma máquina (~0,3 núcleos de sobrecoste), así que las cifras quedan, si acaso, subestimadas.
- Carga escalonada de tasa de llegada constante: 60 s por escalón, timeout de 5 s, sin seguir redirecciones.
- Campaña: un flujo regular → redirección a la oferta, ventana de unicidad de 24 h por IP + User-Agent. Los únicos usan IP pública aleatoria y UA único; los repetidos, 50 pares fijos de IP/UA.
- Tres bases: ~50 mil clics (sanidad), 3,2 millones (principal) y una instantánea previa a la actualización de 3,9 millones para el ensayo de migración (~1 millón en las últimas 24 horas, ~8 500 clics por IP NAT).
- Ejecuciones por pares: v1.6.2 servía la misma base junto a la versión nueva; tras una ejecución caída esperábamos a que se disipara la cola de FPM.
Resultados
| Ejecución (base de 3,2 M salvo indicación) | req/s | Errores | p99 | CPU |
|---|---|---|---|---|
| Únicos, FPM de fábrica (base ~50 mil) | 480 | 0 | 46 ms | 76% |
| Únicos | 480 | 0 | 75 ms | 77% |
| Visitantes repetidos (50 pares NAT) | 480 | 0, ~1% vía spool | 14 ms | 61% |
| Únicos + panel abierto, informes cada 2 s | 320 | 0 | 587 ms | 81% |
| Únicos, FPM/nginx ajustados, geo activada | 560 | 0 | 187 ms | 94% |
| Únicos, sobrecarga deliberada | 800 | 27% de timeouts | — | 97% |
Y el ensayo de migración sobre la instantánea de 3,9 millones: el primer request tras la «actualización» ejecutó la migración 55 en 0,04 s, una pasada de cron construyó el índice en 4,8 s y rellenó 1 008 085 hashes UA en 29,6 s — y el tráfico NAT a 480 req/s pasó sin errores y con un p99 de 182 ms, todos los subid en la base. La versión anterior perdía 27 775 subid en el mismo escenario; la nueva, cero.
Dos detalles para cerrar. Incluso a 800 req/s —más allá del punto de rotura— cada redirección servida quedó escrita en la base: la sobrecarga degrada la latencia, no la durabilidad. Y la corrección de los hashes tuvo su propia verificación: una muestra de 2 000 filas cotejada contra el crc32 de PHP — cero discrepancias.
La economía: un clic cuesta unos 2,5 ms de CPU, lo que pone el techo de una máquina de 2 vCPU en ~550–600 req/s, limitado por CPU. En 4 vCPU la misma aritmética extrapola a ~1 000 req/s — calculado solo sobre el papel, y etiquetado como tal.
Lo que la prueba de estrés cazó antes del lanzamiento
La mitad del valor de la prueba son los bugs que sacó a la luz. Todos corregidos antes de la etiqueta:
- Cursores sin cerrar en el camino del clic provocaban un SQLITE_BUSY_SNAPSHOT instantáneo — los clics de visitantes repetidos iban al spool en lugar de la base.
- El debounce se basaba solo en la IP — los vecinos de NAT perdían clics y recibían un subid que no existía.
- Los filtros de fecha de los informes usaban date(created_at, tz) e ignoraban el índice — los informes se arrastraban en bases grandes.
- Un INSERT del píxel tenía 23 columnas y 22 valores — los clics del píxel se perdían en silencio.
- Las filas antiguas con hash NULL mantenían el colapso NAT vivo una ventana de unicidad tras la actualización.
Por qué aguanta
- SQLite en modo WAL, un solo archivo, cero servicios extra. No hay ClickHouse, Redis ni MySQL que se coman la RAM — el cuello de botella es el I/O de disco, y escala con tu VPS.
- Una cola de escritura (spool): si SQLite se bloquea un instante, el clic aterriza en una cola en archivo que un cron por minuto vacía — la redirección del visitante no espera y el clic no se pierde.
- Comprobaciones de unicidad por índice: cada clic lleva un hash crc32 compacto del UA, y las cuatro comprobaciones por clic buscan en el índice (ip, ua_hash, created_at) en vez de recorrer todos los clics de una IP.
Advertencias honestas
- Probado sobre el release candidate de v1.6.3. La arquitectura de almacenamiento (SQLite, spool, índices) no ha cambiado en las versiones actuales.
- Nuestro banco no es tu VPS: las cifras absolutas viajan con el hardware. Lo que se traslada son las proporciones y el modo de fallo.
- La cifra de 4 vCPU es una extrapolación, no una medición; lo decimos en vez de redondear hacia arriba.
- Es una prueba de estrés sintética más un ensayo de migración a escala real — no un A/B de tráfico real contra otros trackers.
Qué significa para ti
Un VPS modesto —de la clase que la mayoría de guías recomiendan para empezar— ya tiene margen para volúmenes diarios serios, y arriba no te espera una tarifa por eventos. Si estás en Keitaro por aquello de «SQLite no sobrevivirá», estos son los datos que querías ver.
Orbitra se instala en un servidor Ubuntu limpio en unos 15 minutos — un comando, y la demo en vivo está abierta si quieres probar el panel primero.
FAQ
¿En qué hardware corrió la prueba?
En un contenedor en la nube con 2 vCPU y 7 GB de RAM sobre Ubuntu 24.04, nginx y PHP-FPM con la configuración de fábrica de install.sh, y k6 en la misma máquina — así que las cifras quedan, si acaso, subestimadas. Los valores absolutos siempre dependen de tu VPS: escalas el servidor, no el plan.
¿Aplican estos resultados a la versión actual?
La prueba corrió sobre el release candidate de v1.6.3. La arquitectura de almacenamiento (SQLite + cola de escritura + índices de unicidad) es la misma en las versiones actuales; lo posterior son funciones e interfaz.
¿Por qué SQLite y no ClickHouse?
Un solo archivo, modo WAL, sin servidor de base de datos aparte. En volúmenes bajos y medios el cuello de botella es el I/O de disco, no la RAM, así que un VPS barato rinde mucho. Para millones de clics al día sostenidos, una arquitectura columnar es más fuerte — lo decimos abiertamente.
¿La migración perdió datos?
En el escenario de visitantes repetidos, la versión anterior perdía 27 775 subid; la nueva, cero. Los informes sobre 3,2 millones de clics coincidieron con la versión anterior fila por fila.
También te puede interesar
Alternativa a Voluum: tracker self-hosted gratuito
Voluum empieza en ~$149/mes. Orbitra es un tracker self-hosted gratuito con cloaking integrado y 50+ integraciones CPA. Comparativa y guía de migración.
Leer el artículo →Alternativa a Keitaro: tracker self-hosted gratuito
El competidor self-hosted más cercano — pero la licencia empieza en $79/mes. Comparativa de funciones e importación de campañas en un clic.
Leer el artículo →