FINORA|Documentación
EcosistemaPresentaciones
Operaciones · Documentación

Arquitectura a escala

Cómo lleva la suite FINORA la orquestación de pagos —aceptación multi-riel, switch, interconexión bancaria, clearing y liquidación— de un millón a cincuenta millones de transacciones diarias, por escalones que se miden antes de declararse cerrados.

Estado En redacciónDocumento fuente finora-docs/architecture/ARQUITECTURA_ORQUESTACION_PAGOS_ESCALA_2026-09-12.mdActualizado 2026-09-12
Hoy · E0
100 TPS pico
≈1 M tx/día objetivo de diseño · latencia puntual del switch 99 ms medido
Escalón E1
500 TPS pico
≈5 M tx/día · Merchant sin estado, Redis compartido, Patroni, particionado
Escalón E2
1.500 TPS pico
≈15 M tx/día · switch A/B, Kafka único, CQRS y ledger, HSM real
Escalón E3
5.000 TPS pico
≈50 M tx/día · sharding Citus, switch por BIN, Kubernetes, frío columnar

1Resumen ejecutivo

Regla de lectura. Cada cifra lleva su etiqueta: medido se observó en un entorno real; objetivo es una meta escrita en un documento de diseño; estimado es un cálculo de este documento. Ninguna cifra de diseño se presenta como medida.

Dónde estamos. El diseño documentado de la base del core apunta a ≈1 M tx/día, 12 TPS promedio y 100 TPS pico objetivo. El switch declara más de 1.000 TPS y P99 menor de 100 ms objetivo sin prueba de carga registrada; la única medición disponible es una transacción de 99 ms en la demo local del 2026-09-12 medido, sin p95.

Qué ya existe y funciona (verificado en código): Kafka real con outbox en el core consumido por Invest y Trust; Quartz clusterizado en el core; circuit breakers en switch, core, edge y legacy-bridge; idempotencia por índice único en el Merchant (UQ_DAT_INTENT_IDEMPOTENCY); claves API por comercio (DatMerchantApiKey); Payment Hub ISO 20022 con pacs.008 y pacs.002 operativos; Pulse leyendo una réplica.

Qué satura primero: un único primario PostgreSQL, un único servidor Merchant (InMemoryDispatchChannel), dedupe del switch por consulta a DatCardLog, Redis de 256 MB sin clúster, agregaciones de liquidación en memoria y archivos de clearing en el disco del contenedor.

Las cinco decisiones

  1. Aceptación sin estado. Todo componente que multiplica (Merchant, switch) no guarda nada entre peticiones; el estado compartido —despacho, dedupe, idempotencia, límites— vive en Redis.
  2. Switch activo-activo. Dos instancias Aurora con dedupe y SAF compartidos, particionables por BIN o nodo emisor, con HSM real detrás del mismo HsmPort.
  3. Datos por escalón. Patroni, particionado mensual, resúmenes por evento y réplicas de lectura en E1; CQRS y ledger append-only en E2; sharding con Citus en E3.
  4. Un solo backbone de eventos. Kafka, con trace_id de la caja a la cabina; el redpanda del compose, que ningún código usa, se retira.
  5. Medir antes de mover. Ningún escalón se declara cerrado sin una prueba de carga reproducible que lo valide.

2Dimensionamiento

Factor hora pico de 8 a 10 veces el promedio: quincenas, diciembre, Black Friday. Es el mismo factor que ya usan el análisis de 20 M usuarios (12 → 100 TPS) y el diseño del Merchant a escala (6 M/día → 300–500 TPS).

TPS pico por escalón

Capacidad objetivo de cada escalón; el de hoy no tiene medición de carga que lo respalde.

TPS pico por escalónBarras: E0 hoy 100 TPS (objetivo de diseño, sin medición), E1 500 TPS, E2 1.500 TPS, E3 5.000 TPS. 5.000 3.000 1.000 100E0 · hoysin medición 500E1≈5 M/día 1.500E2≈15 M/día 5.000E3≈50 M/día

Escala lineal a propósito: la distancia entre E0 y E3 es de 50 veces y se muestra tal cual. La barra rayada marca la única cifra sin prueba de carga. Detalle numérico en la tabla siguiente.

Escalóntx/díaTPS promedioTPS pico (×8–10)Filas/añoDatos/añoQué satura primero (evidencia)
E0 hoy≈1 M obj.12100365 M0,6–1,2 TB est.Nada hasta 100 TPS si el switch cumple su objetivo; no hay medición
E15 M585001,8 G3–6 TBInMemoryDispatchChannel (un servidor), sesión Jmix, dedupe por count en DatCardLog, Redis 256 MB
E215 M1741.5005,5 G9–18 TBUn solo primario con 6 índices en la tabla caliente; agregaciones en memoria de PosSettlementService; sweeper netgw de una instancia
E350 M5795.00018 G30–60 TBTecho de escritura de un primario (~8–10 K TPS), particiones Kafka, capacidad del HSM

El tamaño por fila (1–2 KB en dat_acquiring_transaction, ×1,6 con índices) es estimado a partir de la entidad; se mide con pg_total_relation_size tras la primera prueba de carga. dat_payment_event añade ≈5 eventos por pago de ≈300 B y por eso nace append-only y particionada. Retención en línea 13 meses; legal 7–10 años en frío.

3Arquitectura objetivo por capa

Cada tarjeta contrasta lo verificado en el código hoy con el componente real de FINORA que lo sustituye. Nada cambia de contrato con la caja, el terminal ni el banco.

Captura y aceptación · Merchant finora-pos

Por qué: es el componente que multiplica (cada terminal, cada caja, cada tienda). Si no es sin estado, ningún balanceador ayuda.

Hoy (verificado)

  • Una instancia; InMemoryDispatchChannel con una cola por terminal («sirve para una instancia del servidor, que es el despliegue de hoy»)
  • Idempotencia por índice único UQ_DAT_INTENT_IDEMPOTENCY
  • Claves API por comercio (DatMerchantApiKey); permitir-globales con default true
  • Sin rate limiting por comercio; RC 96 y EN_DUDA ya bien resueltos hacia el switch

Objetivo

  • N réplicas sin estado detrás de HAProxy ×2 con VIP; UI con sesión pegajosa, API libre
  • DispatchChannel sobre Redis pub/sub (el javadoc ya lo prevé: «se sustituye este bean y nada más cambia»)
  • Redis SETNX 48 h delante del índice; token bucket por comercio y terminal
  • POS_API_KEYS_PERMITIR_GLOBALES=false en producción y rotación de pos-dev-key-2026

Switch Aurora

Por qué: la instancia única es el punto único de fallo del riel de tarjetas, y el dedupe consulta una tabla que crece 365 M filas al año en cada transacción.

Hoy (verificado)

  • Una instancia; AuroraNettyServer con un par boss/worker, puerto 9502 + 9503/9504
  • Dedupe: TransactionService.isDuplicated hace un count JPQL sobre DatCardLog por seis campos
  • Pipeline @AuroraStep con circuit breaker; NodeRouter por prefijo BIN; SAF persistido
  • SimulatorHsmAdapter detrás de HsmPort

Objetivo

  • A/B activo-activo detrás de un port manager externo; salud por /actuator/health
  • Dedupe en Redis compartido; índice compuesto en DatCardLog solo para conciliación
  • Pool por nodo con latido 0800/0810; SAF compartido para que cualquier nodo drene reversos
  • HSM real (presupuesto: P99 criptográfico ≤ 50 ms); desde E2, instancias por BIN/nodo

Interconexión bancaria · Payment Hub finora-netgw, SWIFT, nostro/vostro

Por qué: el hub ya está bien diseñado (hexagonal, idempotente, estado por confirmación). Lo que falta es operativo: multi-réplica del sweeper y cierre del ciclo con el core.

Hoy (verificado)

  • pacs.008 salida + BAH, pacs.002 entrada; camt.053/054 en hoja de ruta
  • Outbox JPA con UNIQUE(idempotency_key, operation); el código deja el TODO multi-réplica: FOR UPDATE SKIP LOCKED
  • Rieles SWIFT/RTGS/ACH/INSTANT/INTERNAL por país; circuit breaker + reintento, sin DLQ
  • El core no consume finora.payments.status; MT y MX desde DatSwiftMessage; monitor nostro/vostro operativo

Objetivo

  • camt.053/054 entrada con conciliación de huérfanas y posición nostro/vostro alimentada por extracto
  • Sweeper con SELECT … FOR UPDATE SKIP LOCKED para N réplicas
  • Un adaptador por riel real con circuit breaker, reintento con jitter y cola de muertos
  • Tópico finora.payments.status consumido por el core para cerrar el ciclo contable

Datos

Por qué: a 1–2 KB por fila el problema no es el espacio sino el mantenimiento de índices y el VACUUM sobre miles de millones de filas. Particionar primero, resumir por evento, y solo al tocar el techo del primario, shardear.

Hoy (verificado)

  • Un primario PostgreSQL 16; pgbouncer en el compose
  • dat_acquiring_transaction sin particionar, seis índices; cabeceras con SUM sobre la tabla grande
  • Pulse ya lee una réplica (PulseReadReplica)
  • Saldos y estados se actualizan en sitio; todo el histórico en línea

Objetivo

  • E1: Patroni + etcd, pgBackRest con PITR fuera de sitio, pgbouncer en transaction pooling; particionado mensual de transacción, clearing, intento y dat_payment_event; BRIN e índices parciales; resúmenes sum_outlet_daily, sum_terminal_daily, sum_merchant_hourly por evento; réplica de reportes
  • E2: CQRS + ledger append-only para posiciones y liquidación, proyecciones reconstruibles
  • E3: Citus por merchant_id / pan_hash, EOD por shard; particiones mayores de 13 meses a columnar/Parquet

Eventos

Por qué: un solo backbone con clave de partición por comercio y por nodo da orden y reproducibilidad; dos brokers en el compose sin consumidores solo dan confusión.

Hoy (verificado)

  • Kafka real: OutboxKafkaConfig + OutboxRelayJob en el core, consumidos por Invest y Trust
  • redpanda levantado en el compose sin ningún consumidor en código

Objetivo

  • Kafka de 3 brokers (KRaft) como único backbone; tópicos por dominio (finora.acc.entries.posted, finora.payments.status, finora.pos.transaction.events, finora.switch.transaction.events)
  • trace_id nace en la caja y viaja en cabecera Kafka, ISO 8583 y dat_payment_event
  • Retirar redpanda del compose

Clearing y liquidación

Por qué: los jobs y las agregaciones fueron escritos para una instancia; a escala hay que clusterizarlos, paralelizarlos por marca y sacar los archivos del disco del contenedor.

Hoy (verificado)

  • Archivos en clearing-out/ y settlement-out/ del contenedor; formato lógico textual, no el físico GCMS
  • PosClearingJobPosSettlementJobReconciliationJob; finora-pos no declara isClustered (el core sí)
  • computePositions y computeMerchantSettlements agrupan en memoria
  • Posteo al core tras flags (default false); doble partida asíncrona cada 15 min; settlement Mastercard real pendiente

Objetivo

  • Object storage (MinIO/S3) con versionado y checksum; formato físico IPM/GCMS con la certificación
  • Quartz clusterizado en finora-pos; jobs paralelos por marca e institución
  • Agregación incremental en SQL por partición; tarifa por registro cuando BIN/MCC difieren
  • Encender el posteo solo con la conciliación de tres vías cubierta

Observabilidad

Por qué: hoy no existe un tablero transaccional en tiempo real del Merchant; MerchantDashboardService alimenta un panel embebido en la ficha del comercio, no una cabina.

Hoy (verificado)

  • Prometheus + Grafana en el compose; netgw_outbound_total{operation,rail,outcome}
  • Pulse con 8 pantallas por STOMP; monitor de salud de 12 servicios

Objetivo

  • Métricas de negocio con trace_id: TPS por riel y comercio, RC por nodo emisor, EN_DUDA abiertas y antigüedad, P50/P95/P99 por pipeline, lotes sin cerrar, cola de despacho por terminal, lag de Kafka y de réplica, profundidad del outbox
  • Pulse como cabina de mando del Merchant; alertas por escalón (P95 > 200 ms, RC 96 > 0,5 %, EN_DUDA > 50, lag > 30 s)

Seguridad y PCI

Por qué: el PAN nunca sale del terminal ni del switch; la caja no lo ve y el Merchant guarda solo el enmascarado. Tokenización de red y HSM real dependen del banco patrocinante y de la certificación de marca.

Hoy (verificado)

  • Hallazgo cerrado: apply-concept del core sí exige @PreAuthorize("hasAuthority('SCOPE_accounting:write')") (commit 595b64a, 2026-07-02); el inventario del 12-09 lo listaba como abierto y aquí queda corregido
  • finora.pos.api-keys.permitir-globales con default true; pos-dev-key-2026 en compose y docs

Objetivo

  • POS_API_KEYS_PERMITIR_GLOBALES=false en producción; rotación de la clave de desarrollo
  • mTLS con nodos de banco; secretos fuera de imágenes; trigger que bloquee UPDATE/DELETE en dat_payment_event
  • Tokenización MDES/VTS y HSM físico cuando exista convenio de red

4Hoja de ruta por escalones

Esfuerzo estimado en semanas de un equipo de dos ingenieros backend y uno de plataforma. La prueba de carga es el criterio de salida: sin ella el escalón no se declara cerrado.

E0 · hoy
100 TPS pico
Una instancia de cada cosa
  • Kafka, outbox, circuit breakers y Quartz clusterizado ya existen
  • Sin prueba de carga registrada
Primera prueba: simulador ISO 8583 a 100 TPS, 10 min, para tener la línea base medida.
E1 · ≈17 semanas
500 TPS pico
≈5 M tx/día · ≈10 semanas de calendario en paralelo
  • Redis pub/sub, dedupe e idempotencia compartidos
  • Patroni + pgBackRest + pgbouncer
  • Particionado mensual, dat_payment_event, resúmenes por evento
  • Sweeper netgw multi-réplica; Quartz clusterizado en POS; archivos a MinIO
  • Cabina Pulse con métricas de negocio
Prueba: k6 a 500 TPS mixto 30 min con dos réplicas; p95 < 200 ms, error < 0,1 %; conmutación forzada del primario con RPO ≤ 5 min y RTO ≤ 30 min.
E2 · ≈20 semanas
1.500 TPS pico
≈15 M tx/día · más lead time del HSM (8–12 semanas)
  • Switch A/B activo-activo con port manager y SAF compartido
  • Kafka de 3 brokers; retiro de redpanda; estados al core
  • CQRS + ledger append-only; agregaciones SQL por partición
  • HSM real detrás de HsmPort; réplica dedicada a conciliación
Prueba: 1.500 TPS con caída de A a mitad de prueba y 0 transacciones perdidas; liquidación del día < 30 min; P99 criptográfico ≤ 50 ms.
E3 · ≈21 semanas
5.000 TPS pico
≈50 M tx/día · CAPEX de cluster, HSM ×2 y sitio de recuperación
  • Citus por merchant_id / pan_hash; EOD por shard
  • Switch particionado por BIN/nodo
  • Kubernetes con autoescalado y segmentación CDE
  • Frío columnar para más de 13 meses
Prueba: 5.000 TPS sostenidos 1 h; EOD < 60 min; rebalanceo de un shard sin parada; estado de cuenta de 5 años < 500 ms.

Antes de E3 hay que confirmar que el negocio necesita 50 M tx/día: es un salto de infraestructura que el plan maestro cifra en CAPEX de seis cifras.

5Riesgos y dependencias externas

Riesgo / dependenciaImpactoMitigación
Certificación de marca (Visa / Mastercard)Sin ella no hay clearing físico ni settlement real; bloquea E2 en tarjetasAvanzar E1 con el formato lógico; simulador y banco simulado; paquete de certificación en paralelo
Banco patrocinanteSu API es hoy punto único de fallo; sin orquestación en producciónNodo autorizador replicado, circuito abierto por nodo, EN_DUDA con barrido; segundo banco por convenio
Firma de la aplicación Wiseasy (T2 / RX / P5L)Sin firma no hay EMV real en terminalCertificación con el fabricante; mientras tanto ECR + PIN pad certificado
HSM realProcurement de 8–12 semanas y CAPEX; bloquea E2Iniciar al cerrar E1; HSM en nube como puente si el P99 cabe en 50 ms
Regulación VE (adquirencia reservada a bancos)Define el modelo de negocioModelo de administradora bajo banco patrocinante
Cifras de diseño tomadas por medidasDecisiones de capacidad erróneasNada se declara cerrado sin prueba de carga registrada

6Diagramas

Diagramas interactivos (pan, zoom, búsqueda, vistas guiadas, exportación). Se abren en la misma pestaña.

7Referencias

  • finora-docs/architecture/ARQUITECTURA_ORQUESTACION_PAGOS_ESCALA_2026-09-12.md — el documento completo del que sale esta página.
  • finora-docs/architecture/ANALISIS_ESCALABILIDAD_20M_USUARIOS.md — escalones F1–F3 y técnicas de lectura histórica.
  • finora-docs/architecture/ARQUITECTURA_TRANSACCIONES_ALTA_ESCALABILIDAD.md y ARCHITECTURE_HIGH_PERFORMANCE.md — diseño del core para +1 M tx/día (cifras objetivo).
  • finora-docs/architecture/ADR_EVENT_BACKBONE.md — outbox transaccional y elección de broker.
  • finora-docs/architecture/GAP_ANALYSIS_PAYMENTS_2026-07.md — brechas del Payment Hub y del lifecycle adquirente.
  • finora-docs/architecture/PLAN_MAESTRO_INFRAESTRUCTURA.md — Kubernetes on-premise, HSM, segmentación PCI, costos.
  • finora-docs/merchant/ARQUITECTURA_MERCHANT_ESCALA_2026-09-04.md — jerarquía, resúmenes por evento y alta disponibilidad del Merchant.
  • finora-docs/merchant/DISENO_COBRO_EMPUJADO_2026-09-11.md — canal de despacho (decisión D6).

Otras guías

FINORA CoreCore bancario: clientes, cuentas, créditos, contabilidad, cumplimiento y SWIFT. Switch AuroraSwitch transaccional ISO 8583: pipeline de pasos, enrutamiento por BIN, nodos emisores y modo core. FINORA MerchantNodo de aceptación: comercios, terminales, medios de pago, orquestación, lotes y liquidación. Payment Hub (netgw)Mensajería ISO 20022: pacs.008/002, outbox y rieles SWIFT, LBTR, CCE e interno. Banco simuladoEl banco visto desde afuera: pago móvil, transferencias, QR, cripto y host ISO 8583 con reglas públicas. InstaladorInstalación dev/qa/prod por país con baseline y contextos Liquibase.