Pasado, presente y futuro de la plataforma interna de Akua

    Cómo reemplazamos AWS DMS por data-replicator: un replicador propio con garantía exactly-once, fallas acotadas y alertas que no se quedan calladas.

    Luis Petoloy 12 de noviembre de 2026 12 min
    Pasado, presente y futuro de la plataforma interna de Akua

    Hoy quiero contarles un poco del pasado, el presente y el futuro que imaginamos para el IDP (Internal Developer Platform) de Akua. Para arrancar, una frase de Kung Fu Panda:

    “El ayer es historia, el mañana es un misterio, el hoy es un regalo; por eso se llama presente.”

    ¿Por qué la traigo? Porque pasaron muchas cosas desde junio de 2024, cuando empezamos a diseñar la plataforma interna de desarrollo de Akua junto a German Yepes, mi compañero de equipo y amigo, que en diciembre de 2025 decidió seguir su camino fuera de Akua. En el trayecto también nos acompañó Guille Pijuan, que hoy sigue en Akua en el equipo de CX (Customer Experience). Y en el presente comparto equipo con Cristian Ortega (Cris) y Jeremy Bacher (Jere).

    Hace casi dos años les contamos qué es nuestro IDP. Hoy vengo a contarles un proyecto en particular: Good Bye AWS DMS (Database Migration Service).

    Con este post arranca la trilogía de la Plataforma: me toca abrir a mí y, muy pronto, Cris y Jere van a contar otros desafíos técnicos que nos enorgullece compartir. Comencemos.

    01 · El ayer es historia

    AWS DMS: cuando el problema no es fallar, sino fallar en silencio

    Primero, un poco de contexto sobre cómo se mueven los datos en Akua: cada proyecto tiene su propia base Aurora PostgreSQL y cada tenant tiene su warehouse, el ODS (Operational Data Store). Ahí se juntan las bases de todos los proyectos, para que quien necesite consultar esos datos lo haga en el ODS y no contra la base productiva.

    Para llevar los datos de un lado al otro usábamos AWS DMS Serverless, que en teoría es justo lo que se necesita: hace una carga inicial y después replica los cambios en tiempo real (CDC, Change Data Capture). En teoría.

    En la práctica, DMS fallaba. Y lo que más nos dolía no era que fallara (todo falla tarde o temprano), sino cómo fallaba: en silencio. Esto es lo que encontramos el día que nos pusimos a mirar en serio:

    • Replicaciones muertas sin que nadie se enterara: 8 replicaciones en estado failed durante meses, sin ninguna señal.
    • El “running” que no corría: tablas en Table error mientras la replicación decía estar running. La consola mostraba que todo estaba bien y la tabla hacía rato que no recibía un dato.
    • Duplicados por todos lados: un warehouse con filas duplicadas entre 80 y 100 veces. Sí, leíste bien.
    • Y además, caro: alrededor de USD 7.400 por mes.

    ¿Por qué duplicaba?

    DMS guarda su “hasta dónde repliqué” (el checkpoint) por un lado y escribe los datos por otro. Si algo se corta entre una cosa y la otra, al volver puede reaplicar lo que ya había escrito. Guarden este detalle, porque es justamente lo que resolvimos de otra manera.

    El primero de nuestros mantras de Construir desde las diferencias dice que la operación y el mantenimiento de la plataforma tienen que tender a cero. Con DMS pasaba exactamente lo contrario: había que operarlo incluso para enterarse de que estaba roto.

    Así que nos hicimos la pregunta incómoda: ¿y si lo construimos nosotros? No es una decisión que se tome a la ligera, porque todo lo que construyes después lo tienes que mantener. Pero cuando la herramienta te dice que todo está bien mientras por debajo se está prendiendo fuego, la cuenta empieza a cerrar bastante rápido.

    02 · El hoy es un regalo

    data-replicator: simple a propósito

    El data-replicator es un proceso escrito en Rust (el porqué de Rust da para otro post) que copia las bases Aurora PostgreSQL de cada proyecto al ODS de su tenant y las mantiene al día en tiempo real.

    Aurora del proyecto ──pgoutput (slot lógico)──▶ data-replicator ──SQL──▶ ODS del tenant
       publication                                  1 pod por tenant         schema public_<p>
       usuario dedicado                             1 tarea por pipeline     + origin por pipeline

    La arquitectura es simple a propósito (otro de nuestros mantras: la simpleza ante todo). Corre un pod por tenant en Fargate con todos los pipelines de ese tenant, donde un pipeline es una base de origen. Si un pipeline se rompe, los demás ni se enteran: cada uno corre aislado y supervisado por separado.

    ~USD 7.400

    por mes con AWS DMS

    ~USD 72

    por mes por pod de data-replicator

    Así se ve el cuadro completo; de cada pieza hablamos en las secciones que siguen:

    data replicator


    Figura 1 · data-replicator: de Aurora al ODS por tenant

    Un momento, ¿qué es un slot?

    Para quien nunca tuvo que pelearse con la replicación lógica de PostgreSQL, un mini glosario:

    • Publication: define qué tablas de la base se publican; en nuestro caso, todas (FOR ALL TABLES).
    • pgoutput: el plugin nativo de PostgreSQL que lee el WAL y emite las transacciones como mensajes (Begin, Insert, Update, Delete, Commit…).
    • Slot: un cursor persistente en el origen que retiene el WAL hasta que el consumidor confirma que lo procesó. Ojo: si nadie lo consume, el disco crece y crece.
    • LSN: la posición dentro del WAL; el “por dónde voy”.

    La vida de un pipeline

    Cada pipeline pasa por cuatro etapas:

    1. Bootstrap. Se conecta al origen con un usuario dedicado y al ODS, verifica que exista la publication (la crea la plataforma en el deploy del proyecto, no el pod) y que ninguna tabla esté sin primary key. Esto último no es un capricho: en PostgreSQL, si publicas una tabla sin replica identity, los UPDATE y DELETE de la aplicación sobre esa tabla empiezan a fallar. El gate está para cuidar al proyecto, no al replicador.
    2. Full load consistente. Crea el slot exportando un snapshot y copia cada tabla bajo ese snapshot con COPY. Del lado del ODS, el TRUNCATE, los datos y la marca de estado van en una única transacción por tabla: o la tabla queda completa y active, o no queda nada. Nada de tablas a medio cargar.
    3. CDC. Arranca exactamente desde el punto consistente del slot, así que entre la carga inicial y los cambios no hay huecos ni duplicados. Cada transacción del origen se aplica como una transacción en el ODS, con statements preparados y pipelineados (se envían de a 500 sin esperar respuesta): una transacción pequeña cuesta un round-trip y no seis.
    4. Reinicio. Reutiliza el slot existente y retoma desde el último commit aplicado. Sin full load, salvo que el bootstrap haya quedado a medias.

    El ciclo completo, desvíos incluidos:

    table lifecycle


    Figura 2 · La vida de un pipeline

    03 · El corazón del asunto

    Exactly-once, la garantía que DMS no da

    ¿Se acuerdan del checkpoint que DMS guarda por separado? Nosotros hacemos que los datos y la posición se commiteen juntos, en la misma transacción del ODS, usando los replication origins de PostgreSQL:

    -- simplificado: una vez por sesión, el applier se ata al origin de su pipeline
    SELECT pg_replication_origin_session_setup('<origin de un pipeline>');
    
    -- por cada transacción que llega del origen
    BEGIN;
    SELECT pg_replication_origin_xact_setup('<end_lsn>', '<commit_ts>');
    INSERT ...; UPDATE ...; DELETE ...;
    COMMIT; -- datos y "hasta dónde llegué", juntos o nada

    ¿Qué significa en la práctica? Que si le haces un kill -9 al pod a mitad de una transacción, o se aplicó todo o no se aplicó nada. Y al volver, retoma exactamente desde el último commit, salteando lo que ya estaba aplicado. Lo validamos rompiendo cosas a propósito en dev, con rewinds del slot y failovers de Aurora incluidos.

    Paso a paso, con el kill -9 incluido:

    exactly once

    Figura 3 · Exactly-once con replication origins

    La letra pequeña

    Nada es gratis, y esto también tiene sus condiciones:

    • Un replication origin por pipeline en el ODS. Nos obligó a subir max\\replication\\slots de 20 (el default) a 100. Es un parámetro estático, o sea, reboot del writer. DMS no lo necesitaba justamente porque guarda su checkpoint aparte.
    • Un solo applier por pipeline, siempre. El Deployment corre con strategy: Recreate (nada de rolling update con dos pods escribiendo a la vez) y al arrancar hace fencing de cualquier sesión propia que haya quedado viva. KEDA y HPA están prohibidos en este Deployment, y no es broma.

    04 · Resiliencia

    Fallar en voz alta y en pequeño

    Si DMS fallaba en silencio y a lo grande, acá la idea fue la inversa: fallar en voz alta y de la forma más acotada posible. Algunos casos:

    • Error determinístico en una tabla (un tipo que no encaja, una constraint, una columna que falta): se suspende solo esa tabla, se guarda la razón y salta una alerta P1. El resto del pipeline sigue como si nada.
    • El origen agrega una columna: el ADD COLUMN se replica automáticamente, con el valor que la fuente materializó para las filas viejas. Así, un DEFAULT 'x' deja las filas previas iguales de los dos lados. Si el default es volátil (clock\\\_timestamp(), nextval()), esas filas quedan en NULL en el destino y el replicador avisa.
    • Tabla nueva en el origen: se detecta en su primer cambio y se copia sola.
    • El origen elimina una tabla: no se propaga (DMS sí lo hacía). La copia queda como historial y decide una persona; borrar datos no es algo que queramos que pase solo.
    • Una tabla destino con otro dueño: si otro escritor creó una tabla con nuestro nombre (nos pasó con un DMS que un deploy volvió a levantar), se suspende esa tabla y jamás se escribe encima.
    • Transacciones gigantes: se bufferean hasta 64 MB y, si son más grandes, se aplican por ventanas dentro de la misma transacción del ODS. La memoria la acota la ventana, no la transacción. Confieso que hasta la v0.2.11 eso era un tope y la transacción que lo superaba dejaba el pipeline en loop; de los errores se aprende.
    • Corte de red u ODS reiniciado: reconexión con backoff y replay desde el origin. Gracias a exactly-once, no hay nada que limpiar.

    ¿Y para recuperar una tabla suspendida? Un UPDATE en la tabla de estado del replicador que la pasa a reload\\\_requested, y el replicador se encarga solo de hacer una copia fresca por un slot temporal. Visto desde una tabla, el ciclo queda así:

    Figura 4 · Estado de una tabla replicada

    Que el silencio también avise

    Todas las métricas salen por OpenTelemetry a Coralogix con el pipeline como label: estado de cada tabla, lag en segundos y en bytes de WAL retenido, eventos descartados, reconexiones y transacciones aplicadas. Sobre eso montamos tres niveles de alertas:

    • P1: tabla suspendida.
    • P2: lag alto, pipeline silencioso o eventos descartados.
    • P3: reconexiones sostenidas o WAL retenido.

    Fíjense en “pipeline silencioso”: es la alerta que nos hubiese ahorrado esos meses de DMS en failed.

    Ahora, ¿qué pasa si el que se cae es el pod? Un pod muerto no puede avisar que está muerto. Para eso tenemos un respaldo desde el origen: una alarma de CloudWatch por cluster que salta si el slot retiene más de 10 GiB durante una hora. Esa alarma sobrevive al pod.

    Y para el día a día, una query que muestra de un vistazo cómo está un tenant:

    -- salud de un tenant, en el ODS: no_activas tiene que ser 0
    SELECT p.pipeline, p.target_schema,
           count(t.table_name) FILTER (WHERE t.state = 'active')  AS activas,
           count(t.table_name) FILTER (WHERE t.state <> 'active') AS no_activas
    FROM _replicator.pipeline_state p
    LEFT JOIN _replicator.table_state t USING (pipeline)
    GROUP BY 1, 2 ORDER BY 1;

    05 · Autodescubrimiento

    Un toggle en Port y el resto se autodescubre

    Port es nuestra capa de presentación, así que acá no podía ser distinto. Cada proyecto tiene una entidad replication en Port con lo mínimo: si está habilitada, a qué schema escribe y alguna que otra opción, con overrides por tenant y entorno. A partir de ahí:

    • El deploy del proyecto: si la entidad está habilitada y el replicador existe en el tenant, crea el publisher: el usuario, la publication, la regla de security group desde el pod y un secret con la conexión. El gate de primary keys que mencioné antes vive acá.
    • El deploy del data-replicator: descubre esos secrets por tag, los cruza con las entidades de Port y arma la configuración de los pipelines. Una entidad habilitada sin publisher, o un publisher sin entidad, genera un warning y sigue.
    • El core-stack del tenant: crea el usuario del lado del ODS y deja max\\replication\\slots en 100.
    “Todo debe autodescubrirse, no hardcodeemos nada.” ¿Les suena?

    Pausar un pipeline es poner enabled: false. Atención: borrar la entidad borra el publisher y deja el slot sin consumidor, y ya saben lo que pasa con un slot sin consumidor (sí, la alarma de la sección anterior).

    Una regla que nos ahorró más de un dolor de cabeza: el deploy del proyecto va después de habilitar la entidad, y se verifica que salió bien comprobando que exista el secret del publisher, no porque el pipeline dijo “terminó”.

    06 · Migración

    Shadow y corte: cambiar el motor con el auto en marcha

    No queríamos un big bang. En los tenants donde DMS seguía vivo, cada pipeline escribe en un schema paralelo, shadow\\\_, con los mismos nombres de tabla que DMS deja en public. Así conviven sin pisarse, comparamos paridad con tráfico real y los consumidores pueden construir sus índices sobre el shadow con calma.

    De punta a punta, el recorrido es este:

    shadow cutover

    Figura 5 · Shadow y corte: de DMS al data-replicator

    Cuando todo cierra, el corte es un rename atómico en una sola transacción:

    ALTER TABLE public.<tabla>     SET SCHEMA dms_retired_<fecha>;  -- lo de DMS, a retiro
    ALTER TABLE shadow_<p>.<tabla> SET SCHEMA public;               -- lo nuestro, a producción
    -- ...por cada tabla, y se actualiza el target_schema del pipeline
    COMMIT;

    SET SCHEMA arrastra índices, constraints y permisos: no hay full load, la ventana es de segundos y el rollback es simétrico (el mismo movimiento al revés). Después se cambia el schema destino en Port, se resincroniza y el siguiente deploy del proyecto elimina su DMS solo: si la entidad replication está habilitada en public, el dueño es el replicador y DMS deja de declararse.

    114

    pipelines en producción ya cortados a public

    23

    pipelines corriendo en dev

    Números a fines de octubre de 2026.

    07 · El mañana es un misterio

    Lo que viene

    Como dice el maestro Oogway, el mañana es un misterio, pero algunas cosas ya están en el radar:

    • Terminar los cortes y apagar el último DMS. Ese día va a haber festejo.
    • Hot reload de la configuración. Hoy cambiar la config reinicia el pod; es un reinicio limpio porque todos los pipelines retoman desde su origin, pero queremos montar la config como ConfigMap y recargarla en caliente.

    Conclusiones

    Cuatro reglas que nos quedaron tatuadas

    1. La fuente de verdad del ODS es el ODS, no las estadísticas de DMS ni lo que diga una consola.
    2. Todo conteo de una API de AWS que devuelve exactamente 100 es paginación. Parece un chiste, pero no lo es.
    3. Fallar está bien; fallar en silencio, no. Mil veces prefiero una tabla suspendida con su razón y una alerta P1 que un “running” que no corre.
    4. Shadow antes que big bang: convivir, comparar y recién después cortar.
    Reemplazar un servicio administrado de AWS por algo propio da vértigo, y está bien que lo dé. Pero cuando lo que pagas no te da garantías y encima no te avisa cuando se rompe, construir con garantías claras (exactly-once, fallas acotadas y visibles, un corte reversible) es lo más parecido a hacer las cosas bien y dormir tranquilos.

    Este fue el primer capítulo de la trilogía. En las próximas publicaciones, Cris y Jere van a contar lo suyo. ¡Hasta la próxima!

    Fuentes y lecturas recomendadas

    Próximo paso

    ¿Quieres conocer la infraestructura detrás de Akua?

    Habla con nuestro equipo y descubre cómo construimos una plataforma de pagos confiable, observable y lista para escalar.

    Habla con nuestro equipo
    Plataforma interna (IDP)Ingeniería de datosPostgreSQLAWSRustObservabilidadArquitecturaTrilogía de la Plataforma

    ¿Listo para transformar
    tus pagos?

    Habla con nuestro equipo y descubre cómo Akua puede acelerar tu operación.