Nos llegó hace unos meses un proyecto que resume bien el problema: un SaaS B2B de gestión documental había migrado su API completa a funciones distribuidas en 18 regiones, celebrando en el changelog una reducción del TTFB de 180 ms a 31 ms. Dos semanas después, el p95 de sus endpoints de listado había pasado de 340 ms a 1.180 ms. La factura de cómputo bajó un 12%. La de transferencia de datos subió un 340%, el ejemplo perfecto del coste oculto de las funciones edge con base de datos.
¿Qué había fallado? Nada, técnicamente. Todo funcionaba exactamente como estaba diseñado. Lo que nadie había medido era el trayecto entre el código y los datos.
Aquí no vamos a decirte que el edge es humo. Lo usamos, nos gusta, y en ciertos escenarios es la única respuesta razonable. Lo que vamos a desmontar es la asunción de que acercar el cómputo al usuario mejora la latencia cuando ese cómputo depende de una base de datos que sigue viviendo en us-east-1.
La promesa de los 20 milisegundos y por qué casi nunca se cumple
El coste oculto de una función distribuida con persistencia centralizada es la suma de tres partidas: la latencia acumulada de cada viaje de ida y vuelta hasta la instancia de datos, el tiempo de espera que el proveedor factura mientras la función aguarda respuesta, y el tráfico saliente entre regiones.
Los benchmarks de los proveedores son honestos. Ejecutan una función que devuelve un JSON estático, o que consulta un KV store replicado en el mismo nodo, y miden 8-20 ms desde el navegador. Son cifras reales.
El problema es que tu aplicación no devuelve JSON estático. Consulta tablas relacionales con joins, valida permisos contra una tabla de roles, escribe un registro de auditoría y luego devuelve la respuesta.
Lo que ocurre es que el marketing mide el arranque del runtime y tú necesitas medir el ciclo completo. En nuestras pruebas sobre 6 proyectos distintos durante 2024, el tiempo de arranque en frío de la capa perimetral osciló entre 4 y 11 ms. Excelente. Pero el tiempo total hasta el primer byte útil se movió entre 210 y 890 ms, y en el 100% de los casos más del 70% de ese tiempo era espera contra el almacén de datos.
¿Qué son exactamente las funciones edge?
Son fragmentos de código que se ejecutan en los nodos de una red de distribución repartida por decenas de ubicaciones, en lugar de en un servidor de una única región. El proveedor enruta cada petición al nodo más próximo al usuario, y el runtime arranca en pocos milisegundos porque no levanta un contenedor completo.
La consecuencia menos obvia es que ese código es efímero y apátrida: no conserva conexiones abiertas, no comparte memoria con sus vecinos y no tiene ningún dato cerca salvo el que el proveedor haya replicado deliberadamente.
¿Realmente acercas el cómputo al usuario o alejas los datos del cómputo?
Esta es la pregunta que reordena todo el análisis. Y la respuesta incómoda es que, en una arquitectura con persistencia centralizada, mover la ejecución al perímetro no acerca nada: solo cambia quién paga la distancia.
Antes, el usuario de Sídney pagaba 280 ms de ida y vuelta hasta tu servidor de Virginia, y luego tu servidor hablaba con la base de datos en el mismo VPC con 0,4 ms de latencia por consulta. Ahora el usuario paga 15 ms hasta el nodo de Sídney, y ese nodo paga 240 ms por cada consulta que necesite hacer.
Si necesita una consulta, has ganado. Si necesita cinco, has perdido de forma notable.
El viaje de ida y vuelta que nadie dibuja en el diagrama
Los diagramas de arquitectura que nos enseñan en las primeras reuniones tienen una flecha entre el usuario y el nodo periférico, etiquetada con algo como «~20 ms». Y luego una flecha entre ese nodo y la persistencia, sin etiqueta.
Esa flecha sin etiqueta es donde vive el problema.
Nosotros pedimos siempre lo mismo: pon el número. Un RTT entre Frankfurt y Virginia son unos 85-95 ms de fibra en condiciones buenas. Entre São Paulo y Virginia, 115-130 ms. Entre Singapur y Virginia, 220-250 ms. Y eso es solo la capa de red: añade el handshake TLS si la conexión no está reutilizada, que son otros dos viajes completos.
Cuando una petición se convierte en siete consultas secuenciales
Aquí está el quid del asunto. Un endpoint ORM típico que parece inocente:
- Validar sesión contra tabla de tokens
- Cargar usuario
- Cargar organización del usuario
- Comprobar permisos de rol
- Consultar listado principal
- Cargar relaciones (el clásico N+1 disfrazado)
- Insertar evento de auditoría
Siete viajes. Secuenciales, porque cada uno depende del anterior. En un despliegue monorregión con la instancia en el mismo VPC: 7 × 0,5 ms = 3,5 ms. Invisible. Desde Singapur contra Virginia: 7 × 235 ms = 1.645 ms.
Confesión: la primera vez que nos topamos con esto, en 2022, tardamos casi tres días en diagnosticarlo porque asumíamos que el cuello de botella era la query lenta. Revisamos planes de ejecución, añadimos índices, reescribimos un CTE. La query tardaba 1,8 ms. El resto era pura geografía. Cambiamos el enfoque a medir latencia de red por consulta y el problema apareció en veinte minutos.
Lo que aprendimos: en el perímetro, el número de round-trips importa diez veces más que el tiempo de ejecución de cada consulta. Es una inversión total de las prioridades de optimización que arrastramos del mundo monolítico.

El error de creer que el pool de conexiones es un detalle de configuración
Todos dábamos por sentado que el pooling era un parámetro que se ajusta al final, cuando ya todo funciona. Hasta que vimos un Postgres de 4 vCPU tumbarse con 900 peticiones por minuto, una carga que antes absorbía sin dificultad.
La razón es estructural, no de tuning.
¿Se puede conectar una función edge a Postgres?
Sí, pero casi nunca de forma directa. El runtime perimetral no mantiene conexiones persistentes entre invocaciones, así que abrir un socket TCP contra el motor relacional en cada petición agota el límite de conexiones en cuanto hay tráfico simultáneo en varias ubicaciones.
La vía viable pasa por un intermediario: un pooler en modo transacción, un proxy que exponga las consultas sobre HTTP, o un driver que hable con el motor a través de WebSocket. Las tres opciones funcionan y las tres cobran su peaje en milisegundos.
Por qué Postgres colapsa antes que tu función
El motor relacional lanza un proceso del sistema operativo por cada conexión. No un hilo ligero: un proceso, con su memoria reservada para work_mem, sus buffers, su overhead. Cada conexión consume del orden de 5-10 MB en reposo, y bastante más si ejecuta ordenaciones o agregaciones.
Un servidor tradicional mantiene un pool de, digamos, 20 conexiones persistentes y multiplexa miles de peticiones sobre ellas. Ese es todo el truco del modelo clásico, y funciona desde hace treinta años.
El runtime distribuido rompe ese contrato. Cada aislado es efímero, vive en un nodo distinto y no puede compartir estado de conexión con sus vecinos. Si tienes tráfico en 30 ubicaciones simultáneamente, no tienes un pool: tienes 30 pools independientes que no se hablan. Nos ha pasado ver picos de 480 conexiones concurrentes en una instancia configurada con max_connections a 100. El resultado no es degradación: es rechazo directo de conexiones y errores 500 en cascada.
Pooling, proxies HTTP y el peaje que añaden
La solución del ecosistema es meter un intermediario: PgBouncer, RDS Proxy, Supavisor, o un proxy HTTP que traduzca consultas sobre una API REST. Funcionan. Resuelven el agotamiento de conexiones de verdad.
Pero no son gratis.
Un pooler en modo transacción añade entre 1 y 4 ms por consulta en el mismo datacenter. Aceptable. Un proxy HTTP que serializa la consulta a JSON, la manda por HTTPS y deserializa la respuesta añade bastante más: medimos entre 18 y 45 ms de overhead por consulta en nuestras pruebas de 2024, y eso sin contar el TLS handshake cuando la conexión es nueva. Multiplica por siete consultas secuenciales y acabas de añadir entre 126 y 315 ms a una petición que ya pagaba la latencia geográfica.
Y hay un efecto secundario que casi nadie menciona: el modo transacción del pooler rompe las sentencias preparadas, los cursores y las transacciones que abarcan varias peticiones. Si tu ORM depende de prepared statements (Prisma lo hacía por defecto durante mucho tiempo), te vas a encontrar errores raros en producción que no aparecen en local. En un caso que auditamos el año pasado, esto provocaba fallos intermitentes del 0,3% de las peticiones, suficiente para generar tickets de soporte pero demasiado poco para aparecer en las alertas.
Lo que nadie dice sobre la factura: invocaciones, egress y tiempo de espera facturado
Vamos con el dinero, que es donde el edge sorprende de verdad.
Hay tres partidas que la gente no anticipa. La primera: muchos proveedores facturan duración de ejecución incluyendo el tiempo en que tu función está esperando una respuesta de red. No estás quemando CPU, estás esperando. Pero el reloj corre. Si tu función espera 1.600 ms a la persistencia, pagas 1.600 ms de GB-segundo.
La segunda: transferencia de datos saliente. Mover datos entre la región de tu instancia y los nodos perimetrales es tráfico entre proveedores en la mayoría de configuraciones, y eso se cobra. Aquel cliente del que hablábamos al principio pasó de 40 € a 176 € mensuales solo en egress, porque cada consulta viajaba en ambas direcciones y devolvía payloads de listado sin paginar.
La tercera es más sutil: el número de invocaciones se multiplica. Middleware, rewrites, comprobaciones de autenticación en cada navegación de la SPA… vimos un frontend que generaba 4,2 invocaciones de función por cada página vista real. Cuando el coste por millón de invocaciones es bajo, nadie mira. Cuando escalas a 8 millones de páginas vistas mensuales, 34 millones de invocaciones sí se notan en la línea de abajo.
Esta escalada de partidas variables es la que lleva a muchos equipos de producto a valorar la opción de salir del pago por uso cuando la carga se vuelve predecible y sostenida, porque el modelo serverless premia la variabilidad y castiga el tráfico constante. La decisión no es ideológica, es una cuenta de servilleta.
Réplicas de lectura globales: la solución que introduce dos problemas nuevos
La respuesta obvia: si el problema es la distancia a los datos, replica los datos. Réplicas de lectura en cinco continentes, cada nodo consulta la suya, y la latencia vuelve a bajar a 5 ms.
Funciona. Y abre dos frentes.
Lecturas obsoletas en flujos de escritura y lectura inmediata
El patrón que rompe todo: el usuario pulsa «Guardar», la escritura va al primario en Virginia, el frontend recarga el listado, la lectura va a la réplica de Sídney, y la réplica todavía no tiene el dato. El usuario ve su cambio desaparecer.
El lag de replicación típico que hemos medido va de 40 ms en condiciones ideales a 900 ms bajo carga de escritura intensa. Y en un incidente concreto, durante un reindexado masivo, llegó a 14 segundos.
Los parches existen: sticky reads al primario durante N segundos tras una escritura, tokens de consistencia, leer del primario en cualquier petición autenticada. Todos funcionan. Todos añaden complejidad al código de acceso a datos, y todos fallan de formas no evidentes cuando alguien nuevo en el equipo escribe un endpoint sin conocer la convención interna del proyecto. Por eso conviene revisar el repositorio entero con nuestra auditoría de código, que localiza cada patrón de lectura posterior a escritura y lo contrasta con la convención declarada.
El coste de replicar datos que casi nadie consulta
Segunda cuestión, puramente económica. Una réplica no es un caché: es una copia completa. Si tu volumen es de 80 GB, cada región mantiene 80 GB, con su almacenamiento, su cómputo mínimo y su tráfico de replicación continuo.
Revisamos la distribución geográfica de tráfico de un cliente que había desplegado réplicas en seis regiones. El 91% de las consultas venían de Europa. Sudamérica generaba el 1,4%. Esa réplica costaba 94 € al mes para servir a 340 usuarios. Salía más barato aceptar 180 ms de latencia extra para ese 1,4% y eliminarla.

Cómo medir tu coste real antes de migrar: instrumentación mínima
Nuestra recomendación es brutalmente simple: no migres nada hasta que tengas cuatro números. Y se consiguen en una tarde.
Número uno: round-trips por endpoint. Instrumenta el driver o el ORM para contar consultas por petición. Si usas Prisma, el evento de query lo da hecho. Si usas un driver directo, envuelve la función de ejecución con un contador en el contexto de la petición. Nosotros consideramos alarma cualquier endpoint por encima de 4 consultas secuenciales.
Número dos: latencia de red pura por consulta. Resta el tiempo de ejecución reportado por el motor (EXPLAIN ANALYZE o pg_stat_statements) del tiempo total medido en tu código. La diferencia es red y pooler. Ese es el número que se multiplica al alejarte de la región.
Número tres: distribución geográfica real del tráfico. Por país, en percentiles, de los últimos 90 días. No por dónde te gustaría crecer: por dónde entran las peticiones hoy.
Número cuatro: ratio de lecturas sobre escrituras, y cuántas de esas lecturas ocurren en los 2 segundos posteriores a una escritura del mismo usuario. Este último es el que decide si las réplicas te van a servir o te van a generar tickets.
Con esos cuatro números puedes estimar la latencia post-migración con un error del 15-20% haciendo una multiplicación. Sin ellos, estás apostando.
Una cosa más, y esta la aprendimos a base de equivocarnos: mide siempre el p95 y el p99, nunca la media. La media esconde el caso del usuario con 200 registros y 40 relaciones cargadas, que es justo el usuario que paga más y el que abre el ticket.
Cuándo la ejecución distribuida sí gana y cuándo la región única sigue siendo la respuesta correcta
Gana con claridad en tres escenarios. Uno: lógica que no toca persistencia, como redirecciones, reescrituras, A/B testing, geolocalización, validación de tokens JWT firmados o cabeceras de seguridad. Cero consultas, latencia real de 10 ms, y aquí el edge es simplemente la mejor herramienta que existe.
Dos: cuando tu almacén de datos es él mismo distribuido por diseño, con consistencia eventual asumida y explícita. Bases replicadas en el propio perímetro, KV stores, o modelos de datos donde una lectura obsoleta de 300 ms no rompe nada.
Tres: cuando el payload es grande y cacheable, y puedes servir desde caché el 85-95% de las peticiones. La consulta al origen ocurre una vez cada diez minutos y a nadie le importa que tarde 250 ms.
Esta es la comparación que ponemos sobre la mesa en cada reunión de decisión:
| Criterio | Ejecución perimetral | Región única |
|---|---|---|
| Latencia de cómputo | 4-20 ms (arranque y ejecución cerca del usuario) | 40-280 ms según distancia del usuario a la región |
| Latencia de datos | 85-250 ms por consulta contra instancia remota | 0,4-2 ms por consulta dentro del mismo VPC |
| Coste por millón de invocaciones | Bajo por unidad, pero se multiplica por middleware, rewrites y espera facturada | Coste fijo de instancia, predecible e independiente del volumen de peticiones |
| Complejidad operativa | Alta: pooler o proxy obligatorio, réplicas, sticky reads, egress entre regiones | Baja: pool clásico, una topología, un punto de observabilidad |
¿Y cuándo sigue ganando la región única? Cuando tu aplicación es transaccional, relacional y con lógica de negocio que exige consistencia fuerte. Un ERP, un sistema de facturación, una plataforma de reservas con inventario limitado. Pon el cómputo pegado al almacén, optimiza los round-trips, mete un CDN delante para los estáticos y acepta 120 ms de TTFB. Tu p99 será mejor, tu código más simple y tu factura predecible.
La pregunta correcta no es «¿debería mover esto al edge?». Es «¿cuántas veces necesita este código hablar con los datos, y qué pasa si cada conversación cuesta 200 ms?». Contesta eso con números antes de tocar el pipeline de despliegue, y el coste oculto deja de estar oculto.
Nosotros hemos migrado proyectos al perímetro y hemos revertido migraciones al perímetro. Las dos decisiones fueron correctas en su contexto. La única que nunca es correcta es la que se toma sin medir.
