Preguntas de entrevista de AWS y Cloud con respuestas (40+)
Si tenés una entrevista técnica que involucra AWS o cloud en general, este artículo es lo único que necesitás leer. No es un resumen de documentación — es una guía de preparación real, con las preguntas que de verdad hacen los entrevistadores, las respuestas que quieren escuchar, y código que podés estudiar y reproducir.
Cubrimos desde los conceptos fundamentales hasta arquitectura avanzada, seguridad, costos, y los casos de uso que más aparecen en entrevistas para roles de backend, DevOps, SRE, cloud engineer, y soluciones arquitecto.
Cómo usar esta guía
- Leé la pregunta en voz alta, intentá responderla sin mirar, y después comparás.
- Las respuestas tienen una parte conceptual (lo que decís) y código o ejemplo (lo que demostrás).
- Al final de cada sección hay un bloque de "lo que el entrevistador realmente busca".
SECCIÓN 1 — Fundamentos de AWS (preguntas 1–10)
1. ¿Qué es una región y qué es una Availability Zone (AZ) en AWS?
Respuesta:
Una región es una ubicación geográfica independiente (por ejemplo, us-east-1 en Virginia, sa-east-1 en São Paulo). Dentro de cada región hay mínimo dos, normalmente tres o más Availability Zones (AZs): centros de datos físicamente separados, con energía, red y refrigeración independientes, conectados por fibra de baja latencia.
La clave es que las AZs están separadas lo suficiente para no fallar juntas ante un desastre físico, pero lo suficientemente cerca para sincronización rápida (~1ms de latencia entre AZs en la misma región).
Lo que buscás lograr con esto:
- Alta disponibilidad: distribuís tus instancias entre AZs.
- Resiliencia: si una AZ cae, las otras siguen.
- Sin salir de la región: los datos no cruzan fronteras legales.
Ejemplo de referencia en Terraform:
resource "aws_instance" "web" {
count = 2
ami = "ami-0c02fb55956c7d316"
instance_type = "t3.micro"
availability_zone = element(["us-east-1a", "us-east-1b"], count.index)
tags = {
Name = "web-${count.index}"
}
}Lo que busca el entrevistador: que entiendas la diferencia entre redundancia regional y redundancia de zona, y que sepas cuándo necesitás multi-AZ vs multi-region.
2. ¿Cuándo usarías EC2 vs Lambda vs ECS vs Fargate?
Respuesta:
Estos cuatro servicios resuelven el mismo problema (ejecutar código) a distintos niveles de abstracción:
| Servicio | Cuándo usarlo | Lo que gestionás vos |
|---|---|---|
| EC2 | Control total del OS, cargas constantes, licencias por nodo | Sistema operativo, patches, escalado |
| Lambda | Eventos discretos, carga variable, funciones cortas (<15 min) | Solo el código |
| ECS (EC2 launch type) | Contenedores, pero querés gestionar el cluster | Instancias EC2 del cluster |
| Fargate | Contenedores sin gestionar servidores | Solo la definición del contenedor |
La lógica de decisión:
¿Corrés contenedores? → ECS/Fargate
¿Querés gestionar infra del cluster? → ECS EC2 launch type
¿No querés gestionar nada? → Fargate
¿No corrés contenedores?
¿Función corta, event-driven, sin estado? → Lambda
¿Proceso largo, stateful, o necesitás el OS? → EC2Ejemplo Lambda en Python (trigger S3):
import json
import boto3
def handler(event, context):
s3 = boto3.client('s3')
for record in event['Records']:
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key']
response = s3.get_object(Bucket=bucket, Key=key)
content = response['Body'].read().decode('utf-8')
# Procesás el archivo
print(f"Procesando {key} de {bucket}: {len(content)} chars")
return {'statusCode': 200}Lo que busca el entrevistador: que no respondas "Lambda siempre porque serverless" ni "EC2 siempre porque control". Quiere escuchar los trade-offs.
3. Explicá qué es S3 y los diferentes storage classes
Respuesta:
S3 (Simple Storage Service) es el servicio de object storage de AWS. No es un filesystem — es un key-value store donde el "key" es la ruta del objeto y el "value" es el blob binario.
Características fundamentales:
- Durabilidad: 99.999999999% (11 nueves) — replica automáticamente en mínimo 3 AZs.
- Escalabilidad: sin límite de almacenamiento ni de objetos.
- Consistencia: desde noviembre 2020, strong consistency en todos los objetos (antes era eventual para puts nuevos).
Storage classes por patrón de acceso:
| Clase | Cuándo usarla | Costo relativo |
|---|---|---|
| Standard | Acceso frecuente | Alto |
| Standard-IA | Acceso infrecuente, recuperación rápida | Medio (cobro por retrieval) |
| One Zone-IA | Infrecuente, podés perder una AZ | Bajo |
| Glacier Instant Retrieval | Archivos accedidos 1x/trimestre, ms de latencia | Muy bajo |
| Glacier Flexible Retrieval | Archivos, recuperación en minutos-horas | Muy bajo |
| Glacier Deep Archive | Archivos rara vez accedidos, recuperación en horas | Mínimo |
| Intelligent-Tiering | Patrón impredecible, AWS mueve los objetos | Variable + fee de monitoreo |
Ejemplo de lifecycle policy en JSON:
{
"Rules": [
{
"ID": "mover-logs-a-ia",
"Status": "Enabled",
"Filter": { "Prefix": "logs/" },
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 365
}
}
]
}4. ¿Qué es IAM? Explicá usuarios, grupos, roles y políticas
Respuesta:
IAM (Identity and Access Management) es el sistema de control de acceso de AWS. El modelo es: identidad + política = lo que podés hacer.
- Usuario: identidad permanente ligada a una persona o aplicación. Tiene credenciales (usuario/contraseña o access keys).
- Grupo: colección de usuarios. Asignás políticas al grupo, no a cada usuario.
- Rol: identidad temporal sin credenciales permanentes. Lo asumís (un servicio, una instancia, un usuario de otra cuenta) y recibís credenciales temporales vía STS.
- Política: documento JSON que define permisos (Allow/Deny sobre Actions en Resources).
Principio de menor privilegio: siempre el mínimo permiso necesario para la tarea.
Ejemplo de política para que Lambda lea de S3 y escriba en DynamoDB:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LeerS3",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::mi-bucket-prod",
"arn:aws:s3:::mi-bucket-prod/*"
]
},
{
"Sid": "EscribirDynamo",
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:UpdateItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789:table/mi-tabla"
}
]
}Trust policy del rol (quién puede asumir este rol):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}Lo que busca el entrevistador: que no uses usuarios con access keys hardcodeados en producción. Quiere escuchar "roles" para servicios AWS y "AssumeRole" para acceso cross-account.
5. ¿Qué es una VPC y para qué la usás?
Respuesta:
Una VPC (Virtual Private Cloud) es tu red virtual privada dentro de AWS — aislada logicamente del resto de cuentas. Dentro de una VPC definís:
- CIDR block: el rango de IPs de la red (ej.
10.0.0.0/16). - Subnets: subdivisiones dentro de la VPC, asociadas a una AZ. Pueden ser públicas (con route a Internet Gateway) o privadas (sin route directo a internet).
- Internet Gateway (IGW): permite tráfico bidireccional entre la subnet pública e internet.
- NAT Gateway: permite que recursos en subnets privadas inicien conexiones a internet, pero no recibir conexiones entrantes.
- Route Tables: tablas de enrutamiento que determinan a dónde va el tráfico.
- Security Groups: firewall stateful a nivel de recurso (instancia, RDS, etc.).
- NACLs (Network ACLs): firewall stateless a nivel de subnet.
Arquitectura típica de 3 capas:
Internet
|
[Internet Gateway]
|
[Subnet Pública - 10.0.1.0/24] (AZ-a) / [Subnet Pública - 10.0.2.0/24] (AZ-b)
| → Load Balancer, NAT Gateway
[Subnet Privada App - 10.0.10.0/24] (AZ-a) / [10.0.11.0/24] (AZ-b)
| → EC2, ECS, Lambda
[Subnet Privada DB - 10.0.20.0/24] (AZ-a) / [10.0.21.0/24] (AZ-b)
| → RDS, ElastiCacheSecurity Group vs NACL:
| | Security Group | NACL |
|---|---|---|
| Nivel | Recurso | Subnet |
| Stateful | Sí (respuesta automática) | No (debés abrir ambas direcciones) |
| Reglas | Solo Allow | Allow y Deny |
| Evaluación | Todas las reglas | Por número (primero que matchea gana) |
6. ¿Cómo funciona Auto Scaling en AWS?
Respuesta:
Auto Scaling ajusta automáticamente la cantidad de instancias (o tareas ECS, o réplicas de Aurora, etc.) según la demanda. Los tres componentes clave:
- 1Launch Template/Configuration: define QUÉ instancias lanzar (AMI, tipo, user data, security groups, rol IAM).
- 2Auto Scaling Group (ASG): define CUÁNTAS instancias (min, max, desired) y en QUÉ subnets.
- 3Scaling Policies: definen CUÁNDO escalar.
Tipos de scaling policies:
- Target Tracking: "Manteneme el CPU en 60%". AWS calcula cuántas instancias necesita.
- Step Scaling: si CPU > 70%, agregá 2 instancias; si CPU > 90%, agregá 4.
- Scheduled Scaling: "Todos los lunes a las 8am, llevá el desired a 10."
- Predictive Scaling: ML que predice la carga y escala antes de que llegue.
Ejemplo de Target Tracking con Terraform:
resource "aws_autoscaling_policy" "cpu_target" {
name = "cpu-target-tracking"
autoscaling_group_name = aws_autoscaling_group.web.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ASGAverageCPUUtilization"
}
target_value = 60.0
}
}Ciclo de vida de una instancia en un ASG:
Pending → Pending:Wait → Pending:Proceed → InService
InService → Terminating → Terminating:Wait → Terminating:Proceed → TerminatedLos lifecycle hooks te permiten ejecutar acciones en las transiciones Wait (ej. sacar la instancia del load balancer, drenar conexiones, backupear logs).
7. Diferencia entre ELB (ALB vs NLB vs CLB)
Respuesta:
| | ALB (Application) | NLB (Network) | CLB (Classic) |
|---|---|---|---|
| Capa OSI | 7 (HTTP/HTTPS) | 4 (TCP/UDP/TLS) | 4 y 7 |
| Routing | Path, host, headers, query string | IP y puerto | Round-robin básico |
| Performance | Alto throughput HTTP | Millones de req/seg, latencia ultra-baja | Legacy |
| WebSockets | Sí | Sí | No |
| Sticky sessions | Cookie-based | Source IP | Cookie |
| Cuándo usarlo | APIs REST, microservicios, path routing | Gaming, IoT, necesitás IP estática | No uses, es legacy |
ALB listener rule de ejemplo:
{
"Type": "forward",
"Conditions": [
{
"Field": "path-pattern",
"Values": ["/api/v2/*"]
}
],
"Actions": [
{
"Type": "forward",
"TargetGroupArn": "arn:aws:elasticloadbalancing:..."
}
]
}Health check básico de un target group (Terraform):
resource "aws_lb_target_group" "api" {
name = "api-tg"
port = 3000
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/health"
interval = 30
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 3
}
}8. ¿Qué es CloudFront y cuándo lo usarías?
Respuesta:
CloudFront es el CDN (Content Delivery Network) de AWS. Tiene ~400 puntos de presencia (edge locations) distribuidos globalmente que cachean y sirven contenido desde la ubicación más cercana al usuario.
Cuándo lo usás:
- Servir assets estáticos (JS, CSS, imágenes) de un bucket S3.
- Acelerar APIs reduciendo latencia para usuarios globales.
- Terminación SSL/TLS en el edge.
- Protección DDoS (integra con AWS Shield).
- Transformar respuestas con Lambda@Edge o CloudFront Functions.
Flujo de una request:
Usuario (Buenos Aires)
→ Edge Location (GRU - São Paulo)
→ [Cache HIT] → responde desde el edge (baja latencia)
→ [Cache MISS] → va al Origin (ej. S3 en us-east-1) → guarda en caché → respondeEjemplo de distribución para SPA en S3 (Terraform):
resource "aws_cloudfront_distribution" "spa" {
origin {
domain_name = aws_s3_bucket.app.bucket_regional_domain_name
origin_id = "S3-spa"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-spa"
viewer_protocol_policy = "redirect-to-https"
forwarded_values {
query_string = false
cookies { forward = "none" }
}
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
# SPA: cualquier path no encontrado en S3 → index.html
custom_error_response {
error_code = 403
response_code = 200
response_page_path = "/index.html"
}
restrictions {
geo_restriction { restriction_type = "none" }
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.cert.arn
ssl_support_method = "sni-only"
}
}9. ¿Cómo funciona Route 53 y qué es el failover routing?
Respuesta:
Route 53 es el servicio DNS de AWS. Además de DNS estándar, tiene políticas de routing inteligentes:
| Política | Cómo funciona |
|---|---|
| Simple | Un solo registro, responde siempre lo mismo |
| Weighted | Distribuye tráfico en % (ej. 90% producción, 10% canary) |
| Latency-based | Responde con la region que tenga menor latencia para ese usuario |
| Geolocation | Responde según la ubicación geográfica del usuario |
| Failover | Primary activo; si falla (health check), responde Secondary |
| Multivalue | Hasta 8 IPs saludables, como round-robin con health checks |
Failover con health check:
resource "aws_route53_health_check" "primary" {
fqdn = "api.mi-app.com"
port = 443
type = "HTTPS"
resource_path = "/health"
failure_threshold = 3
request_interval = 30
}
resource "aws_route53_record" "primary" {
zone_id = aws_route53_zone.main.zone_id
name = "api.mi-app.com"
type = "A"
set_identifier = "primary"
failover_routing_policy {
type = "PRIMARY"
}
health_check_id = aws_route53_health_check.primary.id
alias {
name = aws_lb.primary.dns_name
zone_id = aws_lb.primary.zone_id
evaluate_target_health = true
}
}Diferencia entre ALIAS y CNAME:
CNAME: registro DNS estándar, no podés usarlo en el apex de un dominio (ej.mi-app.comsin subdominio), cobra por queries.ALIAS: extensión de AWS, funciona en el apex, gratuito para targets de AWS (ELB, CloudFront, S3 website), resuelve al IP directamente.
10. Explicá el modelo de responsabilidad compartida de AWS
Respuesta:
AWS opera bajo un modelo donde la responsabilidad de seguridad se divide entre AWS y el cliente:
AWS es responsable de ("security OF the cloud"):
- Hardware físico (servidores, switches, datacenters)
- Infraestructura de la región/AZ
- Hypervisor y virtualización
- Software de servicios gestionados (RDS, Lambda, S3)
Vos sos responsable de ("security IN the cloud"):
- Sistema operativo de tus instancias EC2 (patches, acceso SSH)
- Configuración de security groups y NACLs
- Datos que guardás (encriptación en tránsito y en reposo)
- Gestión de IAM (usuarios, roles, políticas)
- Configuración de tus aplicaciones
- Datos del cliente y su privacidad
La línea varía según el servicio:
- EC2: vos gestionás el OS y arriba.
- RDS: AWS gestiona el motor y el OS; vos gestionás las bases de datos, usuarios, y datos.
- Lambda: AWS gestiona todo excepto tu código.
- S3: AWS gestiona la infraestructura; vos gestionás permisos de los buckets y encriptación de objetos.
Error típico de entrevista: pensar que "usé un servicio gestionado = estoy seguro". El entrevistador puede preguntarte: "¿Qué pasaría si un bucket S3 es público?" — la respuesta es que es responsabilidad tuya configurar los permisos correctamente.
SECCIÓN 2 — Bases de datos en AWS (preguntas 11–18)
11. RDS vs DynamoDB: ¿cuándo usás cada uno?
Respuesta:
RDS (Relational Database Service):
- Para datos con relaciones complejas, transacciones ACID, esquemas fijos.
- Soporta MySQL, PostgreSQL, MariaDB, Oracle, SQL Server, Aurora.
- Escalado vertical (cambiás el tipo de instancia) o con read replicas para reads.
- Multi-AZ: instancia standby sincrónica, failover automático en ~60 segundos.
DynamoDB:
- NoSQL key-value / document store completamente gestionado.
- Latencia de milisegundos a cualquier escala.
- Sin esquema (salvo partition key y sort key).
- Escalado horizontal automático con on-demand capacity.
- Ideal para: sesiones de usuario, carros de compra, leaderboards, IoT, datos de series temporales.
El criterio real:
¿Necesitás JOINs complejos o transacciones ACID entre múltiples entidades? → RDS/Aurora
¿Acceso por key conocida, alta escala, latencia baja? → DynamoDB
¿Datos analíticos/BI? → Redshift o Athena
¿Grafos? → Neptune
¿Caché? → ElastiCacheEjemplo DynamoDB con boto3 (Python):
import boto3
from decimal import Decimal
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('usuarios')
# PUT (crear o reemplazar)
table.put_item(
Item={
'user_id': 'usr_123', # partition key
'created_at': '2024-01-15', # sort key
'nombre': 'Ana García',
'plan': 'pro',
'score': Decimal('87.5')
}
)
# GET por key
response = table.get_item(
Key={
'user_id': 'usr_123',
'created_at': '2024-01-15'
}
)
usuario = response.get('Item')
# QUERY (todos los items de un user, ordenados por created_at)
from boto3.dynamodb.conditions import Key
response = table.query(
KeyConditionExpression=Key('user_id').eq('usr_123')
)
items = response['Items']12. ¿Qué es Aurora y en qué se diferencia de RDS estándar?
Respuesta:
Aurora es la base de datos relacional de AWS, compatible con MySQL y PostgreSQL, pero reescrita para la nube. Las diferencias principales con RDS estándar:
Arquitectura de storage:
- RDS: storage en EBS ligado a la instancia, la replica para Multi-AZ es de la instancia completa.
- Aurora: storage distribuido en 6 copias a través de 3 AZs, desacoplado de las instancias. Las instancias solo necesitan páginas modificadas, no copias completas.
Performance:
- Aurora MySQL: hasta 5x el throughput de MySQL en RDS.
- Aurora PostgreSQL: hasta 3x el throughput de PostgreSQL en RDS.
Read Replicas:
- RDS: hasta 5 read replicas, lag típico de segundos.
- Aurora: hasta 15 read replicas, lag típico de milisegundos (porque comparten el mismo storage cluster).
Aurora Serverless v2:
- Escala automáticamente en fracciones de ACU (Aurora Capacity Units) en milisegundos.
- Ideal para cargas impredecibles o desarrollo.
# Conexión a Aurora con IAM authentication (sin passwords hardcodeadas)
import boto3
import pymysql
def get_aurora_connection():
client = boto3.client('rds', region_name='us-east-1')
# Genera token temporal (válido 15 min)
token = client.generate_db_auth_token(
DBHostname='mi-cluster.cluster-xxxx.us-east-1.rds.amazonaws.com',
Port=3306,
DBUsername='mi_usuario'
)
conn = pymysql.connect(
host='mi-cluster.cluster-xxxx.us-east-1.rds.amazonaws.com',
user='mi_usuario',
password=token,
database='mi_db',
ssl={'ca': '/path/to/rds-ca-2019-root.pem'}
)
return conn13. ¿Cómo modelás datos en DynamoDB? Explicá partition key, sort key y los patrones de acceso
Respuesta:
En DynamoDB, el diseño del modelo EMPIEZA por los patrones de acceso, no por las entidades. Es al revés de SQL.
Keys:
- Partition key (PK): determina en qué partición física vive el item. Debe tener alta cardinalidad para distribuir bien la carga.
- Sort key (SK): ordena items dentro de la misma partición. Permite range queries.
- Global Secondary Index (GSI): un índice con una PK/SK diferente. Permite consultar por atributos distintos al PK/SK.
Técnica de single-table design:
PK SK Tipo Datos
----------------------------------------------------------
USER#usr_123 METADATA usuario {nombre, email, plan}
USER#usr_123 SESSION#2024-01-15 sesión {duracion, wpm}
USER#usr_123 SESSION#2024-01-20 sesión {duracion, wpm}
COMPANY#google JOB#backend-sr vacante {titulo, sueldo}
COMPANY#google JOB#ml-lead vacante {titulo, sueldo}Patrones de acceso que cubre:
- "Dame el usuario usr_123" →
GET USER#usr_123 METADATA - "Dame todas las sesiones de usr_123" →
QUERY PK=USER#usr_123 SK begins_with SESSION# - "Dame todas las vacantes de Google" →
QUERY PK=COMPANY#google SK begins_with JOB#
GSI para acceso inverso:
# GSI: PK=email, SK=USER#id → buscar usuario por email
response = table.query(
IndexName='email-index',
KeyConditionExpression=Key('email').eq('ana@example.com')
)14. ¿Qué es ElastiCache y cómo lo integrás con tu arquitectura?
Respuesta:
ElastiCache es el servicio de caché en memoria de AWS. Soporta dos motores:
- Redis: estructuras de datos complejas, persistencia, pub/sub, Lua scripting, clustering. El más usado.
- Memcached: simple key-value, multi-threading nativo, no clustering real.
Patrones de caché:
Cache-Aside (Lazy Loading):
import redis
import json
cache = redis.Redis(host='mi-cluster.xxx.cache.amazonaws.com', port=6379)
def get_user_profile(user_id: str) -> dict:
cache_key = f"user:{user_id}"
# 1. Intentar desde caché
cached = cache.get(cache_key)
if cached:
return json.loads(cached)
# 2. Cache miss → ir a la base de datos
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# 3. Guardar en caché con TTL de 1 hora
cache.setex(cache_key, 3600, json.dumps(user))
return userWrite-Through:
def update_user_profile(user_id: str, data: dict):
# Escribir en DB y caché al mismo tiempo
db.execute("UPDATE users SET ... WHERE id = %s", user_id)
cache.setex(f"user:{user_id}", 3600, json.dumps(data))Cuándo NO usar caché:
- Datos que cambian muy frecuentemente (la invalidación supera el beneficio).
- Datos críticos sin fuente de verdad en DB (caché es volátil).
- Consultas que no se repiten.
15. ¿Qué es Redshift y cuándo lo usarías?
Respuesta:
Redshift es el data warehouse de AWS. Está optimizado para queries analíticas sobre grandes volúmenes de datos históricos (OLAP), no para transacciones (OLTP).
Características clave:
- Columnar storage: guarda datos por columna, no por fila → compresión extrema, queries analíticas mucho más rápidas.
- MPP (Massively Parallel Processing): queries se ejecutan en paralelo en múltiples nodos.
- Redshift Spectrum: consultar directamente datos en S3 sin importarlos.
Arquitectura típica de data pipeline:
Fuentes (RDS, DynamoDB, logs)
→ Kinesis / S3
→ Glue (ETL)
→ Redshift
→ QuickSight / herramientas BIDiferencia con Athena:
- Athena: serverless SQL sobre S3 con Presto. Sin infraestructura, pagás por query. Ideal para queries ocasionales sobre datos en S3.
- Redshift: cluster dedicado, mejor para queries frecuentes y complejas sobre TBs de datos.
16. Explicá los tipos de backups en RDS y cómo funciona Point-in-Time Recovery
Respuesta:
RDS tiene dos mecanismos de backup:
Automated Backups:
- Full backup diario durante la ventana de mantenimiento.
- Transaction logs capturados cada 5 minutos.
- Retención: 0-35 días (0 = deshabilitado).
- Permiten Point-in-Time Recovery (PITR): restaurar a cualquier segundo dentro del período de retención.
Manual Snapshots:
- Iniciados por vos.
- No expiran automáticamente (los conservás hasta que los borrés).
- Se pueden compartir entre cuentas o copiar a otras regiones.
PITR en la práctica:
# AWS CLI: restore a un punto específico en el tiempo
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier mi-db-prod \
--target-db-instance-identifier mi-db-restored \
--restore-time 2024-01-15T14:30:00ZImportante: PITR crea una nueva instancia, no restaura sobre la existente. Siempre probá el restore antes de que lo necesités en producción.
17. ¿Qué es DynamoDB Streams y para qué lo usás?
Respuesta:
DynamoDB Streams captura cada cambio (INSERT, MODIFY, DELETE) en una tabla y lo hace disponible como un stream ordenado por shard. Cada record contiene el estado anterior y/o nuevo del item.
Casos de uso:
- Disparar una Lambda al modificarse un item (event-driven).
- Replicar datos a otras regiones.
- Mantener un ElastiCache sincronizado con DynamoDB.
- Auditoría de cambios.
- Recalcular agregaciones al cambiar datos.
Ejemplo: Lambda que reacciona a cambios en DynamoDB:
def handler(event, context):
for record in event['Records']:
if record['eventName'] == 'INSERT':
new_item = record['dynamodb']['NewImage']
user_id = new_item['user_id']['S']
# Enviar email de bienvenida
send_welcome_email(user_id)
elif record['eventName'] == 'MODIFY':
old = record['dynamodb']['OldImage']
new = record['dynamodb']['NewImage']
# Si el plan cambió, actualizar accesos
if old.get('plan', {}).get('S') != new.get('plan', {}).get('S'):
update_user_permissions(
user_id=new['user_id']['S'],
new_plan=new['plan']['S']
)Consideraciones:
- Los records en el stream duran 24 horas.
- Procesás con Lambda (trigger automático) o con Kinesis Data Streams for DynamoDB (mayor retención).
18. ¿Cuándo usarías Athena vs Redshift vs RDS?
Respuesta:
| Criterio | Athena | Redshift | RDS |
|---|---|---|---|
| Tipo de carga | Analítica ad-hoc | Analítica frecuente/compleja | Transaccional (OLTP) |
| Datos en | S3 (cualquier formato) | Storage propio (copiar desde S3) | EBS |
| Escalado | Serverless | Cluster (resize manual o Serverless) | Vertical + read replicas |
| Costo | Por query (por TB escaneado) | Por hora de cluster | Por hora de instancia |
| Latencia | Segundos-minutos | Segundos | Milisegundos |
| Esquema | Schema-on-read | Schema-on-write | Schema-on-write |
Regla práctica:
- Datos operacionales, aplicación en tiempo real → RDS/Aurora
- Logs, eventos, datos históricos en S3, consultas ocasionales → Athena
- Dashboard BI diario, queries analíticas repetidas sobre TBs → Redshift
SECCIÓN 3 — Arquitectura y escalabilidad (preguntas 19–27)
19. ¿Cómo diseñarías una arquitectura serverless para una API REST?
Respuesta:
Una API serverless típica en AWS se ve así:
Cliente
→ Route 53 (DNS)
→ CloudFront (CDN + WAF)
→ API Gateway (routing, auth, throttling)
→ Lambda (lógica de negocio)
→ DynamoDB / RDS (datos)
→ ElastiCache (caché)
→ SQS/SNS (mensajería async)
→ S3 (archivos)API Gateway + Lambda (ejemplo con SAM):
# template.yaml (SAM)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
ApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.handler
Runtime: nodejs20.x
MemorySize: 512
Timeout: 29
Environment:
Variables:
TABLE_NAME: !Ref UsersTable
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
GetUser:
Type: Api
Properties:
Path: /users/{id}
Method: GET
CreateUser:
Type: Api
Properties:
Path: /users
Method: POST
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: user_id
AttributeType: S
KeySchema:
- AttributeName: user_id
KeyType: HASHHandler de Lambda:
// src/app.js
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, GetCommand, PutCommand } = require('@aws-sdk/lib-dynamodb');
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
exports.handler = async (event) => {
const { httpMethod, pathParameters, body } = event;
try {
if (httpMethod === 'GET') {
const result = await docClient.send(new GetCommand({
TableName: process.env.TABLE_NAME,
Key: { user_id: pathParameters.id }
}));
if (!result.Item) {
return { statusCode: 404, body: JSON.stringify({ error: 'Not found' }) };
}
return { statusCode: 200, body: JSON.stringify(result.Item) };
}
if (httpMethod === 'POST') {
const user = JSON.parse(body);
user.user_id = crypto.randomUUID();
user.created_at = new Date().toISOString();
await docClient.send(new PutCommand({
TableName: process.env.TABLE_NAME,
Item: user
}));
return { statusCode: 201, body: JSON.stringify({ id: user.user_id }) };
}
} catch (error) {
console.error(error);
return { statusCode: 500, body: JSON.stringify({ error: 'Internal error' }) };
}
};20. ¿Cómo implementarías una arquitectura event-driven en AWS?
Respuesta:
Una arquitectura event-driven desacopla productores y consumidores. Los servicios AWS clave:
- SQS (Simple Queue Service): cola de mensajes, procesamiento asíncrono, garantía de entrega at-least-once.
- SNS (Simple Notification Service): pub/sub fan-out, notificaciones push a múltiples subscribers.
- EventBridge: bus de eventos totalmente gestionado, soporta reglas de filtrado, eventos de servicios AWS y de terceros.
- Kinesis Data Streams: streaming de datos en tiempo real, retención hasta 7 días.
Patrón fan-out (SNS → múltiples SQS):
Evento: "nuevo_usuario_registrado"
→ SNS topic
→ SQS "enviar-email" → Lambda email
→ SQS "crear-perfil" → Lambda perfil
→ SQS "analytics" → Lambda analyticsEjemplo con boto3:
import boto3
import json
sns = boto3.client('sns')
sqs = boto3.client('sqs')
# Publicar evento en SNS
def publicar_evento(tipo: str, payload: dict):
sns.publish(
TopicArn='arn:aws:sns:us-east-1:123456789:eventos-usuarios',
Message=json.dumps({
'type': tipo,
'payload': payload,
'timestamp': '2024-01-15T10:00:00Z'
}),
MessageAttributes={
'eventType': {
'DataType': 'String',
'StringValue': tipo
}
}
)
# Lambda consumidor de SQS
def handler(event, context):
for record in event['Records']:
body = json.loads(record['body'])
evento = json.loads(body['Message'])
if evento['type'] == 'nuevo_usuario_registrado':
user_id = evento['payload']['user_id']
email = evento['payload']['email']
enviar_email_bienvenida(user_id, email)SQS Dead Letter Queue (DLQ):
Configurás una DLQ para capturar mensajes que fallaron N veces. Fundamental para no perder mensajes y poder debuggear.
resource "aws_sqs_queue" "main" {
name = "procesar-pagos"
visibility_timeout_seconds = 30
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.dlq.arn
maxReceiveCount = 3 # 3 intentos fallidos → DLQ
})
}
resource "aws_sqs_queue" "dlq" {
name = "procesar-pagos-dlq"
message_retention_seconds = 1209600 # 14 días
}21. ¿Qué es Kinesis y cuándo lo preferís sobre SQS?
Respuesta:
Kinesis es un servicio de streaming de datos en tiempo real, similar a Apache Kafka pero gestionado.
Kinesis Data Streams vs SQS:
| | Kinesis Data Streams | SQS |
|---|---|---|
| Modelo | Streaming (múltiples consumidores leen el mismo stream) | Cola (cada mensaje es consumido por un solo consumidor) |
| Retención | 1-365 días | hasta 14 días |
| Orden | Garantizado dentro de un shard | Solo en FIFO queues |
| Replay | Sí (podés releer desde un punto en el tiempo) | No |
| Throughput | 1MB/s por shard (write), 2MB/s (read) | Virtualmente ilimitado |
| Latencia | ~200ms | Milisegundos |
Cuándo usás Kinesis:
- Múltiples consumidores necesitan el mismo stream (analytics + alertas + archivado = todos leen el mismo).
- Necesitás replay de eventos.
- Datos de IoT, clickstream, logs en tiempo real.
- Orden estricto importa.
Cuándo usás SQS:
- Un solo consumidor por mensaje (worker queue).
- Decoupling simple sin necesidad de replay.
- Fan-out no requerido.
22. ¿Cómo diseñarías para alta disponibilidad (HA) y disaster recovery (DR)?
Respuesta:
Son conceptos distintos:
- Alta disponibilidad (HA): tu sistema sigue funcionando ante la falla de un componente. Metas: 99.9% (8.7h downtime/año), 99.99% (52 min/año).
- Disaster recovery (DR): qué hacés cuando hay un fallo catastrófico (región caída, corrupción de datos, ransomware).
Métricas de DR:
- RPO (Recovery Point Objective): cuántos datos podés perder. "Puedo perder hasta 1 hora de transacciones."
- RTO (Recovery Time Objective): cuánto tiempo podés estar caído. "Debo estar operativo en 4 horas."
Estrategias de DR (orden de menor a mayor costo/complejidad):
| Estrategia | RPO | RTO | Costo |
|---|---|---|---|
| Backup & Restore | Horas | Horas | $ |
| Pilot Light | Minutos | Horas | $$ |
| Warm Standby | Segundos-minutos | Minutos | $$$ |
| Multi-Site Active/Active | ~0 | ~0 | $$$$ |
Pilot Light: tenés las partes críticas replicadas en la región DR pero apagadas o al mínimo. Ante un disaster, las encendés y redirigís el tráfico.
# Ejemplo: replicación de snapshot de RDS a otra región
aws rds copy-db-snapshot \
--source-db-snapshot-identifier arn:aws:rds:us-east-1:123:snapshot:prod-snap \
--target-db-snapshot-identifier prod-snap-dr \
--region sa-east-1HA dentro de una región:
- RDS Multi-AZ: standby sincrónico, failover automático.
- EC2 en múltiples AZs detrás de un ALB.
- Aurora: 6 copias del storage en 3 AZs.
- S3: replicación automática en múltiples AZs (ya viene incluida).
23. ¿Qué es CloudWatch y cómo lo usarías para monitorear tu aplicación?
Respuesta:
CloudWatch es la plataforma de observabilidad de AWS. Cubre:
- Metrics: datos numéricos con dimensiones y timestamps. Cada servicio AWS publica métricas automáticamente.
- Logs: centraliza logs de aplicaciones, Lambda, EC2, VPC Flow Logs, etc.
- Alarms: alertas basadas en métricas con acciones (SNS, Auto Scaling, EC2 actions).
- Dashboards: visualización en tiempo real.
- Insights: queries SQL-like sobre logs con CloudWatch Logs Insights.
- Synthetics: monitoreo de endpoints simulando usuarios reales (canary).
Publicar métricas custom desde tu app:
import boto3
from datetime import datetime
cloudwatch = boto3.client('cloudwatch')
def registrar_metrica(nombre: str, valor: float, unidad: str = 'Count'):
cloudwatch.put_metric_data(
Namespace='MiApp/API',
MetricData=[
{
'MetricName': nombre,
'Value': valor,
'Unit': unidad,
'Timestamp': datetime.utcnow(),
'Dimensions': [
{'Name': 'Environment', 'Value': 'production'},
{'Name': 'Service', 'Value': 'users-api'}
]
}
]
)
# Ejemplo de uso
registrar_metrica('ErrorRate', 0.02, 'Percent')
registrar_metrica('ResponseTime', 245, 'Milliseconds')
registrar_metrica('ActiveUsers', 1523)Query en CloudWatch Logs Insights:
fields @timestamp, @message, level, user_id
| filter level = "ERROR"
| filter @message like /timeout/
| stats count(*) as total_timeouts by user_id
| sort total_timeouts desc
| limit 20Alarm que dispara una notificación:
resource "aws_cloudwatch_metric_alarm" "error_rate" {
alarm_name = "api-error-rate-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "5XXError"
namespace = "AWS/ApiGateway"
period = 60
statistic = "Sum"
threshold = 10
alarm_description = "Más de 10 errores 5xx en 2 minutos"
alarm_actions = [aws_sns_topic.alertas.arn]
ok_actions = [aws_sns_topic.alertas.arn]
dimensions = {
ApiName = "mi-api"
Stage = "prod"
}
}24. ¿Qué es AWS Step Functions y cuándo lo usarías?
Respuesta:
Step Functions es un orquestador de workflows serverless. Te permite coordinar múltiples Lambdas (y otros servicios AWS) en una secuencia con manejo de errores, reintentos, paralelismo y tiempos de espera — sin escribir esa lógica en tu código.
Cuándo lo usás:
- Workflows de negocio largos (onboarding, procesamiento de pedidos).
- Orquestación de microservicios donde necesitás visibilidad del estado.
- Procesos que pueden fallar y necesitan reintentos con backoff.
- Workflows que esperan confirmación humana (waitForTaskToken).
Ejemplo de State Machine (procesamiento de CV):
{
"Comment": "Pipeline de procesamiento de CV",
"StartAt": "ExtraerTexto",
"States": {
"ExtraerTexto": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:extraer-texto-cv",
"Retry": [
{
"ErrorEquals": ["Lambda.ServiceException"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2
}
],
"Next": "AnalizarCV"
},
"AnalizarCV": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:analizar-cv",
"Next": "GenerarPreguntas"
},
"GenerarPreguntas": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "PreguntasTecnicas",
"States": {
"PreguntasTecnicas": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:gen-preguntas-tecnicas",
"End": true
}
}
},
{
"StartAt": "PreguntasBehavioral",
"States": {
"PreguntasBehavioral": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:gen-preguntas-behavioral",
"End": true
}
}
}
],
"Next": "GuardarResultados"
},
"GuardarResultados": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:guardar-dossier",
"End": true
}
}
}25. ¿Qué es AWS X-Ray y cómo implementás trazas distribuidas?
Respuesta:
X-Ray es el servicio de distributed tracing de AWS. Te permite rastrear una request a través de múltiples servicios (Lambda, API Gateway, RDS, servicios externos) y ver dónde se va el tiempo.
Conceptos clave:
- Trace: el recorrido completo de una request de extremo a extremo.
- Segment: trabajo hecho por un servicio o componente.
- Subsegment: trabajo dentro de un segment (llamada a DB, llamada HTTP externa).
- Service Map: visualización gráfica de tu arquitectura con latencias y errores.
Instrumentar una Lambda en Node.js:
const AWSXRay = require('aws-xray-sdk-core');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
exports.handler = async (event) => {
// Crear subsegmento para una llamada externa
const segment = AWSXRay.getSegment();
const subsegment = segment.addNewSubsegment('llamada-externa-api');
try {
const response = await fetch('https://api.ejemplo.com/datos');
subsegment.addAnnotation('status_code', response.status);
subsegment.close();
// DynamoDB ya está instrumentado automáticamente por captureAWS
const dynamodb = new AWS.DynamoDB.DocumentClient();
const result = await dynamodb.get({ ... }).promise();
return { statusCode: 200, body: JSON.stringify(result.Item) };
} catch (error) {
subsegment.addError(error);
subsegment.close();
throw error;
}
};26. Explicá el patrón Circuit Breaker en el contexto de microservicios en AWS
Respuesta:
El circuit breaker protege tu sistema de llamadas en cascada a un servicio que está fallando. En vez de seguir esperando timeouts, abre el circuito y retorna un error rápido.
Estados:
- Closed (normal): llamadas pasan, se cuentan los fallos.
- Open (fallo detectado): llamadas fallan inmediatamente sin hacer la request real.
- Half-open: después de un tiempo, se deja pasar una request de prueba. Si funciona → Closed. Si falla → Open.
Implementación en AWS:
Podés usar AWS App Mesh (service mesh con Envoy) para implementar circuit breaking a nivel de infraestructura, sin tocar el código:
# App Mesh: circuit breaker en la Virtual Node
spec:
backendDefaults:
clientPolicy:
tls:
enforce: false
backends:
- virtualService:
virtualServiceName: pagos-service.mi-namespace.svc.cluster.local
listeners:
- portMapping:
port: 3000
protocol: http
outlierDetection:
maxServerErrors: 5
interval:
value: 30
unit: s
baseEjectionDuration:
value: 30
unit: s
maxEjectionPercent: 100O con AWS Resilience Hub para análisis de resiliencia automatizado.
27. ¿Qué es Infrastructure as Code (IaC) y cuál es tu stack preferido?
Respuesta:
IaC es el principio de definir y gestionar infraestructura a través de código en vez de interfaces gráficas o comandos manuales. Beneficios: reproducibilidad, versionado, revisión de cambios, rollback.
Opciones en AWS:
- CloudFormation: nativo de AWS, YAML/JSON, soporte completo de todos los servicios el día del lanzamiento. Verbose.
- CDK (Cloud Development Kit): escribís tu infra en TypeScript/Python/Java/Go, CDK genera CloudFormation. Más expresivo, reutilizable.
- Terraform: multi-cloud, HCL, gran comunidad, módulos reutilizables. No soporta todos los recursos de AWS el día 1.
- Pulumi: como CDK pero usando lenguajes de programación reales (no DSL).
Ejemplo CDK (TypeScript) — Lambda + API Gateway + DynamoDB:
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
export class ApiStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// DynamoDB Table
const table = new dynamodb.Table(this, 'UsersTable', {
partitionKey: { name: 'user_id', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
// Lambda Function
const fn = new lambda.Function(this, 'ApiHandler', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
environment: {
TABLE_NAME: table.tableName,
},
memorySize: 512,
timeout: cdk.Duration.seconds(29),
});
// Dar permisos de lectura/escritura a la Lambda
table.grantReadWriteData(fn);
// API Gateway
const api = new apigateway.RestApi(this, 'UsersApi', {
restApiName: 'Users API',
});
const users = api.root.addResource('users');
users.addMethod('POST', new apigateway.LambdaIntegration(fn));
const user = users.addResource('{id}');
user.addMethod('GET', new apigateway.LambdaIntegration(fn));
}
}SECCIÓN 4 — Seguridad y costos (preguntas 28–34)
28. ¿Cómo gestionás secretos en AWS? Secrets Manager vs SSM Parameter Store
Respuesta:
SSM Parameter Store:
- Parámetros de configuración y secretos simples.
- Tier gratuito hasta 10,000 parámetros estándar.
- Encriptación con KMS opcional.
- Versionado.
- No tiene rotación automática nativa.
Secrets Manager:
- Diseñado específicamente para secretos.
- Rotación automática con Lambda (integrado con RDS, Redshift, DocumentDB).
- Costo: ~$0.40/secreto/mes.
- Replicación multi-región.
- Auditoría con CloudTrail.
Cuándo usás cada uno:
- Secrets Manager: passwords de DB, API keys de terceros, credenciales con rotación.
- SSM Parameter Store: configuraciones no secretas, feature flags, y secretos simples donde el costo de Secrets Manager no se justifica.
Recuperar un secreto desde Lambda:
import boto3
import json
from functools import lru_cache
secrets_client = boto3.client('secretsmanager')
@lru_cache(maxsize=None) # Cachear en memoria del container Lambda
def get_secret(secret_name: str) -> dict:
response = secrets_client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
def handler(event, context):
# El secreto se cachea — no llama a Secrets Manager en cada invocación
db_creds = get_secret('prod/mi-app/db')
conn = create_db_connection(
host=db_creds['host'],
user=db_creds['username'],
password=db_creds['password']
)
# ...NUNCA: hardcodear credenciales en código, variables de entorno en texto plano para datos sensibles, o commitar secrets al repositorio.
29. ¿Qué es KMS y cómo implementás encriptación en tus servicios?
Respuesta:
KMS (Key Management Service) es el servicio de gestión de claves de AWS. Genera, almacena y gestiona claves criptográficas. Integra directamente con S3, RDS, EBS, DynamoDB, SNS, SQS, y otros.
Tipos de claves:
- AWS Managed Keys (aws/service): las maneja AWS, gratis, sin control directo. Ej:
aws/s3,aws/rds. - Customer Managed Keys (CMK): vos las creás y controlás. Podés rotar, deshabilitar, auditar con CloudTrail. $1/mes + $0.03 por 10k requests de API.
- Customer Provided Keys (SSE-C en S3): vos proveés la clave, AWS la usa para encriptar y la descarta.
Encriptar/desencriptar manualmente con KMS:
import boto3
import base64
kms = boto3.client('kms')
KEY_ID = 'arn:aws:kms:us-east-1:123:key/xxx-yyy-zzz'
def encrypt(plaintext: str) -> str:
response = kms.encrypt(
KeyId=KEY_ID,
Plaintext=plaintext.encode('utf-8')
)
return base64.b64encode(response['CiphertextBlob']).decode('utf-8')
def decrypt(ciphertext_b64: str) -> str:
ciphertext = base64.b64decode(ciphertext_b64)
response = kms.decrypt(
CiphertextBlob=ciphertext,
KeyId=KEY_ID
)
return response['Plaintext'].decode('utf-8')
# Más común: usar KMS a través de los servicios (S3, RDS, etc.)
# donde la encriptación es transparenteEncriptación en S3 con CMK:
resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3_key.arn
}
bucket_key_enabled = true # Reduce costo de KMS API calls
}
}30. ¿Qué es AWS WAF y cómo protegés tu API?
Respuesta:
WAF (Web Application Firewall) filtra tráfico HTTP/HTTPS según reglas. Se integra con CloudFront, ALB, API Gateway, y AppSync.
Tipos de reglas:
- Managed Rule Groups: conjuntos de reglas pre-armados por AWS o terceros (OWASP Top 10, Bot Control, SQL injection, XSS).
- Custom Rules: tus propias reglas (rate limiting por IP, bloquear países, headers específicos).
- IP Sets: listas de IPs a bloquear o permitir.
Ejemplo en Terraform:
resource "aws_wafv2_web_acl" "api_protection" {
name = "api-protection"
scope = "REGIONAL" # CLOUDFRONT para CloudFront
default_action {
allow {}
}
# Rate limiting: 1000 req/5min por IP
rule {
name = "rate-limit"
priority = 1
action { block {} }
statement {
rate_based_statement {
limit = 1000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimit"
sampled_requests_enabled = true
}
}
# AWS Managed Rules: Core Rule Set (OWASP Top 10)
rule {
name = "aws-managed-crs"
priority = 2
override_action { none {} }
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesCRS"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "ApiProtectionACL"
sampled_requests_enabled = true
}
}31. ¿Cómo optimizás costos en AWS? Nombré 5 estrategias concretas
Respuesta:
1. Savings Plans / Reserved Instances para cargas predecibles:
- Savings Plans: compromiso de gasto en $/hora durante 1 o 3 años. Descuentos de hasta 72%.
- Compute Savings Plans: aplica a EC2, Lambda, Fargate — más flexible.
- EC2 Instance Savings Plans: aplica solo a un tipo/región — mayor descuento.
2. Spot Instances para cargas tolerantes a interrupciones:
- Hasta 90% más barato que On-Demand.
- AWS puede interrumpir con 2 minutos de aviso.
- Ideal para: procesamiento de batch, CI/CD, entrenamiento de ML, workers stateless.
3. S3 Intelligent-Tiering y lifecycle policies:
- Logs que nadie mira después de 90 días → S3 Glacier.
- Snapshots viejos → eliminar automáticamente.
4. RDS/Aurora Serverless para workloads variables:
- Escala a cero cuando no se usa (dev/staging).
5. AWS Compute Optimizer y Cost Explorer:
- Compute Optimizer analiza métricas de utilización y recomienda tipos de instancia más baratos.
- Cost Explorer identifica servicios con mayor gasto y tendencias.
# Ver recomendaciones de rightsizing
aws compute-optimizer get-ec2-instance-recommendations \
--filters name=Finding,values=OVER_PROVISIONED
# Cost Explorer: gasto de los últimos 30 días por servicio
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=2024-02-01 \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE32. ¿Qué es AWS Organizations y cómo estructurás una cuenta multi-cuenta?
Respuesta:
AWS Organizations permite gestionar múltiples cuentas AWS desde una cuenta maestra (Management Account). La estructura se organiza en OUs (Organizational Units).
Estrategia de cuentas recomendada:
Root (Management Account)
├── OU: Security
│ ├── Audit Account (CloudTrail centralizado, Config)
│ └── Log Archive Account (todos los logs)
├── OU: Infrastructure
│ └── Shared Services Account (DNS, pipelines CI/CD, ECR)
├── OU: Workloads
│ ├── OU: Producción
│ │ └── Account: App-Prod
│ ├── OU: Staging
│ │ └── Account: App-Staging
│ └── OU: Dev
│ └── Account: App-Dev
└── OU: Sandbox
└── (cuentas temporales para experimentación)Service Control Policies (SCPs):
Políticas que se aplican a OUs enteras y no pueden ser sobreescritas. Ejemplo: prohibir lanzar recursos fuera de us-east-1 y sa-east-1.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyOutsideAllowedRegions",
"Effect": "Deny",
"NotAction": [
"iam:*",
"sts:*",
"cloudfront:*",
"route53:*",
"support:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "sa-east-1"]
}
}
}
]
}33. ¿Qué es AWS Config y cuándo lo usarías junto con GuardDuty?
Respuesta:
AWS Config:
- Registra el estado y los cambios de configuración de tus recursos AWS a lo largo del tiempo.
- Define Config Rules para verificar compliance: "¿Todos los buckets S3 tienen encriptación habilitada?", "¿Todas las instancias EC2 están en un security group con el puerto 22 cerrado a internet?"
- Cuando una regla falla, puede disparar una Lambda de remediación automática.
GuardDuty:
- Servicio de detección de amenazas que analiza continuamente logs de CloudTrail, VPC Flow Logs, y DNS logs.
- Usa machine learning para detectar comportamientos anómalos: acceso inusual a S3, llamadas a API desde IPs maliciosas conocidas, instancias EC2 haciendo minería de criptomonedas.
- No necesitás instalar agentes ni configurar sources.
La diferencia clave:
- Config: postura de seguridad (¿mis recursos están configurados correctamente?).
- GuardDuty: amenazas activas (¿alguien me está atacando o ya está adentro?).
# Habilitar GuardDuty
aws guardduty create-detector --enable --finding-publishing-frequency FIFTEEN_MINUTES
# Crear una Config Rule (ejemplo: S3 con bucket policy pública = NON_COMPLIANT)
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "s3-bucket-public-read-prohibited",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "S3_BUCKET_PUBLIC_READ_PROHIBITED"
}
}'34. ¿Cómo implementás CI/CD en AWS?
Respuesta:
AWS tiene un stack nativo de CI/CD:
- CodeCommit: Git hosting (aunque hoy la mayoría usa GitHub/GitLab).
- CodeBuild: ejecuta los builds (compilar, testear, generar artefactos). Similar a GitHub Actions runners pero gestionado.
- CodeDeploy: despliega a EC2, Lambda, ECS.
- CodePipeline: orquesta todo el pipeline (source → build → deploy).
Pipeline típico con GitHub → CodeBuild → ECS:
# buildspec.yml (CodeBuild)
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_REPO
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Building the Docker image...
- docker build -t $ECR_REPO:$IMAGE_TAG .
- docker tag $ECR_REPO:$IMAGE_TAG $ECR_REPO:latest
post_build:
commands:
- echo Pushing the Docker image...
- docker push $ECR_REPO:$IMAGE_TAG
- docker push $ECR_REPO:latest
- echo Writing image definitions file...
- printf '[{"name":"app","imageUri":"%s"}]' $ECR_REPO:$IMAGE_TAG > imagedefinitions.json
artifacts:
files: imagedefinitions.jsonEstrategias de deployment en ECS:
- Rolling Update: reemplaza contenedores viejos gradualmente.
- Blue/Green (con CodeDeploy): desplegás una versión nueva (green), probás, y switcheás el tráfico. Rollback instantáneo.
- Canary: enviás un % del tráfico a la versión nueva antes del switch completo.
SECCIÓN 5 — Preguntas de diseño de sistemas (preguntas 35–43)
35. Diseñá un sistema de notificaciones push a escala para millones de usuarios
Respuesta:
La clave en este tipo de pregunta es demostrar que entendés los bottlenecks antes de proponer componentes.
Clarificaciones que haría:
- ¿Qué volumen? ¿1M de usuarios activos, 10M?
- ¿Tiempo real o puede haber delay de segundos/minutos?
- ¿Qué canales? (push nativa iOS/Android, email, SMS, in-app)
Diseño:
Productores de eventos
→ SQS / Kinesis (buffer y decoupling)
→ Lambda workers (determinar a qué usuarios enviar)
→ Fan-out por canal:
├── SNS (push nativa) → Apple APNs / Google FCM
├── SES / Pinpoint (email)
├── SNS SMS (texto)
└── DynamoDB (in-app, el cliente hace polling/websocket)
→ DynamoDB (registro de notificaciones enviadas + estado)
→ CloudWatch (métricas de entrega, bounces)Decisiones de diseño importantes:
- Usar Kinesis si el orden importa (ej. notificaciones de estado de pedido).
- Usar SQS si el orden no importa y querés backpressure natural.
- Persistir las notificaciones en DynamoDB para inbox in-app.
- Rate limiting: SNS aplica sus propios límites; para SMS, Pinpoint tiene controles más granulares.
- Opt-out: tabla de preferencias por usuario antes de enviar.
36. Diseñá un pipeline de procesamiento de imágenes/videos subidos por usuarios
Respuesta:
Usuario sube archivo
→ Pre-signed URL de S3 (sube directo a S3, sin pasar por tu backend)
→ S3 Event Notification → SQS (buffer)
→ Lambda / ECS worker
→ Validación (tipo, tamaño, virus scan con GuardDuty Malware Protection)
→ Procesamiento en paralelo (Step Functions o Lambda con SQS)
├── Thumbnail generación (Lambda + Sharp/FFmpeg)
├── Transcoding de video (AWS MediaConvert)
├── Moderación de contenido (Rekognition)
└── Extracción de metadata
→ Guardar resultados en DynamoDB
→ Mover originales a S3 Glacier (lifecycle policy)
→ Invalidar caché de CloudFront si era una actualización
→ Notificar al usuario (SES / SNS)Pre-signed URL (el patrón para uploads directos a S3):
import boto3
s3 = boto3.client('s3')
def generar_presigned_upload_url(user_id: str, file_name: str) -> dict:
key = f"uploads/{user_id}/{file_name}"
url = s3.generate_presigned_url(
'put_object',
Params={
'Bucket': 'mi-bucket-uploads',
'Key': key,
'ContentType': 'image/jpeg',
'Metadata': {'user_id': user_id}
},
ExpiresIn=300 # URL válida por 5 minutos
)
return {'upload_url': url, 'key': key}37. Diseñá un sistema de búsqueda de texto completo
Respuesta:
DynamoDB no hace text search eficiente. Las opciones:
OpenSearch (sucesor de Elasticsearch en AWS):
- Full-text search con relevance scoring, facets, highlighting.
- Indexar desde DynamoDB usando Lambda + DynamoDB Streams, o desde RDS con CDC.
Arquitectura:
Escritura:
DynamoDB / RDS
→ DynamoDB Streams / RDS CDC (Debezium)
→ Lambda / Kinesis
→ OpenSearch (indexación)
Lectura:
API Gateway
→ Lambda
→ OpenSearch (search)
→ DynamoDB (detalles del item por ID)Ejemplo de búsqueda con el SDK de OpenSearch:
from opensearchpy import OpenSearch
client = OpenSearch(
hosts=[{'host': 'mi-domain.us-east-1.es.amazonaws.com', 'port': 443}],
http_auth=('usuario', 'password'),
use_ssl=True
)
def buscar_vacantes(query: str, filtros: dict = {}) -> list:
body = {
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": query,
"fields": ["titulo^3", "descripcion", "empresa^2"],
"fuzziness": "AUTO"
}
}
],
"filter": []
}
},
"highlight": {
"fields": {"titulo": {}, "descripcion": {"fragment_size": 150}}
},
"sort": [{"_score": "desc"}, {"fecha_publicacion": "desc"}]
}
if filtros.get('seniority'):
body["query"]["bool"]["filter"].append(
{"term": {"seniority": filtros['seniority']}}
)
response = client.search(index="vacantes", body=body, size=20)
return [hit['_source'] for hit in response['hits']['hits']]38. ¿Cómo implementarías rate limiting en una API serverless?
Respuesta:
El rate limiting en AWS tiene múltiples capas:
Capa 1: API Gateway (más simple)
- Throttling por stage: límite global de la API.
- Throttling por método/recurso.
- Usage Plans + API Keys: límites por cliente/partner.
resource "aws_api_gateway_stage" "prod" {
# ...
default_route_settings {
throttling_burst_limit = 1000 # pico máximo
throttling_rate_limit = 500 # req/seg sostenido
}
}
resource "aws_api_gateway_usage_plan" "free_tier" {
name = "free-tier"
throttle_settings {
rate_limit = 10 # 10 req/seg
burst_limit = 50
}
quota_settings {
limit = 1000 # 1000 req/día
period = "DAY"
}
}Capa 2: Lambda + ElastiCache (sliding window)
Para rate limiting más granular (por user_id, por endpoint, sliding window):
import redis
import time
cache = redis.Redis(host='mi-cache.xxx.cache.amazonaws.com')
def check_rate_limit(user_id: str, max_requests: int = 100, window_seconds: int = 60) -> bool:
key = f"rate_limit:{user_id}"
now = time.time()
window_start = now - window_seconds
pipe = cache.pipeline()
# Eliminar requests fuera de la ventana
pipe.zremrangebyscore(key, 0, window_start)
# Agregar el timestamp actual
pipe.zadd(key, {str(now): now})
# Contar requests en la ventana
pipe.zcard(key)
# Expirar la key
pipe.expire(key, window_seconds + 1)
results = pipe.execute()
request_count = results[2]
return request_count <= max_requests
def handler(event, context):
user_id = event['requestContext']['authorizer']['claims']['sub']
if not check_rate_limit(user_id, max_requests=100, window_seconds=60):
return {
'statusCode': 429,
'headers': {'Retry-After': '60'},
'body': json.dumps({'error': 'Too many requests'})
}
# Procesar la request...39. ¿Cómo implementarías WebSockets en AWS?
Respuesta:
API Gateway tiene soporte nativo para WebSockets desde 2018.
Cómo funciona:
- Cuando un cliente conecta, API Gateway invoca la Lambda de
$connect. - Cuando manda un mensaje, invoca la Lambda de
$defaulto el route específico. - Cuando desconecta, invoca la Lambda de
$disconnect. - Para mandar mensajes al cliente desde el servidor, usás la Management API de API Gateway.
Guardar las conexiones activas:
import boto3
import json
dynamodb = boto3.resource('dynamodb')
connections_table = dynamodb.Table('ws-connections')
def connect_handler(event, context):
connection_id = event['requestContext']['connectionId']
user_id = event['requestContext']['authorizer']['userId']
connections_table.put_item(Item={
'connection_id': connection_id,
'user_id': user_id,
'connected_at': event['requestContext']['connectedAt']
})
return {'statusCode': 200}
def disconnect_handler(event, context):
connection_id = event['requestContext']['connectionId']
connections_table.delete_item(Key={'connection_id': connection_id})
return {'statusCode': 200}
def broadcast_to_user(user_id: str, message: dict, api_endpoint: str):
"""Enviar mensaje a todas las conexiones activas de un usuario"""
apigw = boto3.client('apigatewaymanagementapi', endpoint_url=api_endpoint)
# Buscar todas las conexiones del usuario
response = connections_table.query(
IndexName='user-id-index',
KeyConditionExpression='user_id = :uid',
ExpressionAttributeValues={':uid': user_id}
)
for item in response['Items']:
try:
apigw.post_to_connection(
ConnectionId=item['connection_id'],
Data=json.dumps(message).encode('utf-8')
)
except apigw.exceptions.GoneException:
# Conexión ya cerrada, limpiar
connections_table.delete_item(Key={'connection_id': item['connection_id']})40. Explicá el patrón Saga para manejar transacciones distribuidas
Respuesta:
En microservicios, no podés hacer transacciones ACID entre múltiples servicios con bases de datos distintas. El patrón Saga divide la transacción en una secuencia de transacciones locales, cada una con su transacción compensatoria en caso de fallo.
Ejemplo: reserva de vuelo
1. ReservarAsiento (avión) → OK
2. CargarTarjeta (pagos) → OK
3. EnviarConfirmación (email) → FALLA
→ Compensar: RevertrCargoTarjeta (pagos)
→ Compensar: LiberarAsiento (avión)Implementación con Step Functions (choreography vs orchestration):
{
"StartAt": "ReservarAsiento",
"States": {
"ReservarAsiento": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:reservar-asiento",
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "FailState"
}],
"Next": "CargarTarjeta"
},
"CargarTarjeta": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:cargar-tarjeta",
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "CompensarAsiento"
}],
"Next": "EnviarConfirmacion"
},
"EnviarConfirmacion": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:enviar-email",
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "CompensarTarjeta"
}],
"End": true
},
"CompensarTarjeta": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:revertir-cargo",
"Next": "CompensarAsiento"
},
"CompensarAsiento": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:liberar-asiento",
"Next": "FailState"
},
"FailState": {
"Type": "Fail",
"Error": "SagaFailed",
"Cause": "Transacción distribuida fallida, compensaciones ejecutadas"
}
}
}41. ¿Cómo implementarías un sistema de autenticación con Cognito?
Respuesta:
Cognito tiene dos componentes:
- User Pools: directorio de usuarios con autenticación (signup, signin, MFA, password recovery).
- Identity Pools (Federated Identities): intercambia tokens de User Pool (u otros IdPs: Google, Facebook, SAML) por credenciales temporales de IAM para acceder a servicios AWS directamente.
Flujo típico de autenticación:
1. Usuario hace login → Cognito User Pool
2. Cognito retorna: ID Token (JWT) + Access Token + Refresh Token
3. Frontend envía Access Token en cada request
4. API Gateway verifica el JWT automáticamente (Cognito Authorizer)
5. Lambda recibe el usuario autenticado en el contextoConfigurar Cognito Authorizer en API Gateway (Terraform):
resource "aws_api_gateway_authorizer" "cognito" {
name = "cognito-authorizer"
rest_api_id = aws_api_gateway_rest_api.api.id
type = "COGNITO_USER_POOLS"
provider_arns = [aws_cognito_user_pool.users.arn]
}Login con AWS Amplify (frontend):
import { signIn, signUp, confirmSignUp } from 'aws-amplify/auth';
async function login(email: string, password: string) {
const { isSignedIn, nextStep } = await signIn({
username: email,
password,
});
if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE') {
// Pedir código MFA al usuario
}
return isSignedIn;
}Trigger Lambda para personalizar el flujo (ej: post-confirmación):
def handler(event, context):
if event['triggerSource'] == 'PostConfirmation_ConfirmSignUp':
user_id = event['request']['userAttributes']['sub']
email = event['request']['userAttributes']['email']
# Crear perfil en DynamoDB
dynamodb.Table('profiles').put_item(Item={
'user_id': user_id,
'email': email,
'plan': 'free',
'created_at': datetime.utcnow().isoformat()
})
return event # Siempre devolver el evento en los triggers de Cognito42. ¿Qué diferencias hay entre SQS Standard Queue y FIFO Queue?
Respuesta:
| | Standard Queue | FIFO Queue |
|---|---|---|
| Orden | Best-effort (no garantizado) | Garantizado (First-In-First-Out) |
| Entrega | At-least-once (puede llegar más de una vez) | Exactly-once (deduplica por 5 min) |
| Throughput | Prácticamente ilimitado | 300 msg/s (sin batching), 3000/s (con batching) |
| Deduplicación | No (vos manejás idempotencia) | Por MessageDeduplicationId (SHA-256 del body) |
| Disponibilidad | Alta | Alta |
Cuándo FIFO:
- Procesar pedidos en el orden en que llegaron.
- Aplicar cambios de configuración en secuencia.
- Prevenir duplicados cuando el backend no es idempotente.
Cuándo Standard:
- Procesamiento de emails, notificaciones.
- Workers donde el orden no importa.
- Máximo throughput.
Diseñar para idempotencia con Standard Queue:
def handler(event, context):
for record in event['Records']:
message = json.loads(record['body'])
message_id = record['messageId']
# Verificar si ya procesamos este mensaje
try:
processed_table.put_item(
Item={'message_id': message_id, 'processed_at': datetime.utcnow().isoformat()},
ConditionExpression='attribute_not_exists(message_id)'
)
except processed_table.meta.client.exceptions.ConditionalCheckFailedException:
print(f"Mensaje {message_id} ya procesado, saltando")
continue
# Procesar el mensaje
procesar(message)43. Explicá cómo funciona Lambda con VPC y cuáles son las implicaciones de performance
Respuesta:
Por defecto, las Lambdas NO están dentro de tu VPC. Se ejecutan en la infraestructura de AWS y tienen acceso a internet, pero no a tus recursos privados (RDS en subnet privada, ElastiCache, etc.).
Cuando añadís Lambda a una VPC:
- La función tiene acceso a recursos privados de la VPC.
- Ya no tiene acceso a internet directamente — necesitás un NAT Gateway en la VPC.
- Se crea una ENI (Elastic Network Interface) por función.
El problema histórico de performance (RESUELTO en 2019):
Antes de 2019, cada invocación de Lambda en VPC creaba una nueva ENI → cold start de hasta 10 segundos. Desde 2019, AWS usa Hyperplane (ENIs compartidas) y el cold start de VPC desapareció como problema.
Cold start actual:
- Lambda sin VPC: ~100-300ms de cold start (nueva instancia del container).
- Lambda con VPC: prácticamente igual.
Qué todavía causa cold starts:
- Primera invocación después de un período de inactividad.
- Scaling a más instancias que las cacheadas.
- Runtime de alto overhead (Java/C# con JVM > Node.js/Python).
Mitigaciones:
- Provisioned Concurrency: mantiene N instancias pre-inicializadas. Costo extra, pero elimina cold starts.
- SnapStart (Java): guarda un snapshot del estado post-inicialización.
resource "aws_lambda_provisioned_concurrency_config" "api" {
function_name = aws_lambda_function.api.function_name
qualifier = aws_lambda_alias.live.name
provisioned_concurrent_executions = 10
}Preguntas rápidas de repaso (44–47)
44. ¿Qué es ARN? ¿Cuál es el formato?
Un ARN (Amazon Resource Name) es el identificador único universal de un recurso en AWS.
arn:partition:service:region:account-id:resource-type/resource-id
# Ejemplos:
arn:aws:s3:::mi-bucket # S3 (sin región ni cuenta)
arn:aws:lambda:us-east-1:123456789:function:mi-fn
arn:aws:iam::123456789:role/mi-rol # IAM (sin región)
arn:aws:rds:us-east-1:123456789:db:mi-db45. ¿Qué es un bucket policy vs una ACL en S3?
- Bucket Policy: documento JSON que define quién puede hacer qué en el bucket. Más expresivo, recomendado.
- ACL: lista de control de acceso más antigua, más limitada. AWS recomienda deshabilitarla y usar solo bucket policies.
- Block Public Access: configuración a nivel de cuenta/bucket que bloquea cualquier política que haría el bucket público. Activado por default.
46. ¿Qué es el principio de inmutabilidad en la infraestructura cloud?
En vez de modificar una instancia/contenedor en producción (SSH para parchear), reemplazás. Lanzás una nueva AMI/imagen con el parche aplicado y terminás las instancias viejas. Ventajas: no hay "configuration drift", los entornos son reproducibles, el rollback es trivial (volvés a la AMI anterior).
47. ¿Qué es AWS Fargate vs EKS vs ECS?
- ECS (Elastic Container Service): orquestador de contenedores nativo de AWS. Dos launch types: EC2 (vos gestionás el cluster) y Fargate (AWS gestiona todo).
- EKS (Elastic Kubernetes Service): Kubernetes gestionado. Usás si ya tenés expertise en k8s, si necesitás portabilidad multi-cloud, o si tenés workloads complejos que aprovechan el ecosistema k8s.
- Fargate: capa de compute serverless que puede usar ECS o EKS. Sin gestionar nodos.
Regla práctica: empezá con ECS + Fargate salvo que ya estés en Kubernetes o tengas una razón clara para usarlo.
Cómo practicar estas respuestas
Leer estas respuestas no es suficiente. La diferencia entre el candidato que consigue el trabajo y el que no está en la fluidez bajo presión.
La forma más efectiva de prepararse:
- 1Leé la pregunta y la respuesta una vez.
- 2Cerrá la guía y explicala en voz alta, como si estuvieras en la entrevista.
- 3Abrí la guía y verificá qué te faltó.
- 4Repetí hasta que la respuesta fluya sin mirar.
El retrieval activo (intentar recordar sin mirar) consolida el conocimiento mucho más que releer. Es incómodo al principio — esa incomodidad es la señal de que está funcionando.
Para el código, no memorices la sintaxis exacta — entendé el patrón. En una entrevista técnica real podés buscar la firma exacta de una función, pero no podés buscar "qué patrón usar cuando mi Lambda necesita acceder a RDS en una subnet privada".
*Dominás AWS cuando podés explicar cada servicio como si le hablaras a alguien inteligente que nunca lo usó, y podés defender las decisiones de arquitectura bajo preguntas de seguimiento. Eso es lo que practica este artículo.*