Tema 3 · 10 · Ejercicio adicional C: estaciones de bicicleta en DynamoDB (clave compuesta)
Presentación del Tema 3: «Ejercicio C» de «Ejercicios adicionales» (DynamoDB con clave de partición + clave de ordenación). Duración: unos 20 minutos, más ~9 minutos de espera si creas el índice secundario global. Coste: céntimos (tabla on-demand de 600 ítems pequeños).
¿Dudas con un script? Todos los scripts de esta carpeta traen su propia ayuda:
python3 genera-estaciones.py --ayuda(también vale--helpo-h) explica los pasos y las opciones. EsteREADME.mdes el paso a paso de la actividad: tenlo a mano y consúltalo antes de preguntar.
Etiquetas de lo que creas. Todo recurso que crees a mano (máquina, volumen, bucket, base de datos, tabla, función...) ponle estas etiquetas, al crearlo (apartado Tags / Etiquetas) o después desde su pestaña de etiquetas:
curso=G214,universidad=CUNEFyactividad=03.10-estacionesdynamodb. Si algún día hay que borrar recursos a mano, se localizan todos de golpe conaws resourcegroupstaggingapi get-resources --region eu-north-1 --tag-filters Key=actividad,Values=03.10-estacionesdynamodb. Más detalle (cómo etiquetar con la CLI, cómo buscar y qué hacer en una emergencia): README principal, sección «Etiquetas».
Qué vas a hacer
Vas a generar 600 lecturas simuladas de estaciones de bicicleta pública, cargarlas en una tabla de DynamoDB con clave compuesta (estacion_id + ts) y comprobar con --return-consumed-capacity la diferencia de coste entre query (lee solo lo que pides) y scan (lee toda la tabla). Al final creas un índice secundario global para resolver la pregunta «¿qué estaciones no tienen bicis ahora mismo?».
genera-estaciones.py ──> batch_estaciones_00.json … _23.json (24 ficheros de 25 ítems)
aws dynamodb batch-write-item ──> tabla Estaciones (estacion_id HASH, ts RANGE) ──> query / scan / índice estado-ts-index
Qué se crea en AWS: la tabla Estaciones (modo PAY_PER_REQUEST) y, si haces la última parte, el índice secundario global estado-ts-index. Todo se hace en CloudShell (región eu-north-1). Las lecturas reales de estaciones en tiempo real no son de descarga libre (Barcelona exige un token y Madrid solo publica información de las estaciones), por eso se generan simuladas.
Qué contiene esta carpeta
| Fichero | Para qué sirve | En qué paso se usa |
|---|---|---|
genera-estaciones.py |
Genera 600 lecturas de estaciones de bicicleta (20 estaciones E01..E20 × 30 lecturas, una cada 5 minutos desde las 17:00 del 2026-03-14) con bicis_libres, anclajes_libres y estado (NORMAL, SIN_BICIS o LLENA), en ficheros batch_estaciones_00.json... de 25 ítems (el máximo de batch-write-item; con 30 ítems en una llamada da Member must have length less than or equal to 25). Solo usa la librería estándar |
Paso 2 |
README.md |
Esta guía (incluye los comandos de la AWS CLI) | Siempre |
Antes de empezar
- [ ] Cuenta de AWS propia, región de la consola
eu-north-1(elígela ANTES de abrir CloudShell). - [ ] Sabes abrir AWS CloudShell (icono
>_). Esta actividad no necesita la RDS ni Adminer. - [ ] Idea previa: la presentación (DynamoDB, ver 03.07): los números viajan como cadenas dentro de
{"N": "..."}.
Comprobación previa en CloudShell: aws sts get-caller-identity y echo "$AWS_REGION" (debe imprimir eu-north-1; si no, cierra CloudShell, cambia la región de la consola y ábrelo de nuevo).
Cómo llevar los ficheros a donde toque
Se hace en CloudShell. Comprime esta carpeta (03.10-estacionesdynamodb) en un zip, abre CloudShell en eu-north-1, Actions > Upload file y sube el zip. Después:
unzip -o 03.10-estacionesdynamodb.zip && cd 03.10-estacionesdynamodb && chmod -R u+rwX .
sed -i 's/\r$//' *.py
chmod +x *.py
ls
Si ya subiste
g214-materiales.zip(README principal), no necesitas este zip: entra concd ~/g214-materiales/tema3/03.10-estacionesdynamodby, si algún fichero daPermission denied, ejecuta allíchmod -R u+rwX . && chmod +x *.py.
Qué verás: ls muestra README.md y genera-estaciones.py. (sed quita los finales de línea de Windows y chmod +x da permiso de ejecución.) Ejecuta todo desde dentro de esta carpeta. También puedes ejecutarlo en tu PC con la AWS CLI configurada; en PowerShell, ver el paso 3.
Paso a paso
Paso 1. Crear la tabla Estaciones. Con el comando de la presentación: create-table con estacion_id como clave de partición (HASH), ts como clave de ordenación (RANGE) y PAY_PER_REQUEST. Equivalente en CLI (sin probar tal cual es la referencia):
aws dynamodb create-table --table-name Estaciones \
--attribute-definitions AttributeName=estacion_id,AttributeType=S AttributeName=ts,AttributeType=S \
--key-schema AttributeName=estacion_id,KeyType=HASH AttributeName=ts,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST --region eu-north-1
aws dynamodb wait table-exists --table-name Estaciones --region eu-north-1
Paso 2. Generar los datos.
python3 genera-estaciones.py # 600 ítems en 24 ficheros (o: genera-estaciones.py 6 5 -> 30 ítems en 2 ficheros)
ls batch_estaciones_*.json
Qué verás: 24 ficheros batch_estaciones_00.json ... batch_estaciones_23.json (cada uno con 25 ítems).
Paso 3. Cargar los lotes y comprobar.
for f in batch_estaciones_*.json; do
aws dynamodb batch-write-item --request-items file://$f --region eu-north-1
done
aws dynamodb scan --table-name Estaciones --select COUNT --region eu-north-1
Qué verás: cada batch-write-item imprime un JSON de tres líneas ({, "UnprocessedItems": {} y }; si UnprocessedItems no estuviera vacío, reenvía esos ítems) y el scan --select COUNT da "Count": 600. En PowerShell sustituye el bucle por Get-ChildItem batch_estaciones_*.json | ForEach-Object { aws dynamodb batch-write-item --request-items file://$($_.Name) --region eu-north-1 } (o usa CloudShell: las comillas simples de los JSON dan problemas en Windows PowerShell 5.1).
Paso 4. query frente a scan. Con los comandos de la presentación (con --return-consumed-capacity TOTAL). Resultados para contrastar (medidos): el query de E12 desde las 18:00 devuelve 18 ítems (Count 18, ScannedCount 18) y consume 0.5; todas las lecturas de E12, 30 ítems, también 0.5; el scan con --filter-expression "estacion_id = :e" devuelve 30 pero lee 600 (ScannedCount 600) y consume 6.5 (13.0 con --consistent-read). Con solo 30 ítems en la tabla ya se ve la diferencia (query 0.5 frente a scan 2.0).
Paso 5. Índice secundario global para «estaciones sin bicis ahora mismo». Hace falta un índice (estado + ts): crearlo con 600 ítems tarda unos 9 minutos hasta IndexStatus ACTIVE, y después una query con --index-name estado-ts-index lo resuelve con 0.5. Comandos (los mismos que se midieron; en una tabla On-demand el índice no necesita capacidad aparte). Acota ts a una ventana reciente: sin esa condición salen todas las lecturas históricas con SIN_BICIS (28 en total), no las de «ahora»:
aws dynamodb update-table --table-name Estaciones --region eu-north-1 --attribute-definitions AttributeName=estado,AttributeType=S AttributeName=ts,AttributeType=S --global-secondary-index-updates '[{"Create":{"IndexName":"estado-ts-index","KeySchema":[{"AttributeName":"estado","KeyType":"HASH"},{"AttributeName":"ts","KeyType":"RANGE"}],"Projection":{"ProjectionType":"ALL"}}}]'
aws dynamodb describe-table --table-name Estaciones --region eu-north-1 --query "Table.GlobalSecondaryIndexes[0].IndexStatus" --output text # espera a ACTIVE (~9 min)
aws dynamodb query --table-name Estaciones --index-name estado-ts-index --region eu-north-1 --key-condition-expression "estado = :s AND ts >= :d" --expression-attribute-values '{":s":{"S":"SIN_BICIS"},":d":{"S":"2026-03-14T19:25:00"}}' --return-consumed-capacity TOTAL
Qué verás: con ts >= 19:25 sale 1 ítem y 0.5 RCU (el scan equivalente lee los 600 y cuesta 6.5).
Errores frecuentes
| Síntoma | Causa | Solución |
|---|---|---|
Member must have length less than or equal to 25 |
Un batch-write-item con más de 25 ítems |
Usa los ficheros que genera el script (25 ítems cada uno) |
ResourceNotFoundException al cargar o consultar |
La tabla no existe todavía, o está en otra región | aws dynamodb wait table-exists --table-name Estaciones --region eu-north-1 y comprueba --region |
UnprocessedItems no está vacío |
DynamoDB no procesó algún ítem | Reenvía esos ítems |
Permission denied al lanzar ./genera-estaciones.py |
Sin permiso de ejecución | chmod +x genera-estaciones.py (o python3 genera-estaciones.py) |
bad interpreter o $'\r': command not found |
Finales de línea de Windows | sed -i 's/\r$//' *.py y repite |
| En PowerShell 5.1 los JSON con comillas simples dan error | Las comillas simples se interpretan distinto | Usa CloudShell |
El scan --select COUNT da menos de 600 |
Faltan lotes por cargar | Repite el bucle (los put repetidos no duplican: misma clave) |
La query al índice no devuelve nada |
El índice aún no está ACTIVE, o la ventana de ts no contiene ninguna lectura SIN_BICIS |
Espera a ACTIVE (~9 min) y usa ts >= 2026-03-14T19:25:00 |
Las filas de esta tabla distintas de la primera y de la de PowerShell son deducciones, sin probar.
Limpieza
aws dynamodb delete-table --table-name Estaciones --region eu-north-1
rm batch_estaciones_*.json
El borrado de la tabla se lleva también el índice. Comprueba que ya no está:
aws dynamodb list-tables --region eu-north-1
Qué debes ver: la lista sin Estaciones. Esta actividad no comparte recursos con las demás (la tabla Usuarios es de 03.07); la última carpeta que borra la RDS y Adminer es la 03.09.
Cómo sabes que has terminado
- [ ]
Estacionestiene 600 ítems (Count600). - [ ] El
queryde E12 consume 0.5 frente a 6.5 delscancon filtro. - [ ] (Opcional) El índice
estado-ts-indexestáACTIVEy laqueryporSIN_BICISdesde las 19:25 devuelve 1 ítem con 0.5. - [ ] Has borrado la tabla
Estacionesy los ficherosbatch_estaciones_*.json.
Anterior: 03.09 · Ejercicios adicionales A y B · Siguiente: 03.11 · Ejercicio D (CSV a Parquet)