feat: Update docker-compose configuration to use environment variables for sensitive data, enhance testing documentation with new LaTeX files for capacity planning, and refine README for clarity on compilation and testing processes.
This commit is contained in:
@@ -2,15 +2,20 @@
|
||||
\label{ch:resumen}
|
||||
|
||||
\section{Veredicto global}
|
||||
La campa\~na de pruebas del \textbf{13 de julio de 2026} sobre el stack IPSExtractor desplegado en el host \texttt{ubuntu} (perfil \texttt{shared8g}) concluye:
|
||||
La campa\~na de pruebas (carga 13-jul-2026 + LLM/ARQ 17-jul-2026) sobre el stack
|
||||
IPSExtractor en el host \texttt{ubuntu} (perfil \texttt{shared8g}) concluye:
|
||||
|
||||
\begin{center}
|
||||
\large
|
||||
Stack \textbf{operativo y estable} para orquestaci\'on y control remoto del bot.\\[0.3em]
|
||||
\normalsize
|
||||
Carga ligera del gateway y heartbeats: \pass\ bajo contenci\'on moderada.\\[0.2em]
|
||||
Pipeline completo a \textbf{100\,000 facturas/mes} en este host: \fail\ (ver cap.~\ref{ch:capacidad}).
|
||||
\end{center}
|
||||
\callout{\large Stack operativo: orquestador y agent}{%
|
||||
Gateway y heartbeats del orquestador \texttt{bot-descarga} (ST-BOT-*): \pass\ bajo contenci\'on moderada.\\
|
||||
Unitarias agent Selenium / API \texttt{bot-descarga}: \pass.\\
|
||||
Vitest desktop: \pass\ (25/25).\\
|
||||
LLM ST-EXT-02 (23 PDFs): \pass\ --- p50 7{,}1\,s $\approx$506 fact/h.\\
|
||||
Cola ARQ ST-EXT-01 (30 docs, mock): \pass\ --- drenado en 22{,}5\,s.\\
|
||||
OCR ST-COUNT-01 (9 PDFs, 3 conc.): \pass\ --- p50 35{,}8\,s; $\approx$325 fact/h obs.\\
|
||||
E2E-07 (agent offline $\rightarrow$ 409): \pass.\\[0.3em]
|
||||
\textbf{100\,000 facturas/mes} en este host: \fail\ del pipeline completo\\
|
||||
(detalle en el documento \emph{Capacidad 100k/mes}).
|
||||
}
|
||||
|
||||
\section{KPIs de la campa\~na}
|
||||
\begin{table}[ht]
|
||||
@@ -22,20 +27,35 @@ Pipeline completo a \textbf{100\,000 facturas/mes} en este host: \fail\ (ver cap
|
||||
ST-GW-01 p95 & 40{,}85\,ms & $<$500\,ms & \pass \\
|
||||
ST-GW-01 fail rate & 0\,\% & $<$1\,\% & \pass \\
|
||||
ST-BOT-01 p95 (reintento) & 85{,}55\,ms & $<$800\,ms & \pass \\
|
||||
Unitarias agent bot & 19/19 & 100\,\% & \pass \\
|
||||
Unitarias API bot store & 7/7 & 100\,\% & \pass \\
|
||||
Unitarias agent Selenium & 19/19 & 100\,\% & \pass \\
|
||||
Unitarias API \texttt{bot-descarga} (orquestador) & 7/7 & 100\,\% & \pass \\
|
||||
Unitarias count-api (pipeline) & 3/3 & 100\,\% & \pass \\
|
||||
ST-EXT-02 LLM p50 & 7{,}114\,s & --- & $\approx$506 fact/h \\
|
||||
ST-EXT-01 ARQ mock & 30/30 drain & --- & 22{,}5\,s; $\approx$4\,5k docs/h cola \\
|
||||
ST-COUNT-01 OCR & p50 35{,}8\,s & --- & 9/9; $\approx$325 fact/h obs. \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{KPIs ejecutivos de la campa\~na.}
|
||||
\caption{KPIs (carga 13-jul; LLM/ARQ/OCR 17-jul).}
|
||||
\end{table}
|
||||
|
||||
\section{Respuesta directa: 100\,000 facturas/mes}
|
||||
\section{Capacidad 100k/mes (resumen)}
|
||||
\begin{description}
|
||||
\item[Host actual (8\,GiB + MLflow/n8n/MinIO/\ldots)] \fail\ para el \emph{pipeline completo} (conteo + extracci\'on + bot + limpieza).
|
||||
\item[Infra dedicada dimensionada] \pass\ \emph{condicional}: workers LLM, OCR con RAM $\geq$16--32\,GiB, $N$ agents Selenium, cuota API, sin zoo Docker competidor.
|
||||
\item[Host actual] \parcial\ / \fail\ --- pipeline $\approx$ OCR $\sim$100--325/h; 139/h no garantizado.
|
||||
\item[Infra dedicada] \pass\ condicional (OCR, workers, $N$ agents, cuota LLM).
|
||||
\item[Coste LLM] $\approx$ USD\,2\,570/mes si 100k pasan a extracci\'on (gpt-5.4 mini).
|
||||
\end{description}
|
||||
Detalle cuantitativo en el cap\'itulo~\ref{ch:capacidad}.
|
||||
El modelo por etapa, gr\'aficas y requisitos est\'an en el PDF
|
||||
\textbf{Capacidad100k\_IPSExtractor.pdf} (carpeta \texttt{docs/plan\_pruebas/capacidad\_100k/}).
|
||||
|
||||
\section{Riesgo principal}
|
||||
Contenci\'on de plataforma: swap $\approx$2{,}1\,GiB y \texttt{mlflow-server} $\approx$1{,}84\,GiB provocaron un outlier de 23\,s en heartbeats (corrida 1). Tras aliviar carga, p95 cay\'o a 86\,ms. Los fallos de latencia deben correlacionarse siempre con \texttt{free -h} / \texttt{docker stats}.
|
||||
Contenci\'on de plataforma: swap $\approx$2{,}1\,GiB y \texttt{mlflow-server} $\approx$1{,}84\,GiB
|
||||
provocaron un outlier de 23\,s en heartbeats (corrida 1). Tras aliviar carga, p95 cay\'o a 86\,ms.
|
||||
Correlacionar siempre latencias con \texttt{free -h} / \texttt{docker stats}.
|
||||
|
||||
\section{Alcance de esta campa\~na}
|
||||
\textbf{Incluido:} stack Docker IPS, perfil shared8g, unitarias agent Selenium / orquestador \texttt{bot-descarga} / count / Vitest desktop (25/25),
|
||||
humo gateway, E2E-07 (worker offline $\rightarrow$ 409), k6 ST-GW-01/ST-BOT-01 (orquestador),
|
||||
bench LLM ST-EXT-02 (23 PDFs), cola ARQ ST-EXT-01 (30 docs, mock),
|
||||
ST-COUNT-01 (9 PDFs reales, 3 concurrentes).\\
|
||||
\textbf{Fuera de alcance:} ST-COUNT-01 lab agresivo, ST-EXT-01 lab (100--500), E2E Imaging/VPN,
|
||||
ST-BOT-03 events, procesar 100k PDFs reales.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
\chapter{Arquitectura y patrones (contexto)}
|
||||
\label{ch:arq-informe}
|
||||
|
||||
Este cap\'itulo resume c\'omo est\'a construido IPSExtractor para que los resultados
|
||||
de las pruebas tengan sentido sin leer el plan completo.
|
||||
|
||||
\callout{\textbf{Nombres (importante)}}{%
|
||||
\textbf{bot-descarga} = servicio de orquestaci\'on (API Docker): jobs, claim, heartbeats.\\
|
||||
\textbf{Agent Selenium} = programa en el PC que descarga PDFs.\\
|
||||
ST-BOT-* mide el orquestador; $\sim$422 fact/h es capacidad del agent.
|
||||
}
|
||||
|
||||
\section{Vista de componentes}
|
||||
El host solo expone el Gateway en \textbf{:8189}. El resto de APIs est\'a en la red Docker.
|
||||
Los agents Selenium corren en PCs externas.
|
||||
|
||||
\input{../diagrams/arquitectura}
|
||||
|
||||
\section{Patrones de dise\~no}
|
||||
\begin{description}[style=nextline,leftmargin=0.5cm]
|
||||
\item[API Gateway]
|
||||
Puerta \'unica. Las pruebas ST-GW-* miden latencia/errores del proxy, no del OCR ni del LLM.
|
||||
\item[Microservicios]
|
||||
count-api (OCR), extraction+workers (LLM), \texttt{bot-descarga} (orquestador), cleaning (CSV).
|
||||
Cada uno tiene su propio cuello de botella.
|
||||
\item[Cola ARQ]
|
||||
Con \texttt{ASYNC\_QUEUE=true} el ingest encola y los workers drenan.
|
||||
El bench LLM de esta campa\~na corri\'o en modo sync (respuesta en el mismo request).
|
||||
\item[Claim del orquestador (\texttt{bot-descarga})]
|
||||
Un job pending lo toma un solo agent Selenium (claim at\'omico). Heartbeats marcan al worker online.
|
||||
\end{description}
|
||||
|
||||
\input{../diagrams/patrones}
|
||||
|
||||
\section{Pipeline de negocio}
|
||||
\input{../diagrams/pipeline_negocio}
|
||||
|
||||
Cuando este informe habla de ``capacidad'', se refiere a cu\'antas facturas/hora
|
||||
puede sostener \textbf{cada etapa} y, en el documento de Capacidad 100k, el pipeline completo.
|
||||
|
||||
\section{Definici\'on de factura}
|
||||
Una \textbf{factura} = un PDF de cuenta m\'edica evaluado en una etapa
|
||||
(conteo OCR, extracci\'on LLM, descarga agent Selenium o limpieza).
|
||||
@@ -8,17 +8,18 @@ Documentar de forma profesional los \textbf{resultados} de pruebas de IPSExtract
|
||||
\begin{itemize}
|
||||
\item Stack Docker IPS: gateway, extraction, workers, cleaning, count-api, bot-descarga, PostgreSQL, Redis.
|
||||
\item Perfil de carga \texttt{shared8g} en host de staging compartido.
|
||||
\item Unitarias del agent (\texttt{bots/bot\_descarga}) y del store API (\texttt{services/bot\_descarga}).
|
||||
\item Unitarias del agent Selenium (\texttt{bots/bot\_descarga}) y del orquestador API (\texttt{services/bot\_descarga}).
|
||||
\item Modelo anal\'itico de capacidad por etapa del pipeline.
|
||||
\item E2E-07: rechazo de job si el agent Selenium est\'a offline (HTTP 409).
|
||||
\end{itemize}
|
||||
|
||||
\section{Fuera de alcance de esta campa\~na}
|
||||
\begin{itemize}
|
||||
\item ST-COUNT-01 / OCR masivo y ST-EXT bajo LLM real a escala.
|
||||
\item E2E Imaging/Selenium con VPN.
|
||||
\item E2E Imaging/Selenium con VPN (E2E-05/08 a volumen).
|
||||
\item ST-BOT-03 (events) --- requiere \texttt{DOWNLOAD\_JOB\_ID} de un job real (\neje).
|
||||
\item Procesar 100\,000 PDFs reales.
|
||||
\end{itemize}
|
||||
|
||||
\section{Definici\'on de factura}
|
||||
Una \textbf{factura} = un PDF de cuenta m\'edica evaluado por etapa (conteo OCR, extracci\'on LLM, descarga bot, limpieza). El veredicto 100k/mes se expresa por etapa y para el pipeline completo.
|
||||
Una \textbf{factura} = un PDF de cuenta m\'edica evaluado por etapa (conteo OCR, extracci\'on LLM, descarga agent Selenium, limpieza). El veredicto 100k/mes se expresa por etapa y para el pipeline completo.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
\chapter{C\'omo leer este informe}
|
||||
\label{ch:como-leer}
|
||||
|
||||
\section{Plantilla de interpretaci\'on}
|
||||
Cada bloque de resultados sigue (expl\'icita o impl\'icitamente) cuatro preguntas:
|
||||
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{3.2cm}p{9.5cm}@{}}
|
||||
\toprule
|
||||
\textbf{Pregunta} & \textbf{Qu\'e responde} \\
|
||||
\midrule
|
||||
\textbf{Qu\'e eval\'ua} & Riesgo o propiedad del sistema bajo prueba \\
|
||||
\textbf{C\'omo se midi\'o} & Herramienta, perfil, comando o script \\
|
||||
\textbf{Resultado} & N\'umeros, PASS/FAIL/PARCIAL/N/E \\
|
||||
\textbf{Qu\'e significa} & Implicaci\'on pr\'actica (seguir, dimensionar o investigar) \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\section{Leyenda de estados}
|
||||
\begin{itemize}
|
||||
\item \pass --- cumple el umbral o criterio acordado.
|
||||
\item \fail --- no cumple; requiere acci\'on o redespliegue de entorno.
|
||||
\item \parcial --- funcional OK pero latencia/SLO degradado (t\'ipico: contenci\'on host).
|
||||
\item \neje --- no ejecutado en esta campa\~na (falta dato, VPN, job id, etc.).
|
||||
\end{itemize}
|
||||
|
||||
\section{Perfil de carga \texttt{shared8g}}
|
||||
Host compartido 8\,GiB con MLflow, n8n, MinIO, etc. Los SLOs son m\'as permisivos
|
||||
que en laboratorio dedicado:
|
||||
|
||||
\begin{itemize}
|
||||
\item Gateway \texttt{/health/live}: p95 $<$500\,ms; fail $<$1\,\%.
|
||||
\item Heartbeats bot: p95 $<$800\,ms; checks $>$95\,\%.
|
||||
\item No usar \texttt{/health} agregado como SLO de latencia (puede superar 10\,s).
|
||||
\end{itemize}
|
||||
|
||||
\section{Herramientas}
|
||||
k6, pytest, go test, Docker stats, curl, scripts en \texttt{docs/plan\_pruebas/scripts/}.
|
||||
|
||||
\section{Mapa de cap\'itulos de resultados}
|
||||
\begin{enumerate}
|
||||
\item Unitarias --- correctitud de dominio (agent, store, count-api).
|
||||
\item Humo / funcional --- el stack responde.
|
||||
\item Carga k6 --- gateway y heartbeats bajo VUs.
|
||||
\item Count-api --- qu\'e prueba el OCR y latencias medidas.
|
||||
\item LLM ST-EXT-02 --- latencia real gpt-5.4-mini.
|
||||
\item Cola ARQ ST-EXT-01 --- enqueue + drenado con mock.
|
||||
\item ST-COUNT-01 --- OCR bajo carga concurrente.
|
||||
\item Hallazgos y conclusiones --- s\'intesis.
|
||||
\end{enumerate}
|
||||
@@ -1,6 +1,24 @@
|
||||
\chapter{Resultados --- pruebas unitarias}
|
||||
\label{ch:unitarias}
|
||||
|
||||
\callout{\textbf{Veredicto unitarias:} \pass}{%
|
||||
54 casos verdes: 19 agent Selenium + 7 API \texttt{bot-descarga} + 11 count-api + \textbf{25 Vitest desktop}.
|
||||
}
|
||||
|
||||
\section{Qu\'e eval\'uan}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{2.8cm}p{4.2cm}p{2.2cm}p{3.5cm}@{}}
|
||||
\toprule
|
||||
\textbf{Suite} & \textbf{Qu\'e eval\'ua} & \textbf{Resultado} & \textbf{Significado} \\
|
||||
\midrule
|
||||
Agent Selenium & Parsers, cancel, MinIO keys, GUI & 19/19 \pass & Dominio del PC agent est\'able \\
|
||||
API \texttt{bot-descarga} & Register, claim, events, auth & 7/7 \pass & Orquestador estable en unitario \\
|
||||
count-api & Features + clustering OCR & 11/11 \pass & Pipeline de conteo en izolaci\'on \\
|
||||
Desktop (Vitest) & UI shell, API client, CSV, schema & 25/25 \pass & Cliente Electron coherente \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\section{Scorecard}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
@@ -11,43 +29,72 @@
|
||||
\texttt{bots/bot\_descarga/tests} & \pass & 19/19 & 3{,}21\,s \\
|
||||
\texttt{services/bot\_descarga/tests} & \pass & 7/7 & 1{,}45\,s \\
|
||||
\texttt{go test} count-api (\texttt{internal/pipeline}) & \pass & 11/11 & + Timing/HTTP en cap.~\ref{ch:count-latency} \\
|
||||
Vitest desktop & \neje & --- & No ejecutado en esta sesi\'on \\
|
||||
Vitest \texttt{services/desktop\_app} & \pass & 25/25 & $\approx$40\,s (7 archivos) \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Resultados unitarios.}
|
||||
\end{table}
|
||||
|
||||
\section{Cobertura agent}
|
||||
\section{Cobertura agent Selenium}
|
||||
Incluye excel reader, PDF validator, file manager, cancel, browser fatal, MinIO keys, GUI settings (19 casos).
|
||||
|
||||
\section{Cobertura API store}
|
||||
\section{Cobertura API \texttt{bot-descarga} (orquestador)}
|
||||
Incluye helpers MinIO, register/heartbeat/offline, claim at\'omico, cancel, events, filtros por job padre (7 casos).
|
||||
|
||||
\section{Cobertura count-api}
|
||||
Ver cap\'itulo~\ref{ch:count-latency}: explicaci\'on test a test con ejemplos,
|
||||
gr\'aficas y resultados (11 unitarias PASS; HTTP 3 PDFs ok en 2{,}2--4{,}5\,s).
|
||||
Ver cap\'itulo~\ref{ch:count-latency}: 11 unitarias PASS + \textbf{23 PDFs} medidos (HTTP y Go ok; promedio $\sim$2\,s/PDF).
|
||||
|
||||
\section{Cobertura desktop (Vitest)}
|
||||
Ejecutado: \texttt{cd services/desktop\_app \&\& npm test}\\
|
||||
7 archivos / 25 tests \pass\ (warnings \texttt{act(...)} en \texttt{App.test.tsx}, sin fallos).
|
||||
Incluye: \texttt{App} (shell/home/config), \texttt{apiClient}, CSV, path mapping, payloads,
|
||||
cleaning settings, schema display.
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
xbar,
|
||||
width=0.85\textwidth,
|
||||
height=4.2cm,
|
||||
xbar stacked,
|
||||
width=0.88\textwidth,
|
||||
height=5.2cm,
|
||||
xlabel={Casos},
|
||||
symbolic y coords={agent,api-store,count-api,desktop},
|
||||
symbolic y coords={desktop,count-api,api-store,agent},
|
||||
ytick=data,
|
||||
xmin=0, xmax=22,
|
||||
nodes near coords,
|
||||
bar width=10pt,
|
||||
legend style={at={(0.98,0.35)},anchor=east,font=\small}
|
||||
yticklabels={desktop Vitest,count-api,bot-descarga API,agent Selenium},
|
||||
xmin=0, xmax=28,
|
||||
enlarge y limits=0.18,
|
||||
bar width=12pt,
|
||||
axis y line*=left,
|
||||
axis x line*=bottom,
|
||||
tick align=outside,
|
||||
legend style={
|
||||
at={(0.98,0.12)},
|
||||
anchor=south east,
|
||||
font=\small,
|
||||
draw=ipsgrey!40,
|
||||
fill=white
|
||||
},
|
||||
visualization depends on={rawx \as \rawxval},
|
||||
nodes near coords={%
|
||||
\pgfmathparse{abs(\rawxval)<0.5 ? "":int(\rawxval)}%
|
||||
\pgfmathresult
|
||||
},
|
||||
every node near coord/.append style={
|
||||
font=\footnotesize\bfseries,
|
||||
text=black,
|
||||
/pgf/number format/assume math mode=true
|
||||
},
|
||||
]
|
||||
\addplot[fill=passgreen!70] coordinates {(19,agent) (7,api-store) (11,count-api) (0,desktop)};
|
||||
\addplot[fill=passgreen!75, draw=passgreen] coordinates {
|
||||
(25,desktop) (11,count-api) (7,api-store) (19,agent)
|
||||
};
|
||||
\addlegendentry{PASS}
|
||||
\addplot[fill=ipsgrey!40] coordinates {(0,agent) (0,api-store) (0,count-api) (1,desktop)};
|
||||
\addplot[fill=ipsgrey!35, draw=ipsgrey] coordinates {
|
||||
(0,desktop) (0,count-api) (0,api-store) (0,agent)
|
||||
};
|
||||
\addlegendentry{N/E}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Unitarias ejecutadas vs pendientes.}
|
||||
\caption{Unitarias ejecutadas (todas en verde tras Vitest desktop).}
|
||||
\label{fig:unitarias}
|
||||
\end{figure}
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
\chapter{Resultados --- humo y funcional}
|
||||
\label{ch:humo}
|
||||
|
||||
\callout{\textbf{Veredicto humo / funcional:} \pass}{%
|
||||
Gateway live y health agregado OK.\\
|
||||
E2E-07 (worker offline $\rightarrow$ 409): \pass.
|
||||
}
|
||||
|
||||
\section{Qu\'e eval\'uan}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{3.2cm}p{3.2cm}p{2.4cm}p{4.0cm}@{}}
|
||||
\toprule
|
||||
\textbf{Check} & \textbf{Qu\'e eval\'ua} & \textbf{Resultado} & \textbf{Significado} \\
|
||||
\midrule
|
||||
\texttt{/health/live} & Liveness gateway & \pass\ 11\,ms & Responde sin depender de upstreams \\
|
||||
\texttt{/health} & Health agregado & \pass\ 194\,ms & extraction, cleaning, counting, bot-descarga ok \\
|
||||
E2E-07 & Job con agent offline & \pass\ 409 & Orquestador no asigna sin agent online \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\section{Humo gateway}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
@@ -10,14 +28,49 @@
|
||||
\midrule
|
||||
\texttt{GET /health/live} & \pass & 11\,ms & Liveness \\
|
||||
\texttt{GET /health} & \pass & 194\,ms & 4 servicios ok \\
|
||||
Contenedores IPS healthy & \pass & --- & 17\,h uptime \\
|
||||
Contenedores IPS healthy & \pass & --- & Stack arriba \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Humo HTTP.}
|
||||
\end{table}
|
||||
|
||||
\section{Funcional E2E-07 (worker offline $\rightarrow$ 409)}
|
||||
\neje\ en esta campa\~na (no se envi\'o \texttt{POST /bot-descarga/jobs} contra worker offline). Queda como follow-up de 2 minutos en staging.
|
||||
\subsection*{Qu\'e es}
|
||||
Comprueba que el orquestador \texttt{bot-descarga} \textbf{no} crea un job de descarga
|
||||
si el \textbf{agent Selenium} no est\'a online (sin heartbeat). Debe responder \textbf{HTTP 409}.
|
||||
|
||||
\section{Script preparado}
|
||||
Se a\~nadi\'o \texttt{docs/plan\_pruebas/scripts/k6\_smoke\_suite.js} para repeticiones automatizadas de live/health/config.
|
||||
\subsection*{C\'omo se ejecut\'o}
|
||||
Script: \texttt{docs/plan\_pruebas/scripts/e2e\_07\_worker\_offline.py}
|
||||
\begin{enumerate}
|
||||
\item \texttt{POST /jobs} --- crea job Desktop padre.
|
||||
\item Verifica que el worker \texttt{e2e07-offline-pc} no est\'a online.
|
||||
\item \texttt{POST /bot-descarga/jobs} (multipart Excel + \texttt{worker\_id} offline).
|
||||
\end{enumerate}
|
||||
|
||||
\subsection*{Resultado}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}ll@{}}
|
||||
\toprule
|
||||
\textbf{Paso} & \textbf{Resultado} \\
|
||||
\midrule
|
||||
Health live & 200 \\
|
||||
Job padre Desktop & 200 (id creado) \\
|
||||
Worker online? & no \\
|
||||
Crear job descarga & \textbf{409} (esperado) \\
|
||||
Detalle API & Worker no est\'a online; enciende el agent primero \\
|
||||
\midrule
|
||||
\textbf{Veredicto} & \pass \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\callout{\textbf{Significado}}{%
|
||||
El orquestador protege el flujo: sin agent Selenium encendido no se encola descarga.\\
|
||||
Evidencia en \texttt{docs/plan\_pruebas/informes/e2e\_07\_latest.json}.
|
||||
}
|
||||
|
||||
\section{Scripts}
|
||||
\begin{itemize}
|
||||
\item Humo: \texttt{docs/plan\_pruebas/scripts/k6\_smoke\_suite.js}
|
||||
\item E2E-07: \texttt{docs/plan\_pruebas/scripts/e2e\_07\_worker\_offline.py}
|
||||
\end{itemize}
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
\chapter{Resultados --- pruebas de carga}
|
||||
\label{ch:carga}
|
||||
|
||||
\callout{\textbf{Veredicto carga:} \pass\ (con matiz)}{%
|
||||
ST-GW-01 \pass. ST-BOT-01: corrida 1 \parcial\ (contenci\'on); corrida 2 \pass.\\
|
||||
ST-BOT-03 \neje\ (falt\'o DOWNLOAD\_JOB\_ID).
|
||||
}
|
||||
|
||||
\section{Qu\'e eval\'uan}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{2.2cm}p{3.8cm}p{3.0cm}p{3.8cm}@{}}
|
||||
\toprule
|
||||
\textbf{ID} & \textbf{Qu\'e eval\'ua} & \textbf{Resultado} & \textbf{Significado} \\
|
||||
\midrule
|
||||
ST-GW-01 & Gateway bajo VUs (\texttt{/health/live}) & p95 41\,ms, 0\,\% fail \pass & Orquestaci\'on no es cuello \\
|
||||
ST-BOT-01 & Heartbeats masivos (orquestador \texttt{bot-descarga}) & \#2 p95 86\,ms \pass & Orquestador OK si hay RAM \\
|
||||
ST-BOT-03 & Tormenta de events & \neje & Pendiente job real \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\section{Tabla consolidada}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
@@ -19,46 +37,68 @@ ST-BOT-03 & --- & --- & --- & --- & --- & \neje \\
|
||||
\end{table}
|
||||
|
||||
\section{ST-GW-01}
|
||||
\label{sec:st-gw-01}
|
||||
|
||||
\subsection*{Qu\'e es}
|
||||
Prueba de \textbf{carga del API Gateway} (puerto :8189). Con la herramienta k6 se simulan
|
||||
varios usuarios virtuales (VUs) que llaman una y otra vez a \texttt{GET /health/live}
|
||||
(``\textquestiondown est\'as vivo?'').
|
||||
|
||||
No procesa facturas: no pasa por OCR, LLM ni agent Selenium. Solo comprueba que la \textbf{puerta de entrada}
|
||||
sigue respondiendo r\'apido cuando hay muchas peticiones a la vez.
|
||||
|
||||
\subsection*{Umbrales que deb\'ia cumplir}
|
||||
\begin{itemize}
|
||||
\item Checks 13768/13768 (100\,\%); throughput $\approx$57\,req/s.
|
||||
\item Thresholds p95$<$500\,ms y fail$<$1\,\% en verde.
|
||||
\item p95 (el 95\,\% de las respuestas) $<$ 500\,ms
|
||||
\item porcentaje de fallos $<$ 1\,\%
|
||||
\end{itemize}
|
||||
|
||||
\section{ST-BOT-01 --- comparativa}
|
||||
Corrida 1: funcional OK, latencia FAIL (contenci\'on host).\\
|
||||
Corrida 2: funcional OK, latencia PASS (host m\'as libre).
|
||||
\subsection*{Resultado}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}lrl@{}}
|
||||
\toprule
|
||||
\textbf{M\'etrica} & \textbf{Valor} & \textbf{Lectura} \\
|
||||
\midrule
|
||||
Peticiones & 6\,884 & carga real enviada \\
|
||||
Fallos & 0\,\% & ninguna petici\'on rota \\
|
||||
Checks k6 & 13\,768/13\,768 (100\,\%) & todas las aserciones OK \\
|
||||
Promedio & 12{,}6\,ms & muy por debajo del umbral \\
|
||||
p95 & 40{,}9\,ms & \pass\ (l\'imite 500\,ms) \\
|
||||
M\'aximo & 125\,ms & sin outliers graves \\
|
||||
Ritmo & $\approx$57\,req/s & ver secci\'on~7.5 \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.95\textwidth,
|
||||
height=7cm,
|
||||
ylabel={Latencia (ms)},
|
||||
symbolic x coords={GW-p95,BOT1-p95,BOT2-p95,GW-max,BOT1-max,BOT2-max},
|
||||
xtick=data,
|
||||
x tick label style={rotate=30, anchor=east, font=\small},
|
||||
nodes near coords,
|
||||
nodes near coords style={font=\scriptsize},
|
||||
bar width=12pt,
|
||||
ymin=0,
|
||||
enlarge x limits=0.12,
|
||||
grid=major,
|
||||
legend entries={medici\'on, umbral GW 500, umbral BOT 800},
|
||||
legend style={at={(0.02,0.98)},anchor=north west,font=\scriptsize}
|
||||
]
|
||||
\addplot[fill=ipsblue!75] coordinates {
|
||||
(GW-p95,40.85) (BOT1-p95,2280) (BOT2-p95,85.55)
|
||||
(GW-max,125.37) (BOT1-max,23120) (BOT2-max,98.31)
|
||||
};
|
||||
\addplot[red, sharp plot, update limits=false, forget plot] coordinates {(GW-p95,500) (BOT2-max,500)};
|
||||
\addplot[orange, sharp plot, update limits=false] coordinates {(BOT1-p95,800) (BOT2-p95,800)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{p95 y max por caso. BOT1-max (23\,s) fuera de escala visual relativa --- ver tabla.}
|
||||
\label{fig:latencias}
|
||||
\end{figure}
|
||||
\callout{\textbf{Veredicto ST-GW-01:} \pass}{%
|
||||
El gateway aguanta carga concurrente ligera sin errores y con latencia baja.\\
|
||||
\textbf{Significado:} la orquestaci\'on HTTP \textbf{no} es el cuello de botella del sistema;
|
||||
el l\'imite de capacidad est\'a en OCR / LLM / agent Selenium (otros cap\'itulos), no en :8189.
|
||||
}
|
||||
|
||||
|
||||
\section{ST-BOT-01 --- comparativa}
|
||||
\subsection*{Qu\'e es}
|
||||
Prueba de \textbf{carga del orquestador \texttt{bot-descarga}} (API Docker en :8200, v\'ia gateway).
|
||||
k6 env\'ia 80 \texttt{POST /bot-descarga/workers/\{id\}/heartbeat} simulando agents online.
|
||||
\textbf{No} mide descarga Selenium ni la capacidad $\sim$422 fact/h del agent PC; solo la API de control
|
||||
(jobs, claim, heartbeats) bajo presi\'on.
|
||||
|
||||
Misma prueba (80 heartbeats k6 contra el orquestador \texttt{bot-descarga}), dos momentos del host:
|
||||
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{3.6cm}p{4.6cm}p{4.6cm}@{}}
|
||||
\toprule
|
||||
& \textbf{Corrida 1 --- host compartido} & \textbf{Corrida 2 --- host m\'as libre} \\
|
||||
\midrule
|
||||
Qu\'e pasaba & Swap $\approx$2{,}1\,GiB; MLflow y otros servicios compitiendo & Menos carga; m\'as RAM libre \\
|
||||
Funcional & 100\,\% HTTP 200 & 100\,\% HTTP 200 \\
|
||||
p95 & \textbf{2{,}28\,s} (umbral 800\,ms) & \textbf{86\,ms} \\
|
||||
max & \textbf{23{,}1\,s} & \textbf{98\,ms} \\
|
||||
Veredicto & \parcial\ (latencia) & \pass \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
@@ -66,45 +106,91 @@ Corrida 2: funcional OK, latencia PASS (host m\'as libre).
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.9\textwidth,
|
||||
height=6cm,
|
||||
ylabel={ms},
|
||||
symbolic x coords={avg,med,p95,max},
|
||||
height=6.4cm,
|
||||
ylabel={Latencia heartbeat (ms)},
|
||||
symbolic x coords={promedio,p95,maximo},
|
||||
xtick=data,
|
||||
legend style={at={(0.02,0.98)},anchor=north west},
|
||||
bar width=10pt,
|
||||
ymin=0, ymax=600,
|
||||
enlarge x limits=0.15,
|
||||
xticklabels={promedio,p95,m\'aximo},
|
||||
enlarge x limits=0.25,
|
||||
ymin=0, ymax=2800,
|
||||
bar width=16pt,
|
||||
grid=major,
|
||||
title={ST-BOT-01: corrida 1 vs 2 (max de \#1 truncado en eje)}
|
||||
major grid style={ipsgrey!25},
|
||||
legend style={
|
||||
at={(0.5,-0.2)},
|
||||
anchor=north,
|
||||
legend columns=2,
|
||||
font=\small,
|
||||
draw=ipsgrey!40
|
||||
},
|
||||
point meta=explicit symbolic,
|
||||
nodes near coords,
|
||||
every node near coord/.append style={font=\scriptsize\bfseries, anchor=south, yshift=1pt},
|
||||
]
|
||||
\addplot[fill=failred!60] coordinates {(avg,511) (med,57) (p95,500) (max,600)};
|
||||
\addlegendentry{\#1 (p95 real 2280; max 23120)}
|
||||
\addplot[fill=passgreen!70] coordinates {(avg,57) (med,56) (p95,86) (max,98)};
|
||||
\addlegendentry{\#2 PASS}
|
||||
% Maximo corrida 1: valor visual 2500 (recortado); etiqueta indica 23,1 s real
|
||||
\addplot[fill=failred!65, draw=failred] coordinates {
|
||||
(promedio,511) [511 ms]
|
||||
(p95,2280) [2,28 s]
|
||||
(maximo,2500) [23,1 s*]
|
||||
};
|
||||
\addlegendentry{Corrida 1 --- host compartido}
|
||||
\addplot[fill=passgreen!75, draw=passgreen] coordinates {
|
||||
(promedio,57) [57 ms]
|
||||
(p95,86) [86 ms]
|
||||
(maximo,98) [98 ms]
|
||||
};
|
||||
\addlegendentry{Corrida 2 --- host m\'as libre}
|
||||
\draw[orange, thick, dashed] (axis cs:promedio,800) -- (axis cs:maximo,800)
|
||||
node[midway, above, font=\scriptsize, text=orange] {umbral p95 = 800\,ms};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Before/after heartbeats (eje limitado; valores exactos en tabla~\ref{tab:carga}).}
|
||||
\caption{Misma prueba ST-BOT-01, distinto estado del host. (*) El m\'aximo real de la corrida 1
|
||||
fue 23{,}1\,s; la barra est\'a recortada solo para que se lean el resto de valores.
|
||||
Detalle num\'erico en la tabla~\ref{tab:carga}.}
|
||||
\label{fig:bot-before-after}
|
||||
\end{figure}
|
||||
|
||||
\callout{\textbf{Conclusi\'on de esta prueba:}}{%
|
||||
Con un \textbf{host dedicado} (sin competir con MLflow/swap/otros stacks) el servicio \texttt{bot-descarga} se comporta
|
||||
mucho mejor: p95 baja de 2{,}28\,s a 86\,ms y desaparece el outlier de 23\,s.
|
||||
El fallo de la corrida 1 no es un bug del heartbeat (siempre HTTP 200), sino
|
||||
\textbf{contenci\'on del host compartido}. Para SLOs estables en staging/prod hace falta
|
||||
aislar IPSExtractor en un nodo dedicado.
|
||||
}
|
||||
|
||||
|
||||
\section{Throughput gateway}
|
||||
\textbf{Qu\'e mide.} En ST-GW-01, k6 martillea \texttt{GET /health/live} del gateway
|
||||
(:8189). Ese endpoint solo comprueba que el proceso responde; \textbf{no} cuenta PDFs,
|
||||
\textbf{no} pasa por OCR ni por el LLM.
|
||||
|
||||
\textbf{Resultado.} $\approx$57 peticiones por segundo (req/s), con p95 41\,ms y 0\,\% de fallos.
|
||||
Eso indica que la ``puerta\'\' del sistema aguanta bien la orquestaci\'on HTTP ligera.
|
||||
|
||||
\textbf{Qu\'e \emph{no} significa.} 57\,req/s \textbf{no} se traduce a 57 facturas/hora ni a
|
||||
capacidad del pipeline. La capacidad real la marcan las etapas pesadas (OCR, LLM, agent Selenium),
|
||||
documentadas en otros cap\'itulos y en Capacidad 100k.
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
width=0.7\textwidth,
|
||||
height=5cm,
|
||||
width=0.65\textwidth,
|
||||
height=4.2cm,
|
||||
ybar,
|
||||
symbolic x coords={ST-GW-01},
|
||||
xtick=data,
|
||||
ylabel={req/s},
|
||||
ylabel={peticiones / segundo},
|
||||
nodes near coords,
|
||||
ymin=0,
|
||||
bar width=30pt
|
||||
every node near coord/.append style={font=\small\bfseries},
|
||||
ymin=0, ymax=80,
|
||||
bar width=36pt,
|
||||
enlarge x limits=0.6
|
||||
]
|
||||
\addplot[fill=ipsblue] coordinates {(ST-GW-01,57.33)};
|
||||
\addplot[fill=ipsblue!80, draw=ipsblue] coordinates {(ST-GW-01,57.33)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Throughput observado en health liveness (no equivale a PDFs/h).}
|
||||
\caption{Ritmo del gateway en health liveness ($\approx$57\,req/s). Solo mide HTTP ligero,
|
||||
no facturas procesadas.}
|
||||
\label{fig:throughput}
|
||||
\end{figure}
|
||||
|
||||
@@ -1,104 +1,10 @@
|
||||
\chapter{Capacidad: 100\,000 facturas al mes}
|
||||
\label{ch:capacidad}
|
||||
\chapter*{Movido}
|
||||
\label{ch:capacidad-moved}
|
||||
|
||||
\section{Traducci\'on del objetivo}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrr@{}}
|
||||
\toprule
|
||||
\textbf{Escenario operativo} & \textbf{Facturas/d\'ia} & \textbf{Facturas/hora} \\
|
||||
\midrule
|
||||
100\,000 / mes, 24$\times$7 & $\approx$3\,333 & $\approx$\textbf{139} \\
|
||||
100\,000 / mes, 8\,h $\times$ 22\,d & $\approx$4\,545 & $\approx$\textbf{570} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Throughput m\'inimo para cumplir 100k/mes.}
|
||||
\end{table}
|
||||
El an\'alisis de \textbf{100\,000 facturas/mes} vive ahora en el documento independiente:
|
||||
|
||||
\section{Modelo por etapa}
|
||||
Supuestos: host compartido medido el 2026-07-13; estimaciones \textbf{conservadoras} (no bench OCR/LLM masivo). Detalle en \texttt{scripts/estimate\_capacity.md}.
|
||||
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrll@{}}
|
||||
\toprule
|
||||
\textbf{Etapa} & \textbf{Host act.} & \textbf{Dedicado} & \textbf{vs 139/h} & \textbf{vs 570/h} \\
|
||||
\midrule
|
||||
Gateway (orquestaci\'on) & $\gg$10\,000 & $\gg$20\,000 & OK & OK \\
|
||||
count-api OCR & $\sim$40 & $\sim$400 & RIESGO & NO \\
|
||||
extraction LLM & $\sim$100 & $\sim$800 & \'AMBAR & NO$^{*}$ \\
|
||||
bot Selenium (1 agent) & $\sim$60 & $\sim$500$^{\dagger}$ & RIESGO & NO \\
|
||||
cleaning & $\sim$800 & $\sim$3\,000 & OK & \'AMBAR \\
|
||||
\textbf{Pipeline completo} & $\sim$35 & $\sim$450 & \fail & \'AMBAR \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Facturas/hora estimadas. $^{*}$Sujeto a cuota LLM. $^{\dagger}$Con $N$ agents en paralelo.}
|
||||
\label{tab:capacidad}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=\textwidth,
|
||||
height=7.5cm,
|
||||
ylabel={Facturas / hora (estimadas)},
|
||||
symbolic x coords={Gateway,OCR,LLM,Bot,Cleaning,Pipeline},
|
||||
xtick=data,
|
||||
x tick label style={font=\small},
|
||||
legend style={at={(0.02,0.98)},anchor=north west,font=\small},
|
||||
bar width=8pt,
|
||||
ymin=0, ymax=1200,
|
||||
enlarge x limits=0.08,
|
||||
grid=major,
|
||||
title={Capacidad estimada vs objetivos 139/h y 570/h}
|
||||
]
|
||||
\addplot[fill=ipsblue!70] coordinates {
|
||||
(Gateway,1000) (OCR,40) (LLM,100) (Bot,60) (Cleaning,800) (Pipeline,35)
|
||||
};
|
||||
\addlegendentry{Host actual (Gateway truncado)}
|
||||
\addplot[fill=passgreen!55] coordinates {
|
||||
(Gateway,1000) (OCR,400) (LLM,800) (Bot,500) (Cleaning,1000) (Pipeline,450)
|
||||
};
|
||||
\addlegendentry{Dedicado (estim.)}
|
||||
\addplot[failred, ultra thick, sharp plot, update limits=false] coordinates {
|
||||
(Gateway,139) (Pipeline,139)
|
||||
};
|
||||
\addlegendentry{Objetivo 139/h}
|
||||
\addplot[warnorange, ultra thick, sharp plot, update limits=false] coordinates {
|
||||
(Gateway,570) (Pipeline,570)
|
||||
};
|
||||
\addlegendentry{Objetivo 570/h}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Brecha de capacidad. Gateway real $\gg$1000/h; se trunca para legibilidad.}
|
||||
\label{fig:capacidad}
|
||||
\end{figure}
|
||||
|
||||
\section{Interpretaci\'on}
|
||||
\begin{enumerate}
|
||||
\item El \textbf{gateway} no es el cuello de botella (evidencia ST-GW-01).
|
||||
\item En el \textbf{host actual}, OCR + Selenium + LLM compiten por CPU/RAM/swap con MLflow: el pipeline completo queda $\ll$139/h.
|
||||
\item Con infra \textbf{dedicada} (sin zoo, m\'as RAM, workers, $N$ PCs bot, cuota LLM), acercarse a 139/h (24$\times$7) es plausible; 570/h exige dimensionamiento agresivo y prueba de carga espec\'ifica.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Veredicto}
|
||||
\begin{center}
|
||||
\fbox{\parbox{0.92\textwidth}{\centering
|
||||
\large \textbf{Se pueden procesar +100\,000 facturas/mes?}\\[0.4em]
|
||||
\normalsize
|
||||
En el servidor actual compartido: \fail.\\[0.2em]
|
||||
En arquitectura dedicada y dimensionada: \pass\ condicional (cuota LLM, OCR, agents).\\[0.3em]
|
||||
\footnotesize Esta campa\~na no ejecut\'o 100k PDFs; el veredicto es modelo + evidencia de carga ligera.
|
||||
}}
|
||||
\texttt{docs/plan\_pruebas/capacidad\_100k/} $\rightarrow$ \textbf{Capacidad100k\_IPSExtractor.pdf}
|
||||
\end{center}
|
||||
|
||||
\section{Requisitos m\'inimos sugeridos para 100k/mes}
|
||||
\begin{itemize}
|
||||
\item Host(s) dedicados IPS: RAM $\geq$16--32\,GiB; SSD; sin MLflow/Airflow en el mismo nodo.
|
||||
\item count-api: pool OCR dimensionado; l\'imite de memoria realista.
|
||||
\item extraction: r\'eplicas worker + concurrencia acorde a RPM del LLM; presupuesto de coste.
|
||||
\item bot: varios agents PC en paralelo hacia Imaging/MinIO.
|
||||
\item Observabilidad: colas ARQ, p95 por etapa, alertas de swap.
|
||||
\end{itemize}
|
||||
Este archivo se conserva solo como marcador para no romper enlaces antiguos.
|
||||
|
||||
@@ -3,10 +3,21 @@
|
||||
|
||||
\section{Hallazgos}
|
||||
\begin{enumerate}
|
||||
\item \textbf{H1 --- Contenci\'on de plataforma:} ST-BOT-01 \#1 (p95 2{,}28\,s, max 23\,s) vs \#2 (p95 86\,ms) demuestra sensibilidad a swap/MLflow, no a un bug funcional del heartbeat (100\,\% HTTP 200 en ambas).
|
||||
\item \textbf{H1 --- Contenci\'on de plataforma:} ST-BOT-01 \#1 (p95 2{,}28\,s, max 23\,s) vs \#2 (p95 86\,ms)
|
||||
demuestra que un \textbf{host dedicado / sin contenci\'on} se comporta mucho mejor.
|
||||
No es un bug del heartbeat (100\,\% HTTP 200 en ambas); es sensibilidad a swap/MLflow
|
||||
en el host compartido.
|
||||
\item \textbf{H2 --- Gateway saludable:} 0\,\% errores y p95 41\,ms bajo 25 VUs en liveness.
|
||||
\item \textbf{H3 --- Unitarias s\'olidas:} 29 casos verdes (19 agent + 7 API store + 3 count-api pipeline).
|
||||
\item \textbf{H4 --- Capacidad 100k:} imposible en host compartido actual; viable solo con escalado dedicado.
|
||||
\item \textbf{H3 --- Unitarias s\'olidas:} agent Selenium + \texttt{bot-descarga} + count-api
|
||||
+ Vitest desktop (25/25) en verde.
|
||||
\item \textbf{H4 --- LLM medido:} p50 7{,}1\,s $\approx$506 fact/h; etapa viable vs 139/h.
|
||||
\item \textbf{H5 --- Cola ARQ validada:} ST-EXT-01 30/30 drenados en 22{,}5\,s (mock); prod debe usar \texttt{ASYNC\_QUEUE=true}.
|
||||
\item \textbf{H6 --- OCR bajo carga:} ST-COUNT-01 9/9 ok; p50 35{,}8\,s con 3 concurrentes
|
||||
(vs $\sim$2\,s en serie) $\Rightarrow$ $\approx$100--325 fact/h en shared8g.
|
||||
\item \textbf{H7 --- Capacidad 100k:} imposible en host compartido actual (OCR/contenci\'on);
|
||||
ver PDF \emph{Capacidad 100k/mes}.
|
||||
\item \textbf{H8 --- E2E-07:} crear job con agent offline $\rightarrow$ HTTP 409 \pass;
|
||||
el orquestador \texttt{bot-descarga} no asigna descarga sin agent online.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Scorecard global}
|
||||
@@ -18,18 +29,18 @@
|
||||
width=0.9\textwidth,
|
||||
height=5.5cm,
|
||||
ylabel={Cantidad de casos},
|
||||
symbolic x coords={Unitaria,Humo,Carga,Funcional,Capacidad},
|
||||
symbolic x coords={Unitaria,Humo,Carga,LLM,ARQ,Count},
|
||||
xtick=data,
|
||||
legend style={at={(0.5,-0.22)},anchor=north,legend columns=3,font=\small},
|
||||
bar width=18pt,
|
||||
bar width=14pt,
|
||||
ymin=0, ymax=8,
|
||||
enlarge x limits=0.12
|
||||
enlarge x limits=0.1
|
||||
]
|
||||
\addplot[fill=passgreen!70] coordinates {(Unitaria,3) (Humo,3) (Carga,2) (Funcional,0) (Capacidad,0)};
|
||||
\addplot[fill=passgreen!70] coordinates {(Unitaria,4) (Humo,4) (Carga,2) (LLM,1) (ARQ,1) (Count,3)};
|
||||
\addlegendentry{PASS}
|
||||
\addplot[fill=warnorange!70] coordinates {(Unitaria,0) (Humo,0) (Carga,1) (Funcional,0) (Capacidad,0)};
|
||||
\addplot[fill=warnorange!70] coordinates {(Unitaria,0) (Humo,0) (Carga,1) (LLM,0) (ARQ,0) (Count,0)};
|
||||
\addlegendentry{PARCIAL}
|
||||
\addplot[fill=ipsgrey!50] coordinates {(Unitaria,1) (Humo,0) (Carga,1) (Funcional,1) (Capacidad,1)};
|
||||
\addplot[fill=ipsgrey!50] coordinates {(Unitaria,0) (Humo,0) (Carga,1) (LLM,0) (ARQ,0) (Count,0)};
|
||||
\addlegendentry{N/E}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
@@ -38,4 +49,6 @@
|
||||
\end{figure}
|
||||
|
||||
\section{Limitaciones}
|
||||
No se midieron tiempos reales OCR/LLM por PDF en esta campa\~na; las cifras de capacidad son estimaciones documentadas para decisi\'on de arquitectura, no un compromiso de SLA.
|
||||
No se ejecut\'o ST-COUNT-01 lab agresivo ni E2E Imaging a volumen; ST-EXT-01 lab (100--500) pendiente.
|
||||
El veredicto 100k/mes del pipeline completo es modelo + evidencia parcial
|
||||
(documento Capacidad 100k), no un compromiso de SLA.
|
||||
|
||||
@@ -3,24 +3,34 @@
|
||||
|
||||
\section{Conclusiones}
|
||||
\begin{enumerate}
|
||||
\item IPSExtractor en staging responde correctamente a humo y carga ligera de control (gateway + heartbeats).
|
||||
\item La calidad de latencia depende cr\'iticamente de no saturar el host compartido.
|
||||
\item \textbf{100\,000 facturas/mes no son alcanzables} en el laptop Ubuntu actual con el zoo Docker observado.
|
||||
\item Con infraestructura dedicada y dimensionada, el objetivo es \textbf{condicional\-mente viable}; requiere prueba de carga espec\'ifica por etapa (OCR, LLM, bot).
|
||||
\item IPSExtractor en staging responde correctamente a humo y carga ligera (gateway + heartbeats).
|
||||
\item \textbf{Host dedicado vs compartido:} ST-BOT-01 muestra que, con la misma prueba,
|
||||
un host libre (sin MLflow/swap compitiendo) baja el p95 de 2{,}28\,s a 86\,ms y elimina
|
||||
el outlier de 23\,s. El comportamiento es claramente mejor en nodo dedicado;
|
||||
la latencia mala no fue un bug funcional (HTTP 200 en ambas corridas).
|
||||
\item Agent Selenium ($\approx$422\,fact/h) y LLM ($\approx$506\,fact/h) cubren el objetivo 24$\times$7 \emph{por etapa}.
|
||||
\item La cola ARQ (ST-EXT-01) dren\'o 30 docs en $\approx$23\,s con mock; arquitectura async validada.
|
||||
\item OCR bajo 3 concurrentes (ST-COUNT-01): p50 35{,}8\,s; $\approx$100--325 fact/h en shared8g;
|
||||
degradaci\'on fuerte vs serie ($\sim$2\,s).
|
||||
\item El pipeline a 100k/mes en el host actual \textbf{no est\'a garantizado}
|
||||
(ritmo $\approx\min$ etapas $=$ OCR $\sim$100--325/h; piso $\sim$100 $<$ 139);
|
||||
detalle en \textbf{Capacidad100k\_IPSExtractor.pdf}.
|
||||
\item Con infra dedicada + OCR dimensionado, 24$\times$7 es \textbf{condicionalmente viable}.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Recomendaciones}
|
||||
\begin{enumerate}
|
||||
\item Separar IPS de MLflow/Airflow/n8n en el nodo de producci\'on/staging de rendimiento.
|
||||
\item Ejecutar ST-COUNT-01 y bench LLM solo con $\geq$3\,GiB libres y sin swap activo.
|
||||
\item Completar E2E-07 y ST-BOT-03; archivar JSON de k6 en \texttt{informes/}.
|
||||
\item Dimensionar agents bot ($N$) antes de comprometer vol\'umenes mensuales de descarga.
|
||||
\item Incorporar CI: pytest agent + store en cada PR.
|
||||
\item Separar IPSExtractor en un \textbf{host dedicado} (sin MLflow/Airflow/n8n en el mismo nodo
|
||||
de rendimiento); ST-BOT-01 lo justifica con evidencia.
|
||||
\item Producci\'on: \texttt{ASYNC\_QUEUE=true} + workers dimensionados a la cuota LLM.
|
||||
\item Repetir ST-COUNT-01 en perfil lab (m\'as rps / VUs) con $\geq$3\,GiB libres.
|
||||
\item Completar ST-BOT-03, ST-EXT-01 perfil lab (100--500) y E2E pendientes.
|
||||
\item Dimensionar agents Selenium ($N$) y workers extraction antes de comprometer vol\'umenes.
|
||||
\item Incorporar CI: pytest agent Selenium + orquestador \texttt{bot-descarga} en cada PR.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Pr\'oxima campa\~na sugerida}
|
||||
\section{Documentos relacionados}
|
||||
\begin{itemize}
|
||||
\item Ventana con MLflow detenido.
|
||||
\item Bench: 50--200 PDFs reales por etapa (OCR, LLM) midiendo p50/p95 y coste.
|
||||
\item Actualizar gr\'afica de capacidad con datos medidos (reemplazar estimaciones).
|
||||
\item \textbf{Plan de pruebas} --- estrategia e IDs.
|
||||
\item \textbf{Capacidad 100k/mes} --- modelo, coste LLM, veredicto de volumen.
|
||||
\end{itemize}
|
||||
|
||||
@@ -26,11 +26,27 @@ services/bot_descarga/tests: 7 passed in 1.45s
|
||||
count-api internal/pipeline: 3 passed (+8 features/clustering nuevos)
|
||||
Timing: go test -run Timing -v (3 PDFs en docs/plan_pruebas/data/)
|
||||
HTTP: bash docs/plan_pruebas/scripts/measure_count_latency.sh
|
||||
LLM: python docs/plan_pruebas/scripts/measure_llm_latency.py
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Scripts}
|
||||
\texttt{k6\_gateway\_health.js}, \texttt{k6\_bot\_heartbeats.js}, \texttt{k6\_bot\_events.js},
|
||||
\texttt{k6\_count\_batches.js}, \texttt{k6\_smoke\_suite.js}, \texttt{estimate\_capacity.md}.
|
||||
\texttt{k6\_count\_batches.js}, \texttt{k6\_smoke\_suite.js},
|
||||
\texttt{st\_ext\_01\_arq\_bench.py}, \texttt{st\_ext\_02\_llm\_bench.py},
|
||||
\texttt{llm\_mock\_server.py}, \texttt{measure\_count\_latency.*}, \texttt{estimate\_capacity.md}.
|
||||
|
||||
\section{Extracto ST-EXT-01}
|
||||
\begin{lstlisting}
|
||||
mode=async queued=30/30 drained=True
|
||||
enqueue_wall_s=1.307 drain_s=22.544 total_s=23.851
|
||||
throughput_docs_per_h ~ 4528 (mock; no SLO LLM)
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Documentos hermanos}
|
||||
\begin{itemize}
|
||||
\item Plan: \texttt{PlanPruebas\_IPSExtractor.pdf}
|
||||
\item Capacidad 100k: \texttt{Capacidad100k\_IPSExtractor.pdf}
|
||||
\end{itemize}
|
||||
|
||||
\section{Reproducibilidad}
|
||||
\begin{enumerate}
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
\chapter{Count-api: qu\'e eval\'ua cada prueba}
|
||||
\label{ch:count-latency}
|
||||
|
||||
Este cap\'itulo explica, con ejemplos, las pruebas del microservicio \textbf{count-api}
|
||||
(conteo de servicios en facturas PDF) y resume los \textbf{resultados} obtenidos
|
||||
en el host Ubuntu el 13 de julio de 2026.
|
||||
\callout{\textbf{Veredicto count-api:} \pass\ en muestra}{%
|
||||
11/11 unitarias; 23/23 PDFs HTTP ok; latencia tipica $\sim$2\,s/PDF.\\
|
||||
No sustituye ST-COUNT-01 agresivo (capacidad OCR bajo carga).
|
||||
}
|
||||
|
||||
\section{Qu\'e eval\'ua / resultado / significado}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{3.0cm}p{4.0cm}p{5.5cm}@{}}
|
||||
\toprule
|
||||
\textbf{Bloque} & \textbf{Resultado} & \textbf{Qu\'e significa} \\
|
||||
\midrule
|
||||
Features + clustering & 11/11 \pass & L\'ogica OCR de conteo correcta sin PDF \\
|
||||
HTTP 23 PDFs & 23/23 \pass & Endpoint responde con PDFs reales \\
|
||||
Latencia $\sim$2\,s/PDF & medido & En host dedicado $\sim$1800/h te\'oricos; en shared8g contenci\'on baja a $\sim$40/h estimados \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
Este cap\'itulo explica las pruebas del microservicio \textbf{count-api}
|
||||
(conteo de servicios en facturas PDF) y resume los \textbf{resultados} del 13 de julio de 2026.
|
||||
|
||||
\section{Pipeline bajo prueba}
|
||||
\begin{center}
|
||||
@@ -27,7 +44,7 @@ en el host Ubuntu el 13 de julio de 2026.
|
||||
\end{center}
|
||||
|
||||
Las unitarias de \texttt{features} y \texttt{clustering} \textbf{no} abren el PDF:
|
||||
simulan ``cajas'' OCR \texttt{\{texto, X, Y\}}. Timing y HTTP s\'i usan los 3 PDFs reales.
|
||||
simulan ``cajas'' OCR \texttt{\{texto, X, Y\}}. Timing y HTTP usan los \textbf{23 PDFs} reales en \texttt{docs/plan\_pruebas/data/}.
|
||||
|
||||
\section{Resultado global count-api}
|
||||
\begin{table}[ht]
|
||||
@@ -37,11 +54,11 @@ simulan ``cajas'' OCR \texttt{\{texto, X, Y\}}. Timing y HTTP s\'i usan los 3 PD
|
||||
\textbf{Bloque} & \textbf{Resultado} & \textbf{Casos} & \textbf{Notas} \\
|
||||
\midrule
|
||||
Features + clustering & \pass & 11/11 & Ubuntu, \texttt{go test -v} \\
|
||||
HTTP 3 PDFs reales & \pass & 3/3 & 200, status ok, $\sim$2--4{,}5\,s \\
|
||||
Timing Go por etapa & pendiente / skip path & --- & re-ejecutar \texttt{-run Timing -count=1} \\
|
||||
HTTP 23 PDFs reales & \pass & 23/23 & 200, status ok \\
|
||||
Timing Go por etapa & \pass & 23/23 & 46{,}3\,s total; todos ok \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Scorecard count-api en esta campa\~na.}
|
||||
\caption{Scorecard count-api --- campa\~na 23 PDFs (13 jul 2026).}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}[ht]
|
||||
@@ -55,18 +72,16 @@ Timing Go por etapa & pendiente / skip path & --- & re-ejecutar \texttt{-run Tim
|
||||
symbolic x coords={Features,Clustering,HTTP-PDF,Timing-Go},
|
||||
xtick=data,
|
||||
nodes near coords,
|
||||
ymin=0, ymax=12,
|
||||
ymin=0, ymax=25,
|
||||
bar width=14pt,
|
||||
enlarge x limits=0.15,
|
||||
legend style={at={(0.98,0.98)},anchor=north east,font=\small}
|
||||
]
|
||||
\addplot[fill=passgreen!75] coordinates {(Features,7) (Clustering,4) (HTTP-PDF,3) (Timing-Go,0)};
|
||||
\addplot[fill=passgreen!75] coordinates {(Features,11) (Clustering,0) (HTTP-PDF,23) (Timing-Go,23)};
|
||||
\addlegendentry{PASS}
|
||||
\addplot[fill=ipsgrey!45] coordinates {(Features,0) (Clustering,0) (HTTP-PDF,0) (Timing-Go,1)};
|
||||
\addlegendentry{Pendiente}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Cobertura count-api por bloque.}
|
||||
\caption{Cobertura count-api: 11 unitarias + 23 PDFs medidos (HTTP y Go).}
|
||||
\label{fig:count-score}
|
||||
\end{figure}
|
||||
|
||||
@@ -168,12 +183,8 @@ un par cercano forma cluster.
|
||||
|
||||
\section{Pruebas con PDFs reales}
|
||||
\subsection{Fixtures}
|
||||
\begin{itemize}
|
||||
\item \texttt{500000284335937.pdf}
|
||||
\item \texttt{500000284335940.pdf}
|
||||
\item \texttt{500000284335954.pdf}
|
||||
\end{itemize}
|
||||
Ubicaci\'on: \texttt{docs/plan\_pruebas/data/}.
|
||||
\textbf{23 PDFs} en \texttt{docs/plan\_pruebas/data/} (facturas m\'edicas reales, $\sim$280--294\,KB c/u).
|
||||
Listado completo en el CSV \texttt{informes/count\_latency\_20260713.csv}.
|
||||
|
||||
\subsection{TestProcessPDFTimingFixtures}
|
||||
\textbf{Eval\'ua:} pipeline completo (render$\rightarrow$OCR$\rightarrow$cluster$\rightarrow$conteo) y que
|
||||
@@ -182,55 +193,125 @@ Ubicaci\'on: \texttt{docs/plan\_pruebas/data/}.
|
||||
\subsection{Script HTTP \texttt{measure\_count\_latency}}
|
||||
\textbf{Eval\'ua:} el camino real del usuario/Desktop: gateway \texttt{POST /count/v1/count}.
|
||||
|
||||
\section{Resultados de latencia HTTP (medidos)}
|
||||
Host \texttt{ubuntu}, gateway \texttt{127.0.0.1:8189}, 13 jul 2026:
|
||||
\section{Resultados agregados (23 PDFs)}
|
||||
Host \texttt{ubuntu}, 13 jul 2026. Corrida secuencial (\texttt{-count=1}).
|
||||
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrll@{}}
|
||||
\begin{tabular}{@{}lrrrr@{}}
|
||||
\toprule
|
||||
\textbf{PDF} & \textbf{t (s)} & \textbf{HTTP} & \textbf{servicios} & \textbf{status} \\
|
||||
\textbf{M\'etrica} & \textbf{HTTP (s)} & \textbf{Total Go (ms)} & \textbf{OCR (ms)} & \textbf{Render (ms)} \\
|
||||
\midrule
|
||||
500000284335937.pdf & 4{,}45 & 200 & 1 & ok \\
|
||||
500000284335940.pdf & 2{,}29 & 200 & 1 & ok \\
|
||||
500000284335954.pdf & 2{,}19 & 200 & 1 & ok \\
|
||||
Promedio & 1{,}81 & 2008 & 1876 & 131 \\
|
||||
M\'inimo & 1{,}62 & 1823 & 1689 & 108 \\
|
||||
M\'aximo & 2{,}42 & 2435 & 2285 & 155 \\
|
||||
Desv.\ est.\ aprox. & 0{,}21 & 150 & 140 & 14 \\
|
||||
\midrule
|
||||
Promedio & 2{,}98 & --- & --- & --- \\
|
||||
M\'in / M\'ax & 2{,}19 / 4{,}45 & --- & --- & --- \\
|
||||
Servicios detectados/factura & \multicolumn{4}{c}{41--48 (promedio 43{,}5)} \\
|
||||
Cluster (ms) & \multicolumn{4}{c}{$<$2 (despreciable)} \\
|
||||
Infer (ms) & \multicolumn{4}{c}{0 (heur\'istica sin ONNX en test)} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Wall-clock HTTP. Fuente: \texttt{informes/count\_latency\_20260713.csv}.}
|
||||
\label{tab:count-http}
|
||||
\caption{Estad\'isticas sobre 23 facturas. HTTP v\'ia gateway; Go v\'ia \texttt{ProcessPDF} directo.}
|
||||
\label{tab:count-stats}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.95\textwidth,
|
||||
ybar stacked,
|
||||
width=0.92\textwidth,
|
||||
height=6cm,
|
||||
ylabel={Segundos (HTTP)},
|
||||
symbolic x coords={...937,...940,...954},
|
||||
ylabel={Milisegundos (promedio)},
|
||||
symbolic x coords={Pipeline},
|
||||
xtick=data,
|
||||
nodes near coords,
|
||||
nodes near coords style={font=\small},
|
||||
ymin=0, ymax=5.5,
|
||||
bar width=22pt,
|
||||
enlarge x limits=0.2,
|
||||
title={Tiempo de conteo por PDF (gateway)}
|
||||
ymin=0, ymax=2200,
|
||||
legend style={at={(0.5,-0.18)},anchor=north,legend columns=3,font=\small},
|
||||
title={Desglose promedio del tiempo por etapa (Go)}
|
||||
]
|
||||
\addplot[fill=ipsblue!80] coordinates {(...937,4.45) (...940,2.29) (...954,2.19)};
|
||||
\addplot[warnorange, ultra thick, sharp plot, update limits=false] coordinates {(...937,2.98) (...954,2.98)};
|
||||
\addplot[fill=ipsblue!60] coordinates {(Pipeline,131)};
|
||||
\addlegendentry{Render}
|
||||
\addplot[fill=warnorange!70] coordinates {(Pipeline,1876)};
|
||||
\addlegendentry{OCR}
|
||||
\addplot[fill=passgreen!50] coordinates {(Pipeline,1)};
|
||||
\addlegendentry{Cluster}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Barras: tiempo por PDF. L\'inea naranja: promedio 2{,}98\,s.}
|
||||
\label{fig:count-http-bars}
|
||||
\caption{El OCR representa $\sim$93\,\% del tiempo de procesamiento.}
|
||||
\label{fig:count-breakdown}
|
||||
\end{figure}
|
||||
|
||||
\textbf{Lectura:} en este host, contar \emph{una} factura tarda del orden de \textbf{2--5 segundos}
|
||||
(OCR suele dominar). Extrapolaci\'on ingenua a 3\,s/PDF $\approx$ 1\,200 facturas/hora de conteo
|
||||
si el nodo estuviera dedicado y sin colas --- ver cap\'itulo de capacidad 100k/mes para el contexto completo.
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
width=0.95\textwidth,
|
||||
height=5.5cm,
|
||||
xlabel={PDF (orden alfab\'etico, n=23)},
|
||||
ylabel={Total (ms)},
|
||||
title={Tiempo total por factura (TestProcessPDFTimingFixtures)},
|
||||
grid=major,
|
||||
ymin=1700, ymax=2500,
|
||||
]
|
||||
\addplot[blue, mark=*, thick] coordinates {
|
||||
(1,2435) (2,1954) (3,1980) (4,1994) (5,1823) (6,1955) (7,1965) (8,1873)
|
||||
(9,1937) (10,1947) (11,2430) (12,1999) (13,2016) (14,1963) (15,1875)
|
||||
(16,2186) (17,1901) (18,2002) (19,2024) (20,1840) (21,2280) (22,1884) (23,1916)
|
||||
};
|
||||
\addplot[warnorange, dashed, thick] coordinates {(0,2008) (24,2008)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Serie de tiempos totales. L\'inea punteada: promedio 2008\,ms.}
|
||||
\label{fig:count-series}
|
||||
\end{figure}
|
||||
|
||||
\section{Resultados de latencia HTTP}
|
||||
Gateway \texttt{127.0.0.1:8189}, \texttt{POST /count/v1/count}. Los 23 PDFs respondieron \textbf{200 / ok}.
|
||||
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\small
|
||||
\begin{tabular}{@{}rr@{}}
|
||||
\toprule
|
||||
\textbf{HTTP wall-clock} & \textbf{Valor} \\
|
||||
\midrule
|
||||
Promedio & 1{,}81\,s \\
|
||||
M\'in / M\'ax & 1{,}62 / 2{,}42\,s \\
|
||||
Throughput secuencial & $\approx$0{,}55 PDF/s ($\approx$1\,980/h) \\
|
||||
Tiempo total corrida HTTP & $\approx$42\,s (23 archivos) \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Resumen HTTP (sin concurrencia). Fuente: CSV del 13 jul 2026.}
|
||||
\label{tab:count-http}
|
||||
\end{table}
|
||||
|
||||
\textbf{Lectura:}
|
||||
\begin{itemize}
|
||||
\item \textbf{100\,\% PASS} en 23/23 PDFs (HTTP y Go).
|
||||
\item El cuello de botella es \textbf{OCR Tesseract} ($\sim$1{,}7--2{,}3\,s por factura).
|
||||
\item Render PDF $\sim$130\,ms; clustering $<$2\,ms.
|
||||
\item Cada factura devuelve $\sim$41--48 l\'ineas de servicio detectadas.
|
||||
\item A $\sim$2\,s/PDF secuencial $\Rightarrow$ $\approx$1\,800--2\,000 conteos/hora en este host si estuviera dedicado al count-api.
|
||||
\end{itemize}
|
||||
|
||||
\section{Detalle por archivo}
|
||||
El listado completo (HTTP + ms por etapa) est\'a en:
|
||||
\texttt{docs/plan\_pruebas/informes/count\_latency\_20260713.csv}.
|
||||
|
||||
Ejemplo de fila (\texttt{500000284335937.pdf}):
|
||||
\begin{center}
|
||||
\small
|
||||
\begin{tabular}{@{}lr@{}}
|
||||
Render & 155\,ms \\
|
||||
OCR & 2279\,ms \\
|
||||
Cluster & 1\,ms \\
|
||||
Total Go & 2435\,ms \\
|
||||
HTTP & 2{,}42\,s \\
|
||||
Servicios & 44 \\
|
||||
Status & ok \\
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\section{C\'omo reproducir}
|
||||
\begin{lstlisting}[language=bash]
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
\chapter{Extracci\'on LLM: bench de latencia (ST-EXT-02)}
|
||||
\label{ch:llm-latency}
|
||||
|
||||
\callout{\textbf{Veredicto LLM:} \pass\ (23/23)}{%
|
||||
Modelo \textbf{gpt-5.4-mini}; 20 campos congelados.\\
|
||||
p50 $=\mathbf{7{,}114}$\,s $\Rightarrow$ $\approx\mathbf{506}$ fact/h (1 worker).\\
|
||||
0 errores, 0$\times$429. Coste corrida est.\ USD 0{,}59.
|
||||
}
|
||||
|
||||
\section{Qu\'e eval\'ua / resultado / significado}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{3.2cm}p{4.2cm}p{5.2cm}@{}}
|
||||
\toprule
|
||||
\textbf{Pregunta} & \textbf{Respuesta} & \textbf{Implicaci\'on} \\
|
||||
\midrule
|
||||
Qu\'e eval\'ua & Latencia punta a punta ingest+LLM con mismos campos & Comparabilidad entre PDFs \\
|
||||
C\'omo & Script \texttt{st\_ext\_02\_llm\_bench} & Schema congelado en JSON \\
|
||||
Resultado & 23 ok; p50 7{,}1\,s; p95 8{,}6\,s & Etapa LLM cubre 139/h \\
|
||||
Significado & $\approx$506 fact/h (1\,w); $\approx$1012 (2\,w) & Cuello del mes no es el LLM en esta muestra \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
El script mide wall-clock por PDF
|
||||
(\texttt{POST /extraction/schemas/upload} $\rightarrow$ \texttt{POST /tasks/extraction}
|
||||
$\rightarrow$ \texttt{POST /tasks/\{id\}/ingest}).
|
||||
|
||||
\section{C\'omo ejecutar}
|
||||
\begin{lstlisting}[language=bash,caption={ST-EXT-02 (Windows PowerShell)}]
|
||||
.\docs\plan_pruebas\scripts\st_ext_02_llm_bench.ps1
|
||||
.\docs\plan_pruebas\scripts\st_ext_02_llm_bench.ps1 -Limit 10 -Model gpt-5.4-mini
|
||||
\end{lstlisting}
|
||||
|
||||
Schema congelado: \texttt{docs/plan\_pruebas/informes/st\_ext\_02\_schema.json}.
|
||||
CSV: \texttt{informes/llm\_latency\_20260717.csv}.
|
||||
|
||||
\section{Resultados (2026-07-17)}
|
||||
\texttt{task\_id=aeee04f9-34c5-4cb0-a303-353dde128265}; modo ingest sync en esta campa\~na.
|
||||
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}llrl@{}}
|
||||
\toprule
|
||||
\textbf{M\'etrica} & \textbf{Valor} & \textbf{Unidad} & \textbf{Notas} \\
|
||||
\midrule
|
||||
Modelo & gpt-5.4-mini & --- & 20 campos fijos \\
|
||||
PDFs ok / total & 23 / 23 & --- & 0 fallos, 0$\times$429 \\
|
||||
wall\_s promedio & 7{,}326 & s & \\
|
||||
wall\_s p50 & \textbf{7{,}114} & s & \\
|
||||
wall\_s p95 & 8{,}582 & s & min 6{,}463 / max 9{,}944 \\
|
||||
Throughput 1 worker & \textbf{$\approx$506} & fact/h & $3600/\mathrm{p50}$ \\
|
||||
Throughput 2 workers & $\approx$1\,012 & fact/h & sin cuota RPM \\
|
||||
Coste corrida (est.) & 0{,}5911 & USD & $23\times 0{,}0257$ \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{ST-EXT-02 --- latencia LLM medida (mismo schema/campos).}
|
||||
\label{tab:llm-latency}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.85\textwidth,
|
||||
height=5.5cm,
|
||||
ylabel={Facturas / hora},
|
||||
symbolic x coords={LLM-1w,LLM-2w,Obj-139,Obj-570},
|
||||
xtick=data,
|
||||
nodes near coords,
|
||||
bar width=14pt,
|
||||
ymin=0, ymax=1200,
|
||||
enlarge x limits=0.15,
|
||||
grid=major,
|
||||
title={Throughput LLM vs objetivos 100k/mes}
|
||||
]
|
||||
\addplot[fill=ipsblue!70] coordinates {(LLM-1w,506) (LLM-2w,1012) (Obj-139,139) (Obj-570,570)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{La etapa LLM sola supera 139/h; con 2 workers supera 570/h (sin cuota).}
|
||||
\label{fig:llm-vs-obj}
|
||||
\end{figure}
|
||||
|
||||
\section{Lectura para capacidad}
|
||||
Detalle del modelo 100k/mes (pipeline, OCR, coste USD\,2\,570) en el documento
|
||||
\textbf{Capacidad100k\_IPSExtractor.pdf}. Aqu\'i solo: la etapa LLM \textbf{no} es el cuello
|
||||
en el host actual frente a OCR/contenci\'on.
|
||||
|
||||
\subsection*{Nota agent Selenium}
|
||||
633 PDFs / 90\,min $\Rightarrow$ $\approx$422 fact/h (1 agent PC) --- tambi\'en cubre 139/h en descarga.
|
||||
@@ -0,0 +1,83 @@
|
||||
\chapter{Cola ARQ: bench ST-EXT-01 (LLM mock)}
|
||||
\label{ch:arq-ext01}
|
||||
|
||||
\callout{\textbf{Veredicto ST-EXT-01:} \pass}{%
|
||||
30/30 encolados y drenados; modo \textbf{async}; mock LLM.\\
|
||||
Enqueue 1{,}3\,s; drain 22{,}5\,s; throughput cola $\approx$4\,500 docs/h (mock, no LLM real).
|
||||
}
|
||||
|
||||
\section{Qu\'e eval\'ua / resultado / significado}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{3.2cm}p{4.5cm}p{5.0cm}@{}}
|
||||
\toprule
|
||||
\textbf{Pregunta} & \textbf{Respuesta} & \textbf{Implicaci\'on} \\
|
||||
\midrule
|
||||
Qu\'e eval\'ua & Enqueue + drenado ARQ con LLM mock & Capacidad de cola, no latencia OpenAI \\
|
||||
C\'omo & \texttt{st\_ext\_01\_arq\_bench} + mock :8099 & \texttt{ASYNC\_QUEUE=true}, 1 worker \\
|
||||
Resultado & 30/30 queued; drained; 23{,}9\,s total & Cola operativa en shared8g \\
|
||||
Significado & Prod debe usar async + workers & El cuello real sigue siendo OCR/LLM real \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\section{Condiciones de la corrida}
|
||||
\begin{itemize}
|
||||
\item Fecha: 2026-07-17; perfil \texttt{shared8g}; \texttt{Limit=30}.
|
||||
\item \texttt{task\_id=25890e3d-9e07-46a7-b961-046421d26eb6}.
|
||||
\item Mock OpenAI-compatible (\texttt{llm\_mock\_server}, delay $\sim$50\,ms).
|
||||
\item \texttt{ASYNC\_QUEUE=true}; \texttt{EXTRACTOR\_BASE\_URL}$\rightarrow$ mock; 1 r\'eplica worker.
|
||||
\item CSV: \texttt{informes/st\_ext\_01\_arq\_20260717.csv}.
|
||||
\end{itemize}
|
||||
|
||||
\section{Resultados}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}llrl@{}}
|
||||
\toprule
|
||||
\textbf{M\'etrica} & \textbf{Valor} & \textbf{Unidad} & \textbf{Notas} \\
|
||||
\midrule
|
||||
mode & async & --- & \texttt{queued=true} en ingest \\
|
||||
Encolados & 30 / 30 & docs & 0 fallos HTTP \\
|
||||
enqueue wall & 1{,}307 & s & avg 0{,}043\,s/doc \\
|
||||
drain & 22{,}544 & s & backlog vaciado \\
|
||||
total & 23{,}851 & s & enqueue + drain \\
|
||||
processed & 30 & docs & \pass\ drained \\
|
||||
Throughput cola & $\approx$4\,528 & docs/h & \textbf{solo mock}; no usar como SLO LLM \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{ST-EXT-01 --- cola ARQ con LLM mock (shared8g, 30 docs).}
|
||||
\label{tab:st-ext-01}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.75\textwidth,
|
||||
height=5.2cm,
|
||||
ylabel={Segundos},
|
||||
symbolic x coords={Enqueue,Drain,Total},
|
||||
xtick=data,
|
||||
nodes near coords,
|
||||
bar width=18pt,
|
||||
ymin=0, ymax=30,
|
||||
enlarge x limits=0.25,
|
||||
grid=major,
|
||||
title={ST-EXT-01 --- tiempos (30 docs, mock)}
|
||||
]
|
||||
\addplot[fill=ipsblue!70] coordinates {(Enqueue,1.307) (Drain,22.544) (Total,23.851)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Casi todo el tiempo es drenado por workers; el enqueue es despreciable.}
|
||||
\label{fig:st-ext-01-times}
|
||||
\end{figure}
|
||||
|
||||
\section{Lectura}
|
||||
\begin{itemize}
|
||||
\item La arquitectura \textbf{Gateway $\rightarrow$ Redis/ARQ $\rightarrow$ workers} funciona bajo el perfil shared8g.
|
||||
\item El throughput $\approx$4\,500 docs/h \textbf{no} es capacidad de extracci\'on real:
|
||||
ST-EXT-02 midió $\approx$506 fact/h con gpt-5.4-mini.
|
||||
\item Recomendaci\'on prod: \texttt{ASYNC\_QUEUE=true} + workers dimensionados a la cuota LLM.
|
||||
\item Pendiente de escala: perfil \texttt{lab} (100--500 docs, 2 workers).
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,85 @@
|
||||
\chapter{Count-api bajo carga (ST-COUNT-01)}
|
||||
\label{ch:st-count-01}
|
||||
|
||||
\callout{\textbf{Veredicto ST-COUNT-01:} \pass\ (shared8g)}{%
|
||||
9/9 PDFs reales OK; 0$\times$5xx; sin OOM observado.\\
|
||||
Bajo concurrencia 3, p50 sube a \textbf{35{,}8\,s} (vs $\sim$2\,s en serie).\\
|
||||
Throughput observado $\approx$\textbf{325} fact/h en la ventana.
|
||||
}
|
||||
|
||||
\section{Qu\'e eval\'ua / resultado / significado}
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{3.2cm}p{4.5cm}p{5.0cm}@{}}
|
||||
\toprule
|
||||
\textbf{Pregunta} & \textbf{Respuesta} & \textbf{Implicaci\'on} \\
|
||||
\midrule
|
||||
Qu\'e eval\'ua & OCR count-api con lotes concurrentes & Cuello de 100k/mes \\
|
||||
C\'omo & \texttt{st\_count\_01\_bench} 60\,s, max 3 workers & PDFs reales en \texttt{data/} \\
|
||||
Resultado & 9 ok; p50 35{,}8\,s; 0 fallos & Contenci\'on fuerte vs serie \\
|
||||
Significado & Host shared: $\sim$100--325 fact/h & vs 139/h \'ambar/OK; vs 570/h NO \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\section{Condiciones}
|
||||
\begin{itemize}
|
||||
\item Fecha: 2026-07-17; perfil \texttt{shared8g}; rate objetivo 1/s; max 3 concurrentes; 60\,s.
|
||||
\item Endpoint: \texttt{POST /count/v1/count} (campo multipart \texttt{files}).
|
||||
\item CSV: \texttt{informes/st\_count\_01\_20260717.csv}.
|
||||
\item Nota: k6 no estaba en el PATH de Windows; se us\'o el bench Python equivalente.
|
||||
El script k6 se corrigi\'o (\texttt{files} en lugar de \texttt{file}).
|
||||
\end{itemize}
|
||||
|
||||
\section{Resultados}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}llrl@{}}
|
||||
\toprule
|
||||
\textbf{M\'etrica} & \textbf{Valor} & \textbf{Unidad} & \textbf{Notas} \\
|
||||
\midrule
|
||||
ok / total & 9 / 9 & --- & 0 fallos \\
|
||||
wall\_s p50 & \textbf{35{,}825} & s & bajo 3 concurrentes \\
|
||||
wall\_s p95 & 43{,}927 & s & min 17{,}6 / max 44{,}7 \\
|
||||
wall total & 99{,}7 & s & incluye drenado final \\
|
||||
Throughput observado & $\approx$325 & fact/h & $9/99{,}7\times 3600$ \\
|
||||
Throughput serie (p50) & $\approx$100 & fact/h & $3600/\mathrm{p50}$ \\
|
||||
$\times$3 ideal & $\approx$301 & fact/h & sin m\'as contenci\'on \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{ST-COUNT-01 --- carga OCR (PDFs reales, shared8g).}
|
||||
\label{tab:st-count-01}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.8\textwidth,
|
||||
height=5.5cm,
|
||||
ylabel={Facturas / hora},
|
||||
symbolic x coords={Serie-p50,Obs-3conc,Obj-139,Obj-570},
|
||||
xtick=data,
|
||||
x tick label style={font=\small},
|
||||
nodes near coords,
|
||||
bar width=12pt,
|
||||
ymin=0, ymax=700,
|
||||
enlarge x limits=0.15,
|
||||
grid=major,
|
||||
title={OCR ST-COUNT-01 vs objetivos 100k}
|
||||
]
|
||||
\addplot[fill=ipsblue!70] coordinates {(Serie-p50,100) (Obs-3conc,325) (Obj-139,139) (Obj-570,570)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Bajo 3 concurrentes el OCR cubre 139/h en esta muestra; no alcanza 570/h.
|
||||
La latencia por PDF se degrada mucho frente a la serie ($\sim$2\,s).}
|
||||
\label{fig:st-count-01}
|
||||
\end{figure}
|
||||
|
||||
\section{Lectura}
|
||||
\begin{itemize}
|
||||
\item \pass\ funcional: count-api aguanta concurrencia ligera sin 5xx/OOM en esta corrida.
|
||||
\item La degradaci\'on 2\,s $\rightarrow$ 36\,s confirma que el OCR es sensible a CPU/RAM compartida.
|
||||
\item Actualiza la fila OCR del modelo 100k: de $\sim$40 estimados a $\sim$100--325 medidos (shared8g).
|
||||
\item Para afirmar 570/h hace falta host dedicado + pool OCR (perfil lab / ST-COUNT agresivo).
|
||||
\end{itemize}
|
||||
Reference in New Issue
Block a user