Soluciones a problemas prácticos tras el despliegue en infraestructura PaaS Serverless
TuBrief 편집팀
2026년 7월 24일
0
Internet Technology원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Las plataformas PaaS todo en uno que permiten desplegar el frontend y el backend con solo unos pocos clics parecen perfectas al principio. El problema surge cuando los usuarios reales empiezan a entrar. Un código que ejecutaba perfectamente en local comienza a lanzar timeouts al subir a producción, o el intento de cambiar un solo esquema de la base de datos deja todo el servicio fuera de servicio. Además, enfrentarse a una situación de bloqueo por parte del proveedor (vendor lock-in) sin saber qué hacer produce un verdadero dolor de cabeza.
A continuación, se resumen varios patrones de infraestructura que permiten disfrutar de la comodidad de estas plataformas mientras se eliminan limpiamente los obstáculos que conllevan.
El proceso local de Node.js permanece encendido continuamente, pero el entorno serverless solo se activa brevemente cuando llega una petición y luego desaparece. Si durante el inicio en frío (cold start) se gestionan de forma deficiente las variables globales o la conexión singleton a la base de datos, el pool de conexiones se agotará en poco tiempo.
Tampoco es viable estar desplegando constantemente en un servidor de staging para verificarlo. Es mucho más rápido simular (mockear) la propia capa de red en local y ejecutar los handlers.
`typescript
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/v1/user/profile', ({ request }) => {
const authHeader = request.headers.get('Authorization');
if (!authHeader) {
return new HttpResponse(null, { status: 401, statusText: 'Unauthorized' });
}
return HttpResponse.json({
id: 'usr_102938',
email: 'dev@example.com',
role: 'ADMIN',
createdAt: new Date().toISOString()
});
})
];
// src/mocks/node.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
`
El proceso para aplicar este patrón es sencillo:
setupServer al ejecutar el emulador..env.local con respecto al endpoint del pooler de transacciones de producción.Sin necesidad de pulsar el botón de despliegue, se puede verificar en local el comportamiento de las interfaces del backend exactamente al 100%. Es una estructura que elimina el tiempo perdido en pruebas remotas inestables y proporciona retroalimentación inmediata nada más escribir el código.
Al utilizar PostgreSQL serverless como Supabase o Neon, ejecutar simplemente una sentencia ALTER TABLE resulta peligroso. Se aplica un bloqueo ACCESS EXCLUSIVE sobre la tabla de destino, lo que hace que las consultas posteriores queden en cola de espera y termine derivando en un fallo por timeout.
En primer lugar, se debe establecer un límite de timeout para la sesión de migración.
`sql
SET lock_timeout = '2000ms';
SET statement_timeout = '5000ms';
ALTER TABLE users ADD COLUMN bio VARCHAR(255);
`
Para modificar de forma segura la estructura de una base de datos en producción, se debe utilizar un enfoque en el que se expandan simultáneamente el código y la base de datos para luego retirar lo antiguo.
`typescript
import { db } from './db';
interface UpdateUserProfileInput {
userId: string;
fullName: string;
}
export async function updateUserProfile({ userId, fullName }: UpdateUserProfileInput) {
await db.transaction(async (tx) => {
await tx.user.update({
where: { id: userId },
data: {
full_name: fullName,
name: fullName
}
});
});
}
`
Son tres pasos para un trabajo seguro:
lock_timeout como mecanismo de seguridad para abortar la operación de inmediato en caso de contención de bloqueos.Siguiendo este procedimiento, se puede cambiar el esquema sin perder ni una sola petición de los usuarios.
Si solo se dejan declaraciones console.log en un entorno serverless, los registros de múltiples instancias se mezclarán desordenadamente. Para rastrear el trayecto desde que entra una petición hasta que sale, es imprescindible contar con un Request ID único y un formato JSON estructurado.
`typescript
import { Hono } from 'hono';
import { requestId } from 'hono/request-id';
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label.toUpperCase() })
},
base: { env: process.env.NODE_ENV }
});
const app = new Hono();
app.use('', requestId());
app.use('', async (c, next) => {
const reqId = c.var.requestId;
const startTime = performance.now();
c.set('logger', logger.child({ reqId }));
await next();
const durationMs = Math.round(performance.now() - startTime);
logger.info({
reqId,
method: c.req.method,
path: c.req.path,
status: c.res.status,
durationMs
}, 'Request processing finished');
});
`
Al utilizar herramientas de monitorización externas, esta configuración evita sorpresas por sobrecostes al superar los límites:
Server-Timing en las cabeceras de respuesta para enviar los puntos de cuello de botella al sistema de monitorización.`typescript
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
beforeSend(event, hint) {
const error = hint.originalException;
if (error && error instanceof Error && error.message.includes('ECONNRESET')) {
return null;
}
return event;
}
});
`
Filtrando únicamente los registros irrelevantes 200 OK y las excepciones de tipo ruido, es posible controlar el sistema adecuadamente dentro de los límites de la capa gratuita (free tier) de la plataforma de monitorización.
Si se acopla directamente el SDK exclusivo de una PaaS específica a la lógica de negocio, habrá que reescribir todo el código al migrar a otra plataforma en el futuro. Esta es la razón por la que se utiliza el patrón Adapter detallado por Martin Fowler, separando el punto de contacto entre la lógica central de la aplicación y los SDK de la nube externa.
`typescript
export interface IStorageService {
uploadFile(path: string, fileBuffer: Buffer, mimeType: string): Promise<{ url: string }>;
deleteFile(path: string): Promise;
}
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
export class S3StorageAdapter implements IStorageService {
private s3: S3Client;
private bucket: string;
constructor(region: string, bucket: string) {
this.s3 = new S3Client({ region });
this.bucket = bucket;
}
async uploadFile(path: string, fileBuffer: Buffer, mimeType: string): Promise<{ url: string }> {
await this.s3.send(new PutObjectCommand({
Bucket: this.bucket,
Key: path,
Body: fileBuffer,
ContentType: mimeType
}));
return { url: https://${this.bucket}.s3.amazonaws.com/${path} };
}
async deleteFile(path: string): Promise {
await this.s3.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: path }));
}
}
`
También se automatizan los backups de datos para poder salir en cualquier momento.
`bash
#!/usrbin/env bash
set -euo pipefail
TIMESTAMP=(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/tmp/db_backup_{TIMESTAMP}"
S3_BUCKET="s3://my-app-exit-backups/pg_dumps"
export PGPASSWORD="${DB_PASSWORD}"
mkdir -p "${BACKUP_DIR}"
pg_dump -h "{DB_PORT}" -U "{DB_NAME}"
-Fc --exclude-table-data='logs_*' > "${BACKUP_DIR}/full_schema_data.dump"
tar -czvf "{BACKUP_DIR}" .
aws s3 cp "{S3_BUCKET}/{R2_ENDPOINT_URL}"
rm -rf "{BACKUP_DIR}.tar.gz"
`
pg_dump para extraer el esquema y los datos.crontab para transferir los archivos de backup a Cloudflare R2 o a un bucket S3 externo.Al mitigar los riesgos de quedar vinculado a una plataforma específica, resulta mucho más fácil reaccionar ante incrementos en el coste de la infraestructura o fallos en el servicio de la plataforma.