El Device Shadow guarda el estado actual de cada dispositivo (parte 5), pero un sistema IoT necesita guardar más cosas: qué dispositivos tiene cada casa, cómo se llaman, y las lecturas de los sensores a lo largo del tiempo. En AWS, la opción serverless más común para eso es DynamoDB.
Si vienes de bases de datos relacionales, DynamoDB obliga a pensar al revés: primero las consultas, después las tablas. En esta parte lo aplicamos a un proyecto de ejemplo.
Lo mínimo que hay que saber
- Cada tabla tiene una clave de partición (partition key) y, opcionalmente, una clave de ordenación (sort key).
- Los items con la misma clave de partición se guardan juntos y se leen juntos con un
Query, ordenados por la clave de ordenación. - Un
Queryes barato y rápido. UnScanrecorre toda la tabla: evítalo en el camino normal de la app. - No hay joins. Si dos cosas se consultan juntas, deben estar en la misma partición o duplicarse.
Paso 1: escribir las consultas
En el proyecto de ejemplo, la app necesita:
- Los dispositivos de una casa, al abrir la app.
- Los datos de un dispositivo, al abrir su pantalla.
- Las lecturas de un sensor en un rango de tiempo, para una gráfica.
Paso 2: la tabla de dispositivos
Las consultas 1 y 2 se resuelven con una tabla donde todo lo de una casa comparte la clave de partición:
| Clave | Atributo | Ejemplo |
|---|---|---|
| Partition key | casaId | casa-demo |
| Sort key | dispositivoId | luz-sala |
{ "casaId": "casa-demo", "dispositivoId": "luz-sala", "tipo": "luz", "nombre": "Sala" }
{ "casaId": "casa-demo", "dispositivoId": "luz-cocina", "tipo": "luz", "nombre": "Cocina" }
{ "casaId": "casa-demo", "dispositivoId": "sensor-patio", "tipo": "sensor", "nombre": "Patio" }
- Consulta 1:
QueryconcasaId = casa-demodevuelve todos los dispositivos en una sola solicitud. - Consulta 2:
GetItemconcasaIdydispositivoIddevuelve uno.
Un item por dispositivo es mejor que un solo item por casa con un arreglo dentro: modificar un dispositivo no obliga a reescribir los demás, y no te acercas al límite de 400 KB por item.
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";
const db = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const { Items } = await db.send(
new QueryCommand({
TableName: "dispositivos",
KeyConditionExpression: "casaId = :c",
ExpressionAttributeValues: { ":c": "casa-demo" },
})
);
Con pocos dispositivos por casa, el resultado cabe en una página. Si una partición puede devolver más de 1 MB, hay que paginar con LastEvaluatedKey.
Paso 3: la tabla de lecturas
Las lecturas de sensores son otra cosa: muchas, pequeñas, y se consultan por rango de tiempo. La clave encaja así:
| Clave | Atributo | Ejemplo |
|---|---|---|
| Partition key | dispositivoId | sensor-patio |
| Sort key | ts (marca de tiempo) | 2026-10-05T08:30:00Z |
// Lecturas del sensor en las últimas 24 horas
const desde = new Date(Date.now() - 24 * 3600 * 1000).toISOString();
const { Items } = await db.send(
new QueryCommand({
TableName: "lecturas",
KeyConditionExpression: "dispositivoId = :d AND ts >= :desde",
ExpressionAttributeValues: { ":d": "sensor-patio", ":desde": desde },
})
);
Dos detalles importantes:
- Usa fechas ISO 8601 o números para la sort key: así el orden alfabético coincide con el temporal.
- Activa TTL (Time To Live) con un atributo como
expira(en segundos Unix). DynamoDB borra solo y sin costo las lecturas viejas, y la tabla no crece sin fin.
Las lecturas pueden llegar a la tabla sin escribir código: una regla de AWS IoT las toma del tópico MQTT y las inserta (parte 14).
¿Una tabla o varias?
El diseño de tabla única (single-table design) mete todo en una tabla con claves genéricas (PK, SK) y prefijos como CASA# o DISP#. Ahorra consultas cuando hay muchas relaciones, pero es más difícil de leer y de cambiar.
Para un proyecto pequeño, una tabla por tipo de dato con claves claras suele ser más fácil de mantener. Lo importante no es el número de tablas, sino que cada consulta de la app sea un Query o un GetItem.
Índices: cuando necesitas buscar por otro campo
Si necesitas buscar por un atributo que no es la clave (por ejemplo, encontrar un dispositivo por su número de serie), crea un índice secundario global (GSI) con ese atributo como clave.
Un truco útil: un GSI solo incluye los items que tienen su atributo clave. Si solo algunos items lo tienen, el índice queda pequeño. Se llama índice disperso (sparse index) y es ideal para buscar "los pocos items que cumplen algo", como dispositivos pendientes de activar.
Escrituras seguras
- Contadores atómicos:
UpdateExpression: "ADD lecturas :uno"incrementa sin leer antes, aunque haya escrituras simultáneas. - Escrituras condicionales:
ConditionExpression: "attribute_not_exists(dispositivoId)"evita sobrescribir un dispositivo que ya existe. - Transacciones:
TransactWriteItemscuando dos escrituras deben ocurrir juntas o ninguna.
Quién puede leer qué
Si la tabla solo se lee desde funciones Lambda, controlas el acceso en el código. Si la app lee directamente con credenciales temporales de Cognito, IAM puede limitar cada usuario a sus propias particiones con la condición dynamodb:LeadingKeys. En cualquier caso, nunca uses un identificador que envía el cliente para decidir qué datos devolver: sácalo de un token verificado (parte 11).
Cuánto cuesta
En modo bajo demanda pagas por lectura, escritura y almacenamiento, sin capacidad reservada. Para una casa con unos pocos dispositivos y lecturas cada minuto, son centavos al mes, y con TTL el almacenamiento no crece. La estimación completa está en la parte 22.
Errores comunes
- Usar
Scanpara listar. Funciona con 10 items y se vuelve lento y caro con 10.000. - Una clave de partición "caliente". Si todos los sensores escriben con la misma clave (por ejemplo, la fecha del día), se concentran en una partición. Reparte por dispositivo.
- Items que crecen sin límite. Un arreglo de lecturas dentro de un item termina chocando con los 400 KB.
- Olvidar la paginación. Un
Querydevuelve como máximo 1 MB por llamada.
Preguntas frecuentes
¿DynamoDB o Timestream para lecturas de sensores?
Para pocas lecturas y gráficas simples, DynamoDB con TTL basta y es más barato de empezar. Para análisis de series temporales a gran escala (agregaciones, ventanas de tiempo), una base de datos de series temporales es más cómoda.
¿Puedo cambiar la clave de una tabla después?
No. La clave se fija al crear la tabla. Para cambiarla hay que crear otra y migrar los datos. Por eso conviene escribir las consultas antes.
¿Necesito guardar el estado del dispositivo en DynamoDB?
No, el estado actual ya está en el Device Shadow. DynamoDB es para lo que el Shadow no guarda: nombres, configuración e historial.