Los tres servicios guardan bytes y ahí se termina el parecido. RDS te alquila una máquina virtual con un motor de base de datos adentro. DynamoDB te vende operaciones contra un hash distribuido en particiones. S3 te vende un índice de claves que apunta a blobs inmutables. Son tres máquinas distintas debajo de la misma palabra: "almacenamiento".
Entender esa máquina te da la forma de la factura y la forma del fallo al mismo tiempo. La unidad que el servicio reserva para vos es la unidad que te cobra y también la que se satura cuando el tráfico crece. Los tres se facturan con lógicas incompatibles —tiempo, operaciones y volumen por clase— y esa lógica es un criterio de diseño, no una consecuencia que se descubre leyendo la factura.
Elegir almacenamiento es elegir contra qué unidad querés chocar. RDS te cobra por hora reservada, DynamoDB por kilobyte movido, S3 por byte guardado y por request. El cuello de botella siempre aparece en la misma unidad que la línea de la factura.
Todos los precios de acá son de us-east-1 (N. Virginia) y están verificados a la fecha del frontmatter contra la página de pricing del servicio correspondiente, linkeada en cada sección. AWS los mueve: contrastá contra la Pricing Calculator antes de decidir con plata real.
RDS: una máquina prendida con un disco atado
RDS es una instancia EC2 con el motor instalado, más un plano de control que hace failover, backups automáticos y patching. La palabra clave es instancia: hay un hypervisor con vCPU y RAM reservadas para vos, con el proceso de PostgreSQL o MySQL levantado. El storage es EBS, un volumen que vos declarás con tamaño fijo y que existe desde el momento en que lo pedís.
De ese mecanismo salen cuatro consecuencias de costo. Los números salen del pricing de RDS para PostgreSQL.
La instancia se cobra por hora, corra queries o no. Una db.t4g.medium con PostgreSQL Single-AZ está alrededor de USD 0,065 la hora: por 730 horas, USD 47,45 al mes. Con 100 GB de gp3 a USD 0,115 el GB-mes se suman USD 11,50. Cerca de USD 59 mensuales que no se mueven si el tráfico es cero. Un staging que nadie usa cuesta lo mismo que producción, salvo que lo apagues — y una instancia RDS detenida vuelve a arrancar sola a los siete días.
El storage se cobra aprovisionado, no usado. 500 GB declarados con 40 GB adentro son 500 GB facturados. Y RDS no achica volúmenes: para bajar de tamaño hay que hacer dump y restore contra una instancia nueva.
Multi-AZ es una segunda instancia con réplica sincrónica en otra AZ. Duplica el cómputo porque son dos máquinas prendidas, no un truco de software — y duplica también la tarifa del storage, que pasa a facturarse a la tarifa Multi-AZ, cerca de USD 0,23 el GB-mes. La misma db.t4g.medium con 100 GB en Multi-AZ arranca en USD 94,90 de cómputo más USD 23 de storage: USD 117,90 al mes con cero tráfico.
Los backups automáticos son gratis hasta el 100% del storage aprovisionado, y esa franquicia es agregada por región, no por instancia: si tenés cuatro bases de 200 GB en us-east-1, el colchón gratis es de 800 GB sobre el total de backups de la región. Arriba de eso, USD 0,095 el GB-mes. Los snapshots manuales no entran en esa franquicia y sobreviven al borrado de la instancia, así que una base que se dio de baja hace dos años puede seguir facturando storage de snapshot todos los meses.
# snapshots manuales vivos y cuánto storage arrastra cada uno
aws rds describe-db-snapshots --snapshot-type manual \
--query 'DBSnapshots[].[DBSnapshotIdentifier,AllocatedStorage]'
El modo de falla se deduce igual de rápido. Como hay una sola máquina sirviendo escrituras, el cuello es esa máquina: conexiones, buffer cache, IOPS del volumen. Un pico no escala solo. Aurora Serverless v2 mueve ACU en caliente; RDS clásico te deja el modify-db-instance con su ventana de mantenimiento. Las palancas reales son rightsizing contra métricas de CloudWatch y reserved instances de 1 o 3 años sobre la clase que ya sabés que vas a sostener — la reserva aplica al cómputo, no al storage ni a los backups.
DynamoDB: un hash distribuido con peaje por kilobyte
La partition key de un item pasa por una función de hash y el resultado decide en qué partición física vive. Una partición es la unidad real del sistema: tiene su propio storage y su propio techo de throughput. Ese techo es 3.000 unidades de lectura y 1.000 de escritura por segundo. Adaptive capacity redistribuye capacidad entre particiones desbalanceadas, pero no sube el techo por partición.
De ahí sale la consecuencia de diseño más importante del servicio: la clave de partición es el diseño de performance. Una partition key de baja cardinalidad — un status, un tenant_id con un tenant gigante, una fecha — concentra tráfico en pocas particiones y te throttlea aunque la tabla tenga capacidad de sobra a nivel global. Se ve como ProvisionedThroughputExceededException en una tabla que según la consola usa el 20% de su capacidad.
La unidad de facturación sale del mismo mecanismo. Una write request unit cubre 1 KB, y una write transaccional consume dos por KB. Una read request unit cubre 4 KB strongly consistent, 0,5 unidades por 4 KB si es eventually consistent, dos si es transaccional. Todo redondea hacia arriba: escribir un item de 1,2 KB cuesta 2 WRU.
El tamaño del item es precio, entonces. Meter un blob JSON de 30 KB dentro del item cuesta 30 WRU cada vez que lo escribís, aunque solo hayas cambiado un campo: DynamoDB cobra por el tamaño del item resultante, no por el delta. El límite duro son 400 KB por item.
Los índices secundarios globales corren por la misma lógica y son la línea que más rápido crece. Un GSI es una tabla replicada: mantiene su propia copia de los atributos proyectados y consume su propia capacidad de escritura cada vez que cambia un item que le corresponde. Tres GSI con proyección ALL multiplican por cuatro el costo de cada write y por cuatro el storage. Y la proyección no se cambia en caliente: para pasar de ALL a KEYS_ONLY hay que borrar el índice y recrearlo, con backfill completo.
El storage cuesta USD 0,25 el GB-mes en table class Standard y USD 0,10 en Standard-IA. Contra los USD 0,023 de S3 Standard, es once veces más caro por byte. El corolario es directo: DynamoDB guarda el puntero, S3 guarda el objeto.
On-demand contra provisioned: la cuenta que decide
Los dos modos cobran la misma máquina con dos relojes distintos. Estas son las tarifas de us-east-1 según el pricing on-demand y provisioned. La tarifa on-demand es la vigente desde el 1 de noviembre de 2024, cuando AWS la bajó a la mitad en todas las regiones: hay material dando vueltas con la tarifa anterior, y con esa tarifa la conclusión de esta sección se invierte.
| on-demand | provisioned | |
|---|---|---|
| Escritura | USD 0,625 por millón de WRU | USD 0,00065 por WCU-hora |
| Lectura | USD 0,125 por millón de RRU | USD 0,00013 por RCU-hora |
| Storage Standard | USD 0,25 por GB-mes | USD 0,25 por GB-mes |
La comparación entra en una línea. Una WCU sostenida durante un mes cuesta 730 × 0,00065 = USD 0,4745 y entrega una escritura por segundo: 2.628.000 escrituras. Esas mismas escrituras en on-demand cuestan 2,628 × 0,625 = USD 1,64. On-demand sale 3,46 veces más caro por operación. Con lecturas da idéntico: USD 0,0949 contra USD 0,3285, otra vez 3,46.
Invertí ese factor y tenés el punto de equilibrio: 28,9% de utilización sostenida. Si tu tabla consume más del 30% de la capacidad que tendrías que aprovisionar para cubrir el pico, provisioned gana. Con reserved capacity a un año, que descuenta hasta 54% sobre la tarifa provisioned, el umbral baja a cerca del 13%.
Eso convierte la elección en una pregunta sobre la forma del tráfico. Un tráfico plano en horario bancario, con picos previsibles y piso alto, vive arriba del 30% y pide provisioned con auto scaling. Una tabla nueva, un batch nocturno o un ambiente de test viven muy abajo y piden on-demand — que además absorbe picos sin throttling, algo que provisioned solo hace con retraso de minutos. Como regla gruesa: con un pico de más de 3,5 veces la media, on-demand gana.
La reserved capacity se compra en bloques de 100 WCU o 100 RCU, con hasta 54% de descuento a un año y 77% a tres, y es exclusiva de provisioned. El viejo free tier siempre gratis de 25 WCU, 25 RCU y 25 GB sigue aplicando solo a cuentas creadas antes del 15 de julio de 2025; las cuentas nuevas reciben créditos con vencimiento. Para presupuestar en serio, calculá sin free tier.
S3: un índice de claves sobre blobs inmutables
S3 mapea una key — una cadena de texto — a un blob de bytes. No hay directorios: las barras dentro de la key son caracteres comunes que la consola dibuja como carpetas. No hay escritura parcial, un PUT reemplaza el objeto entero. Desde diciembre de 2020 todas las operaciones tienen strong read-after-write consistency, así que el viejo folklore sobre lecturas que devuelven la versión anterior ya no aplica.
El índice está particionado por prefijo. Cada prefijo particionado sostiene 3.500 requests de escritura y 5.500 de lectura por segundo, y S3 divide los prefijos que se calientan. Por eso las keys que empiezan todas con la misma cadena — 2026/08/10/... — concentran carga en un prefijo hasta que S3 reacciona. Poner la parte de alta cardinalidad al principio reparte el trabajo desde el primer request.
Como no hay append, un log que crece son objetos nuevos. Y como se cobra por request además de por byte, un millón de archivos de 2 KB cuesta muchísimo más en requests que en storage: USD 0,005 por cada 1.000 PUT, COPY, POST o LIST, y USD 0,0004 por cada 1.000 GET en Standard.
Las clases de S3 y dónde está exactamente la línea de Glacier
Todas las clases guardan el mismo blob con la misma durabilidad declarada. Lo que cambia es cuánto tarda en volver y cuánto pagás por pedirlo. Los precios salen del pricing de S3 y las duraciones mínimas de la tabla de clases de la documentación.
| Clase | USD/GB-mes | Duración mínima | Mínimo facturable | Retrieval |
|---|---|---|---|---|
| S3 Standard | 0,023 | — | — | — |
| Intelligent-Tiering | 0,023 / 0,0125 / 0,004 automáticos; 0,0036 y 0,00099 opt-in | — | 128 KB para monitoreo | sin cargo en los tiers automáticos; con cargo en los opt-in |
| Standard-IA | 0,0125 | 30 días | 128 KB | 0,01 por GB |
| One Zone-IA | 0,01 | 30 días | 128 KB | 0,01 por GB |
| Glacier Instant Retrieval | 0,004 | 90 días | 128 KB | 0,03 por GB |
| Glacier Flexible Retrieval | 0,0036 | 90 días | 40 KB de overhead | 0,03 expedited (1-5 min), 0,01 standard (3-5 h), sin cargo bulk (5-12 h) |
| Glacier Deep Archive | 0,00099 | 180 días | 40 KB de overhead | 0,02 standard (12 h), 0,0025 bulk (48 h) |
Intelligent-Tiering merece el asterisco completo. Los tres tiers automáticos —Frequent, Infrequent y Archive Instant Access— mueven el objeto solos según el acceso, sin cargo de recuperación ni de transición, y cobran USD 0,0025 por cada 1.000 objetos monitoreados. Con objetos grandes ese monitoreo es ruido; con millones de objetos chicos se come el ahorro entero. Los otros dos tiers, Archive Access (USD 0,0036) y Deep Archive Access (USD 0,00099), hay que activarlos explícitamente y sí cobran recuperación y latencia: la escalera automática termina en 0,004, no en 0,00099.
Deep Archive es 23 veces más barato que Standard por byte, y el costo por objeto decide si ese 23x llega a tu factura. AWS agrega 40 KB de overhead a cada objeto archivado — 8 KB de nombre y metadata a tarifa Standard, más 32 KB de índice a tarifa Glacier — y cobra una request por cada objeto que transiciona. Esa request no tiene un precio único: depende de la clase destino.
| Clase destino | USD por cada 1.000 requests de transición |
|---|---|
| Standard-IA, One Zone-IA, Intelligent-Tiering | 0,01 |
| Glacier Instant Retrieval | 0,02 |
| Glacier Flexible Retrieval | 0,03 |
| Glacier Deep Archive | 0,05 |
Con esas dos piezas —overhead por objeto y request por objeto— la amortización se calcula sola. Esta tabla compara Standard contra Deep Archive, en USD por cada millón de objetos, contando el overhead de 40 KB y la transición a USD 0,05 el millar:
| Tamaño del objeto | Ahorro mensual por millón de objetos | Transición por millón | Meses para amortizar |
|---|---|---|---|
| 128 KB | ~USD 2,50 | USD 50 | ~20 |
| 1 MB | ~USD 21 | USD 50 | ~2,4 |
| 10 MB | ~USD 210 | USD 50 | menos de 1 |
Debajo de unos 10 KB por objeto la cuenta se da vuelta del todo: el overhead de 40 KB hace que Deep Archive cueste más por mes que Standard, sin contar la transición. Por eso desde septiembre de 2024 S3 no transiciona por default objetos menores a 128 KB a ninguna clase. Podés forzarlo con el filtro ObjectSizeGreaterThan o el header x-amz-transition-default-minimum-object-size, pero el default está donde está por esta aritmética. Canva llegó al mismo lugar desde el otro extremo: para mover 130 petabytes priorizó buckets con tamaño promedio de objeto de 400 KB o más, y aun así la transición le costó 1,6 millones de dólares por única vez.
Dado el tamaño de objeto correcto, la magnitud cambia de escala. Cien TB parados en Standard cuestan USD 2.355 por mes; los mismos cien TB en Deep Archive cuestan USD 101. Son USD 27.048 al año de diferencia —(23,55 − 1,01) × 100 × 12— y la única acción necesaria es una lifecycle policy de veinte líneas. Ese es el número que hace que alguien apruebe el trabajo; el resto de esta sección es lo que evita que el trabajo salga a pérdida.
Dos detalles más. Borrar antes de la duración mínima cobra el remanente prorrateado, así que un lifecycle mal encadenado —Glacier Instant Retrieval a los 4 días y Deep Archive a los 20— es un cargo garantizado: la segunda transición tiene que esperar al menos 94 días. Y restaurar desde Glacier te cobra dos veces mientras dura la copia temporal, el archivo a tarifa Glacier y la copia a tarifa Standard.
La política que sale de todo esto, en Terraform:
resource "aws_s3_bucket_lifecycle_configuration" "comprobantes" {
bucket = aws_s3_bucket.comprobantes.id
rule {
id = "archivar-comprobantes"
status = "Enabled"
filter {
and {
prefix = "comprobantes/"
# 128 KB en bytes: debajo de esto la transición no se amortiza nunca
object_size_greater_than = 131072
}
}
transition {
days = 30
storage_class = "STANDARD_IA"
}
# 120 y no 90: Standard-IA cobra 30 días mínimos desde que el objeto llega.
transition {
days = 120
storage_class = "GLACIER_IR"
}
# 210 = 120 + 90, el mínimo de Glacier Instant Retrieval. Encadenar antes
# cobra el remanente prorrateado de la clase anterior.
transition {
days = 210
storage_class = "DEEP_ARCHIVE"
}
# los multipart incompletos se facturan como storage y no aparecen en un ls
abort_incomplete_multipart_upload {
days_after_initiation = 7
}
}
}
Qué compra el mismo presupuesto
Los USD 117,90 mensuales de la RDS Multi-AZ del primer ejemplo son un buen patrón de medida, porque son el piso: la instancia con 100 GB encendida y cero tráfico. Ese mismo presupuesto, gastado contra las otras dos máquinas, compra esto:
| Con ~USD 118 al mes tenés | Cantidad |
|---|---|
RDS db.t4g.medium Multi-AZ con 100 GB de gp3 | 1 instancia encendida, sin tráfico |
| Escrituras on-demand en DynamoDB de 1 KB | ~189 millones |
| Lecturas on-demand eventualmente consistentes de 4 KB | ~1.888 millones |
| Storage en S3 Standard | ~5,1 TB |
| Storage en Glacier Deep Archive | ~119 TB |
La tabla no dice que S3 sea mejor que RDS: dice que las tres unidades no son comparables entre sí y que la elección fija cuál de ellas va a dominar la factura. Las cifras de DynamoDB y S3 cuentan solo la línea nombrada; el storage de DynamoDB y las requests de S3 se suman aparte.
Elegir por patrón de acceso
El criterio es una sola pregunta: ¿conocés tus queries de antemano?
DynamoDB exige que sí. El hash te da acceso constante, pero solo si sabés la clave. Un Scan recorre la tabla entera y te cobra cada 4 KB leídos, hayas filtrado o no — el FilterExpression se aplica después de leer, así que un filtro que descarta el 99,9% de los items paga el 100% de las lecturas.
Esa frase tiene precio. Una tabla de 50 GB escaneada con lecturas eventualmente consistentes consume 6,55 millones de RRU: a USD 0,125 el millón, USD 0,82 por pasada. Un job que corre cada cinco minutos son 288 pasadas por día, USD 236 diarios, cerca de USD 7.100 al mes por un reporte que en una base relacional sería un GROUP BY sobre un índice. Los índices secundarios globales bajan ese número cuando la query es conocida, pero son tablas replicadas con su propio throughput y su propio costo. El camino para lo analítico es exportar la tabla a S3 y consultar con Athena.
RDS te deja cambiar de opinión. El planner arma el plan en tiempo de ejecución, los joins existen, un índice nuevo es un CREATE INDEX. Esa flexibilidad se paga en CPU y en que hay una sola máquina sirviendo escrituras.
S3 no tiene queries: te cobra por byte y por request, y la lógica de acceso la ponés vos arriba con Athena, un catálogo o un índice en otra base.
De ahí salen tres anti-patrones. Guardar blobs en columnas bytea o BLOB hace que esos bytes compitan por el buffer cache con las filas que sí se consultan, y encarece cada backup. Correr reportes ad-hoc contra DynamoDB paga la cuenta del Scan de arriba en un servicio que no sabe agregar. Y usar S3 como cola o como base de estado choca con que no hay transacciones ni locks entre objetos.
El camino hasta el dato también se cobra
S3 y DynamoDB son endpoints regionales, no recursos dentro de tu VPC. Una función o una instancia en subnet privada que los alcanza saliendo por un NAT gateway paga USD 0,045 por hora de gateway más USD 0,045 por GB procesado, según el pricing de VPC — procesamiento de datos sobre bytes que ya estás pagando como storage y como request.
El gateway endpoint resuelve exactamente eso y no tiene costo horario ni por GB: es una ruta en la tabla de ruteo de la subnet hacia el prefix list del servicio. Un job que sube 2 TB por mes a S3 por NAT paga cerca de USD 90 de procesamiento que desaparecen enteros al agregar el endpoint. Es la corrección de costo más barata de este artículo y la única que no exige tocar el código.
# ¿el tráfico a S3 sale por NAT? el gateway endpoint aparece en la route table
aws ec2 describe-vpc-endpoints \
--filters Name=vpc-endpoint-type,Values=Gateway \
--query 'VpcEndpoints[].[ServiceName,VpcId,State]'
Herramientas para verlo antes de que llegue la factura
| Herramienta | Para qué |
|---|---|
| AWS Pricing Calculator | Modelar el mismo caso como instancia RDS de 730 horas, tabla DynamoDB on-demand y bucket S3 con distribución por clase, en la misma pantalla, antes de escribir la primera línea de infra. |
| AWS Cost Explorer | La factura real desagregada por usage type y tag, con 12 meses de historia. La consola es gratis; la API cobra USD 0,01 por request paginado, conviene saberlo antes de automatizar. |
| Amazon S3 Storage Lens | Distribución por clase, tamaño promedio de objeto, buckets sin lifecycle y multipart uploads incompletos. El tamaño promedio de objeto es el número que decide si el lifecycle a Glacier ahorra o sale a pérdida. |
AWS CLI (aws s3api) | Aplicar y auditar desde la terminal: put-bucket-lifecycle-configuration con filtro de tamaño, list-objects-v2 con --query para contar por StorageClass, head-object para verificar dónde quedó cada objeto. |
| Infracost | El delta de costo del diff de Terraform comentado en el pull request, que es el único momento en que alguien todavía puede discutirlo. |
| Steampipe | El inventario de la cuenta consultado con SQL: buckets sin lifecycle, tablas provisioned con utilización baja, instancias RDS sin conexiones. Auditoría reproducible en vez de recorrido manual. |
Los cost allocation tags son el requisito previo de casi todo lo anterior: si no están activados en la cuenta, cada agrupación por tag cae entera en "sin etiquetar".
# tamaño real y modo de facturación de una tabla, sin abrir la consola
aws dynamodb describe-table --table-name pagos \
--query 'Table.[TableSizeBytes,ItemCount,BillingModeSummary.BillingMode]'
# las 15 líneas más caras del último mes por usage type: separa instancia-hora
# de storage y WRU de RRU, cosa que agrupar por servicio no hace
aws ce get-cost-and-usage \
--time-period Start=2026-07-01,End=2026-08-01 \
--granularity MONTHLY --metrics UnblendedCost \
--group-by Type=DIMENSION,Key=USAGE_TYPE \
--query 'sort_by(ResultsByTime[0].Groups, &to_number(Metrics.UnblendedCost.Amount))[-15:]'
Lo que queda
Cada máquina te cobra por lo que reserva. RDS reserva una instancia y te la cobra prendida. DynamoDB reserva particiones con techo propio y te cobra por kilobyte redondeado hacia arriba. S3 no reserva nada y te cobra por byte guardado, por request y por la clase donde lo dejaste.
Cuando la factura sorprende, casi siempre es porque el diseño asumió una unidad y el servicio cobra otra: storage aprovisionado que nadie usa, items que crecieron y multiplicaron las WRU, millones de objetos chicos que hacen que el lifecycle cueste más que el ahorro. El mecanismo estaba ahí desde el principio.
Cuatro preguntas antes de elegir, y las cuatro se contestan con un número:
- ¿Cómo vas a leer el dato? Por clave conocida, con queries que todavía no existen, o entero de una pasada. La primera respuesta es DynamoDB, la segunda RDS, la tercera S3.
- ¿Cuántas horas al día está ocioso? Si la respuesta pasa de 16, estás pagando una instancia prendida para nada y el modo de cobro por operación gana solo por eso.
- ¿El volumen crece con el tiempo o con los usuarios? Lo que crece con el tiempo y nunca se borra pide lifecycle desde el día uno. Lo que crece con los usuarios pide revisar el techo por partición.
- ¿Cuánto sale a 10x del volumen actual? Multiplicá cada línea y mirá cuál explota primero. Esa línea es tu unidad, y es la que vas a estar optimizando dentro de dos años.
Keep going
Reading
- Understanding and managing Amazon S3 storage classes — La tabla canónica de durabilidad, AZs, duración mínima y tamaño mínimo facturable por clase: sin ella no hay forma de justificar dónde está el punto de corte.
- Transitioning objects using Amazon S3 Lifecycle — Acá están el overhead de 40 KB por objeto archivado, el cargo prorrateado por borrado anticipado y el motivo del default de 128 KB.
- DynamoDB throughput capacity (on-demand vs provisioned) — La definición oficial de los dos modos y de las consideraciones al cambiar de uno al otro, que es la decisión operativa real.
- Choosing an AWS database service (decision guide) — Guía oficial que ordena las opciones por modelo de dato y patrón de acceso, con un ejemplo de e-commerce que combina varias bases.
- How Canva saves millions annually in Amazon S3 costs — El caso público con cifras: 1,6 millones de USD de costo único en requests de transición y el criterio de mover solo buckets con objetos de 400 KB o más.
- How you should think about DynamoDB costs — El mejor modelo mental de las tres dimensiones que factura DynamoDB, escrito antes de la baja de tarifas de noviembre de 2024: útil para el método, no para los números.
Videos
- AWS re:Invent 2023 - AWS storage cost-optimization best practices (STG202) — Cómo leer los patrones de acceso reales antes de mover datos entre clases, y cómo no pagar de más en transiciones.
- AWS re:Invent 2024 - Advanced data modeling with Amazon DynamoDB (DAT404) — Nivel 400 sobre modelar partiendo del patrón de acceso, que es la contracara de diseño del techo de throughput por partición.
- AWS re:Invent 2025 - Maximize the value of cold data with Amazon S3 Glacier storage classes (STG208) — Las tres clases Glacier en detalle: costos de recuperación, duraciones mínimas y qué se rompe si el dato se accede más de lo previsto.