Skip to content

La carga de GOA filtra la accesión antes de construir el record, sin mover una línea de página - #998

Merged
github-actions[bot] merged 2 commits into
developfrom
feat/goa-accept-prefilter
Oct 8, 2026
Merged

github-actions[bot] merged 2 commits into
developfrom
feat/goa-accept-prefilter

Conversation

@frapercan

Copy link
Copy Markdown
Owner

Qué cambia

load_goa_annotations pasa al plugin de GOA el predicado accept que protea-sources 0.2.1 ya trae en el lock (946227ca). Así el plugin no construye un GoaAnnotationRecord para las líneas de proteínas que no están en el corpus. Son el 98%: en la 167, 385,0 M de 392,8 M líneas.

La prueba de accesión es la misma de _store_buffer, carácter a carácter: strip y pertenencia, y una accesión vacía se rechaza.

Qué NO cambia

Todo lo que deja una carga sale igual: las filas de cada INSERT, la secuencia completa de eventos, los commits y todos los contadores del informe (pages, total_lines_read, annotations_inserted y los tres cubos de rechazo).

  • Las líneas rechazadas siguen contando. Se cuentan en vez de guardarse en el buffer, pero conservan su sitio en lines, en su página y en not_in_universe, así que las páginas terminan en las mismas líneas.
  • Una página de puros rechazos se trata como cualquier otra: se vuelca, hace commit y emite page_done.
  • source.goa.download_done se retiene hasta colocar los rechazos finales, porque antes llegaba después de su page_done. Si total_limit corta la carga, no se emite, igual que antes.

Medido

En el nodo de cómputo (i5-12400F, Python 3.14), sobre las primeras 8 M líneas de la release 190, con page_size 200.000 y sesión mock. Es solo CPU, sin round trips a la base de datos:

CPU líneas/s de CPU
sin filtro 39,6 s 202.224
con filtro 8,1 s 992.555

Sale 4,9x, con los mismos totales en las dos corridas: lines 7.999.992, inserted 14.834, not_in_universe 7.984.869 y 40 páginas.

La cabeza del fichero acepta el 0,19% de las líneas, menos que la media de una release, así que en una release completa la ganancia será algo menor. La cifra a nivel de job queda para cuando corra en el portátil.

Pruebas

tests/test_goa_accession_prefilter.py ejecuta cada GAF de tres formas:

  • el bucle de antes, copiado literalmente de develop a38486d;
  • el actual con prefilter=False;
  • el actual con el filtro.

Exige iguales totales, eventos, commits y filas. Lo hace sobre:

  • un GAF de casos límite, con 56 combinaciones de page_size, total_limit y commit_every_page;
  • 60 GAF aleatorios (2.000 semillas más pasaron en local).

Otro test cuenta los records construidos, para que quitar accept= no pase en silencio.

Además pasan:

  • los 397 tests de los ficheros que tocan la carga de GOA;
  • ruff check protea scripts;
  • mypy de los dos módulos;
  • check_smells.py, sin escribir baseline: el bucle sale a _goa_pages.py, como el informe salió a _goa_load_report.py;
  • el resto de chequeos de lint.

Auto-merge desactivado: la sesión del portátil revisa el test diferencial antes del merge, porque su contabilidad es la evidencia de que las releases cargadas valen.

…mover una línea de página

Casi todas las líneas de una release de GOA son de proteínas de fuera del
corpus: en la 167, 385,0 M de 392,8 M. El plugin de GOA construía un
GoaAnnotationRecord validado para cada una, el bucle lo guardaba en el buffer y
_store_buffer lo recorría otra vez solo para tirarlo. protea-sources 0.2.1 (ya
en el lock, 946227ca) acepta un predicado `accept` sobre las columnas crudas y no
construye record para una línea que rechaza. Ahora la carga se lo pasa, con la
misma prueba de accesión que aplica _store_buffer, carácter a carácter.

Lo único que cambia es la velocidad. Una línea rechazada antes de tiempo no lleva
datos, así que se cuenta en vez de guardarse en el buffer, pero sigue ocupando su
sitio en `lines`, en su página y en `not_in_universe`. Las páginas terminan en las
mismas líneas. Una página entera de rechazos se vuelca, hace commit y emite su
page_done como cualquier otra. source.goa.download_done se retiene hasta colocar
los rechazos finales, porque antes llegaba después de su page_done.

Medido en el nodo de cómputo (i5-12400F, Python 3.14), sobre las primeras 8 M
líneas de la release 190, con page_size 200.000 y una sesión mock, así que es
solo CPU, sin round trips a la base de datos:
- sin filtro: 39,6 s de CPU, 202.224 líneas por segundo de CPU;
- con filtro: 8,1 s de CPU, 992.555 líneas por segundo de CPU;
- 4,9x, con los mismos totales en ambos: lines 7.999.992, inserted 14.834,
  not_in_universe 7.984.869 y 40 páginas.
La cabeza del fichero acepta el 0,19% de las líneas, por debajo de la media de
una release entera, así que en una release completa la ganancia será algo menor.

El bucle de páginas sale a _goa_pages.py, como el informe salió a
_goa_load_report.py, para que la operación no pase de los topes de tamaño.
_flush_page y _store_buffer no cambian.

Pruebas: tests/test_goa_accession_prefilter.py ejecuta cada GAF de tres formas:
- el bucle de antes, copiado literalmente;
- el actual con prefilter=False;
- el actual con el filtro.
Exige iguales los totales, la secuencia completa de eventos, los commits y las
filas de cada INSERT. Lo hace sobre un GAF de casos límite con 56 combinaciones
de page_size, total_limit y commit_every_page, y sobre 60 GAF aleatorios (2.000
semillas más pasaron en local). Otro test cuenta los records construidos, para
que quitar `accept=` no pase en silencio.
Pasan los 397 tests de los ficheros que tocan la carga de GOA, ruff, mypy de los
dos módulos, check_smells y el resto de chequeos de lint.
@github-actions
github-actions Bot enabled auto-merge (squash) October 8, 2026 17:57
@frapercan
frapercan disabled auto-merge October 8, 2026 17:57
@frapercan

Copy link
Copy Markdown
Owner Author

Revisado el test, que era lo acordado, y aprobado. Dejo constancia de lo que comprobé y de lo que el test no puede probar por sí solo.

El test hace lo que hace falta

_assert_same_load compara cuatro cosas, tres veces (referencia copiada de a38486d, actual con prefilter=False, actual con el filtro):

  • totals — el objeto entero, así que entran pages, lines, inserted y los tres cubos de rechazo
  • events — la lista ORDENADA de (event, fields, level), que es lo que cubre la recolocación de source.goa.download_done y los totales corrientes de cada page_done
  • commits — session.commit.call_count
  • rows — los parámetros ligados de cada INSERT, sacados con stmt.compile(dialect=postgresql.dialect()).params

Comparar rows compilados es más de lo que yo habría pedido: atrapa cualquier cambio de contenido o de orden en lo que entra, no solo en los contadores.

La matriz es la correcta por donde duele: page_size en 1, 2 y 3 fuerza que los rechazos cruzen cada frontera de página, que es donde _with_rejections_before se rompería. Y test_a_page_of_foreign_proteins_still_flushes_commits_and_reports fija el contrato con números, no con prosa: total_lines == [3, 6], total_inserted == [0, 0], commits == 2, pages == 3, not_in_universe == 6.

test_the_prefilter_builds_records_only_for_the_corpus tapa el agujero que yo habría buscado primero: sin él, quitar accept= dejaría los tests de igualdad en verde y la carga perdería todo el sentido.

Lo que verifiqué aparte, porque el test lo ejercita pero no lo declara

Toda la aritmética de _goa_pages descansa en un supuesto sobre el plugin que no se puede revisar leyendo el diff: que accept se llame una vez por línea y en orden, sin bufferear. Si el plugin filtrara por bloques, rejected_so_far() leído en el yield ya incluiría líneas posteriores al record entregado, y los rechazos caerían en la página equivocada.

Lo probé contra protea_sources 0.2.1 instalado, con un GAF gzipado servido por HTTP real — no con el io parcheado del test — y 40 líneas de datos donde 1 de cada 4 está en el universo:

accept llamado 40 veces para 40 líneas de datos
10 records entregados, en orden
columna 1 == rec.accession, carácter a carácter   (P00000 vs P00000)
en cada entrega el contador vale exactamente 4n+1
3 rechazos tras el último aceptado, contados

Perezoso, uno por línea, en orden. El supuesto se sostiene, y _GAF_ACCESSION = 1 es la columna correcta.

Las dos vías juntas cierran el hueco: el test ejercita el plugin de verdad a través de _stream_gaf con el texto inyectado, y esta sonda cubre además la capa de HTTP y gzip que el test no toca.

Dos observaciones, ninguna bloqueante

  1. La matriz de page_size llega a 10.000, pero producción corre ya a 200.000 desde la release 175. La aritmética es independiente del tamaño y los casos adversos son 1, 2 y 3, así que el riesgo es bajo; añadir 200.000 a la lista cuesta un carácter y deja el valor de producción cubierto.
  2. La cifra del commit: de acuerdo con usar la medida y no la derivada. Con el aviso que tú mismo pones, que la cabeza de la 190 acepta el 0,19% contra una media del 1,94%, en una release entera el factor será menor que 4,9x. Mi medición por job lo dirá.

Nota de contabilidad, para que quede en el registro: skipped_duplicate_in_batch no lo toca este cambio porque el seen de _store_buffer sigue viendo los mismos records conservados por página. Lo que sí lo mueve es page_size, y eso ya está escrito en el driver como frontera entre las releases 156-174 y el resto.

@github-actions
github-actions Bot enabled auto-merge (squash) October 8, 2026 18:03
@github-actions
github-actions Bot merged commit 55d0df3 into develop Oct 8, 2026
17 checks passed
@codecov

codecov Bot commented Oct 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.08738% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.81%. Comparing base (d8bc801) to head (b97e9e2).
⚠️ Report is 98 commits behind head on develop.

Files with missing lines Patch % Lines
protea/core/operations/_goa_pages.py 97.00% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #998      +/-   ##
===========================================
- Coverage    87.85%   87.81%   -0.05%     
===========================================
  Files          306      330      +24     
  Lines        23414    25408    +1994     
===========================================
+ Hits         20570    22311    +1741     
- Misses        2844     3097     +253     
Files with missing lines Coverage Δ
protea/core/operations/load_goa_annotations.py 90.95% <100.00%> (-0.33%) ⬇️
protea/core/operations/_goa_pages.py 97.00% <97.00%> (ø)

... and 35 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant