# Tema 3 · 7 · DynamoDB: tabla `Usuarios`, CLI, Python y exportar/importar con S3 Presentación del Tema 3: «Creación de base de datos DynamoDB», «Acceso desde AWS CLI», «Ejemplo aplicaciones uso DynamoDB – Ejercicio 1» (Python: `dynamodb-cli.py`) y «Ejercicio 3» (exportar e importar con S3). Duración: unos 10 minutos con `dynamodb-cli.py` y unos 10 minutos más (casi todo espera) con S3: ~2,5 min la exportación y ~3 min la importación, incluso con 7 ítems (medido). Coste: con tablas pequeñas, céntimos; PITR cuesta 0,21 USD por GB-mes mientras esté activo. > **¿Dudas con un script?** Todos los scripts de esta carpeta traen su propia ayuda: `python3 dynamodb-cli.py --ayuda` (también vale `--help` o `-h`) explica los pasos y las opciones. Este `README.md` es 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` = `CUNEF` y `actividad` = `03.07-dynamodbcli`. Si algún día hay que borrar recursos a mano, se localizan todos de golpe con `aws resourcegroupstaggingapi get-resources --region eu-north-1 --tag-filters Key=actividad,Values=03.07-dynamodbcli`. Más detalle (cómo etiquetar con la CLI, cómo buscar y qué hacer en una emergencia): [README principal](../../README.md), sección «Etiquetas». ## Qué vas a hacer Vas a trabajar con una base **no relacional**, Amazon DynamoDB: crear la tabla `Usuarios` (consola o CLI), leer y escribir ítems con la AWS CLI y con un script de Python (`boto3`) desde CloudShell, y por último exportar la tabla a S3 y reimportarla en una tabla nueva. ``` CloudShell ──aws dynamodb ... / dynamodb-cli.py──> tabla Usuarios (UserID, String) tabla Usuarios ──export (PITR)──> S3 (AWSDynamoDB//data/*.json.gz) ──import──> tabla nueva ``` **Qué se crea en AWS:** la tabla `Usuarios` (modo on-demand), opcionalmente un bucket de S3, la exportación a S3 (carpeta `AWSDynamoDB/`), una tabla nueva al importar y un grupo de logs `/aws-dynamodb/imports`. `dynamodb-cli.py` en sí no crea nada: lee o escribe un ítem en una tabla que ya existe. ## Qué contiene esta carpeta | Fichero | Para qué sirve | En qué paso se usa | |---|---|---| | `dynamodb-cli.py` | Script de Python con `boto3` para leer o escribir UN ítem en DynamoDB desde la terminal | Pasos 4 a 6 | | `README.md` | Esta guía (incluye los comandos de la AWS CLI y de la exportación/importación) | Siempre | ## Antes de empezar - [ ] Cuenta de AWS propia, región de la consola `eu-north-1` elegida ANTES de abrir CloudShell (CloudShell trabaja en la región de la consola). - [ ] Sabes abrir AWS CloudShell (el icono `>_` de la barra superior): terminal Linux en el navegador con la AWS CLI, `unzip`, `nano` y Python 3. - [ ] La tabla `Usuarios` con clave de partición `UserID` (String) en `eu-north-1` (el paso 1 la crea por CLI). Esta actividad **no** necesita la RDS ni Adminer. Comprobación previa en CloudShell: ```bash aws sts get-caller-identity echo "$AWS_REGION" ``` Qué verás: un JSON con tu cuenta y `eu-north-1`. Si imprime otra región, cierra CloudShell, cambia la región de la consola a Estocolmo y ábrelo de nuevo. ## Cómo llevar los ficheros a donde toque Se hace en **CloudShell**, porque necesita credenciales de AWS. 1. En tu PC, comprime **esta carpeta** (clic derecho sobre `03.07-dynamodbcli` > *Comprimir en archivo ZIP*, o *Enviar a > Carpeta comprimida en zip* en Windows 10). Obtienes `03.07-dynamodbcli.zip`. 2. En la consola de AWS (región `eu-north-1`) abre CloudShell, pulsa **Actions** > **Upload file** y sube el zip. 3. Descomprime y deja el script listo: ```bash unzip -o 03.07-dynamodbcli.zip && cd 03.07-dynamodbcli && 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 con `cd ~/g214-materiales/tema3/03.07-dynamodbcli` y, si algún fichero da `Permission denied`, ejecuta allí `chmod -R u+rwX . && chmod +x *.py`. `sed` quita los finales de línea de Windows (si no, bash falla con `bad interpreter` o `$'\r': command not found`) y `chmod +x` da permiso de ejecución (un zip creado en Windows no lo conserva; sin él, `./dynamodb-cli.py` da `Permission denied`). Qué verás: `ls` muestra `README.md` y `dynamodb-cli.py`. Alternativa para subir solo el script: **Actions** > **Upload file** y elige `dynamodb-cli.py`, y luego `sed -i 's/\r$//' dynamodb-cli.py && chmod +x dynamodb-cli.py`. Si cierras CloudShell, al volver entra con `cd 03.07-dynamodbcli` (si no encuentras la carpeta, vuelve a subir el zip). ## Paso a paso **Paso 1. Crear la tabla `Usuarios`.** En la presentación se crea desde la consola. El mismo resultado por CLI, en CloudShell: ```bash aws dynamodb create-table --table-name Usuarios \ --attribute-definitions AttributeName=UserID,AttributeType=S \ --key-schema AttributeName=UserID,KeyType=HASH \ --billing-mode PAY_PER_REQUEST --region eu-north-1 aws dynamodb wait table-exists --table-name Usuarios --region eu-north-1 ``` Qué verás: `TableStatus: CREATING` y, tras `wait` (unos 25 s), la tabla `ACTIVE` en modo `PAY_PER_REQUEST` (on-demand). La consola, con *Use default settings*, puede elegir otro modo de capacidad; para el laboratorio da igual. Los tres ítems de la presentación (u001 a u003) se añaden con `put-item` o desde la consola. **Modo de capacidad:** *aprovisionado* (reservas lecturas y escrituras por segundo y las pagas aunque no las uses) o *bajo demanda*, `PAY_PER_REQUEST` (pagas solo por petición); para el laboratorio vale cualquiera. **Paso 2. Búsqueda en la consola.** Resultado esperado del `Scan` con filtro `UserID = u001` sobre los tres ítems: `Items returned` 1, `Items scanned` 3, `RCUs consumed` 2 y `Efficiency` 33,33 %; con `Query` solo se lee el ítem pedido. **Paso 3. Comandos `aws dynamodb ...` de la presentación.** Resultados medidos: `get-item` de un ítem pequeño consume **0.5** unidades de lectura (1.0 con `--consistent-read`); `query` por clave, **0.5**; `scan` de la tabla, **2.0** (también con 3 ó 7 ítems: cuesta 0,5 por cada partición de la tabla) y **sin cambio** con `--projection-expression` (la proyección reduce los datos devueltos, no el consumo). Límites que conviene saber: un ítem pesa como máximo 400 KB y `batch-write-item` admite 25 ítems por llamada. Dos consejos, porque esos comandos no llevan `--region`: - Usan la región de tu CloudShell. Si tu tabla está en `eu-north-1` y CloudShell en otra, `list-tables` saldrá vacío o dará tabla no encontrada: comprueba `echo "$AWS_REGION"` o añade `--region eu-north-1`. - Para contar los ítems de una tabla no te fíes del contador del resumen de la consola (se actualiza cada varias horas; da 0 al principio). Cuenta con `aws dynamodb scan --table-name Usuarios --select COUNT --region eu-north-1`: el campo `Count` de la respuesta es el número de ítems. **Paso 4. Comprobar `boto3`.** ```bash python3 -c "import boto3; print(boto3.__version__)" ``` Qué verás: un número de versión. Si sale `ModuleNotFoundError: No module named 'boto3'`, instálalo con `pip3 install --user boto3`. Si en una máquina Amazon Linux 2023 limpia el sistema responde `pip3: command not found`, instala pip primero (`sudo dnf install -y python3-pip`) y repite `pip3 install --user boto3`. Un aviso `PythonDeprecationWarning` sobre Python 3.9 es normal y no impide que el script funcione (`dynamodb-cli.py` ya lo silencia). La presentación solo dicen «sube el fichero, `chmod +x` y ejecuta»: si falla con `Falta la libreria boto3`, es esto. En CloudShell no suele hacer falta nada de esto (no se ha podido comprobar en un CloudShell real: si algo falla, avisa). Opciones de `dynamodb-cli.py`: | Opción | Qué hace | Por defecto | |---|---|---| | `--mode get` | Lee un ítem por `UserID` (no interactivo) | Sin `--mode`, el script pregunta qué hacer | | `--mode put` | Crea o actualiza un ítem. Aun así te pregunta los datos por teclado | | | `--userid U` | Valor de la clave a consultar en modo `get` (se llama `--userid` aunque tu clave tenga otro nombre) | `u001`, que solo vale con clave de texto: con `--key-type Number` indica siempre el valor | | `--table T` | Nombre de la tabla (distingue mayúsculas) | `Usuarios` | | `--region R` | Región de la tabla | `eu-north-1` (no la de CloudShell) | | `--key-name N` | Nombre de la clave de partición de la tabla (distingue mayúsculas) | `UserID` | | `--key-type String\|Number` | Tipo de esa clave | `String` | | `--quiet` | Quita los mensajes `[INFO]` | Con mensajes | | `-h`, `--help` | Muestra ayuda y «Uso rápido» | | **Paso 5. Leer un ítem.** ```bash ./dynamodb-cli.py --mode get --userid u001 ``` Qué verás (en la salida real, las líneas de resultado llevan un icono delante y el orden de los atributos puede variar): ``` [INFO] Inicializando recurso DynamoDB en región eu-north-1… [INFO] Consultando tabla 'Usuarios' con ConsistentRead=True… [INFO] Key = {'UserID': 'u001'} Se encontró el usuario con UserID='u001': { "UserID": "u001", "Nombre": "Elena", ... } ``` Si el `UserID` no existe: `Conexión correcta, pero no existe UserID='...'.` (código de salida 1). Si hay un error de AWS: `Error al conectar o consultar DynamoDB:` y el mensaje de AWS (código 2). Si no hay credenciales (por ejemplo, lo ejecutas en tu PC sin `aws configure`), el script dice `No hay credenciales de AWS validas para este script` y cómo arreglarlo (código 2). El código de salida se ve con `echo $?` justo después. Añade `--quiet` para ocultar las líneas `[INFO]`. Sin argumentos, `./dynamodb-cli.py` pregunta: `Seleccione modo:` con `1) GET` y `2) PUT` (`Opción [1/2, ENTER=1]`); con GET pide `UserID a consultar [ENTER = u001]`. Con Enter en ambas preguntas lee `u001`. Con una tabla de clave numérica (por ejemplo `users` con `id` numérica, la de la Práctica 2) hay que decírselo; si no, DynamoDB responde `The provided key element does not match the schema`: ```bash ./dynamodb-cli.py --mode get --userid 1002 --table users --key-name id --key-type Number ``` Qué verás: igual que arriba, con `[INFO] Key = {'id': 1002}` y `Se encontró el usuario con id='1002':` seguido del ítem. Si escribes algo que no es un número en `--userid` con `--key-type Number`, el script responde `Clave no valida para el tipo Number: No es un número válido.` (código de salida 2). **Paso 6. Escribir un ítem.** ```bash ./dynamodb-cli.py --mode put ``` El script te guía: primero la clave (`UserID` por defecto; obligatoria y de tipo String, salvo que uses `--key-type Number`) y luego atributos, de uno en uno, con su nombre, su tipo (`String`, `Number` o `Boolean`) y su valor. Un Enter en `Nombre atributo` termina. Ejemplo con un usuario de prueba `u900` (no es ninguno de los que te pide la presentación): ``` UserID (obligatorio, String): u900 Nombre atributo (ENTER para terminar): Nombre Tipo [String/Number/Boolean]: String Valor para 'Nombre' (String): Prueba Nombre atributo (ENTER para terminar): Edad Tipo [String/Number/Boolean]: Number Valor para 'Edad' (Number): 30 Nombre atributo (ENTER para terminar): ``` Qué verás al terminar: `Petición que se va a ejecutar (CLI):` seguido del comando equivalente de la AWS CLI, por ejemplo: ``` aws dynamodb put-item --table-name Usuarios --region eu-north-1 --item '{"UserID": {"S": "u900"}, "Nombre": {"S": "Prueba"}, "Edad": {"N": "30"}}' ``` y después `[INFO]` con `Enviando put_item a 'Usuarios'…` y `Item creado/actualizado correctamente.` Fíjate en cómo viaja cada tipo (`S`, `N`, `BOOL`): es lo que explica la presentación. Reglas del script: - Cada ejecución crea o actualiza UN solo ítem. Para varios usuarios, ejecútalo varias veces. - Si el `UserID` ya existe, `put_item` **reemplaza el ítem entero**: los atributos que no vuelvas a escribir se pierden (no se fusionan). - El tipo hay que escribirlo tal cual: `String`, `Number` o `Boolean` (da igual mayúsculas o minúsculas). `Number` usa punto decimal (`1.5`, no `1,5`). `Boolean` acepta `true`, `false`, `1`, `0`, `si`, `sí`, `no`, `y`, `n`. Un `String` no puede estar vacío. No se puede repetir el nombre de la clave como atributo. - Otra tabla o región: `./dynamodb-cli.py --mode get --userid u001 --table --region eu-north-1`. Si la clave de partición de esa tabla no es `UserID` de tipo String, añade `--key-name ` y `--key-type String` o `Number` según su tipo. **Paso 7. Comprobar con la AWS CLI y borrar la prueba.** ```bash aws dynamodb get-item --table-name Usuarios --key '{"UserID":{"S":"u900"}}' --region eu-north-1 aws dynamodb delete-item --table-name Usuarios --key '{"UserID":{"S":"u900"}}' --region eu-north-1 ``` Qué verás: el primer comando devuelve un JSON con `Item` y cada valor con su tipo; el segundo no imprime nada. Si solo querías probar el script, borra así tu ítem de prueba. También puedes verlo en la consola de DynamoDB (tabla > **Explore table items**) o en el visor de [03.08](../03.08-dynamodbvisor/README.md). **Paso 8. Exportar e importar con S3 (ejercicio 3 de DynamoDB).** Se hace en la consola (la presentación tiene el paso a paso); aquí tienes lo que no cuentan las diapositivas. En la presentación, el ejercicio 2 (visor web, actividad [03.08](../03.08-dynamodbvisor/README.md)) va antes que este: si quieres seguir ese orden exacto, haz 03.08 antes de este paso (si no, el `Count` será 7 en vez de 8; ver «Comprobar»). - **Bucket**: necesitas uno en la misma región que la tabla (`eu-north-1`). Si no lo tienes, créalo (por ejemplo `aws s3 mb s3:// --region eu-north-1`). - **PITR**: la exportación completa necesita *Point-in-time recovery* activado en la tabla (pestaña *Backups*); sin él, la consola o la CLI responden `PointInTimeRecoveryUnavailableException`. Activarlo tarda segundos, **cuesta 0,21 USD por GB-mes** y queda activo mientras exista la tabla (o hasta desactivarlo, ver Limpieza). - **Tiempos medidos**: la exportación tarda unos 2,5 minutos y la importación unos 3, incluso con 7 ítems. - **Resultado**: en el bucket aparece `AWSDynamoDB//data/` con **un fichero `.json.gz` por partición de la tabla** (en una tabla pequeña son 4, y alguno está vacío; no es un error) más los ficheros `manifest-*`. La importación (carpeta `data/`, compresión `GZIP`, formato `DynamoDB JSON`, partition key `UserID` de tipo String) crea una tabla **nueva** con los mismos ítems; importar sobre una tabla existente da `Table already exists`. - **Comprobar**: `aws dynamodb scan --table-name --select COUNT --region eu-north-1` debe dar el mismo `Count` que la original en ese momento: compáralo con `aws dynamodb scan --table-name Usuarios --select COUNT --region eu-north-1`. Si has seguido las diapositivas en orden son **8 ítems**: 3 (u001 a u003) + 1 (u020) + 3 (u010 a u012) + 1 (el registro que añades en el ejercicio 2, con el visor de [03.08](../03.08-dynamodbvisor/README.md)) = 8; serían 7 si no añadiste el del ejercicio 2. No es una pérdida de datos de la importación si coincide con el `Count` de `Usuarios`. No uses el `ItemCount` de la consola: tarda horas en actualizarse (da 0 al principio). - **Por CLI** (si prefieres no usar la consola; sustituye `` y ``; el ARN de la tabla lo da `aws dynamodb describe-table --table-name Usuarios --query Table.TableArn --output text --region eu-north-1`): ```bash aws dynamodb update-continuous-backups --table-name Usuarios --region eu-north-1 --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true aws dynamodb export-table-to-point-in-time --table-arn --s3-bucket --export-format DYNAMODB_JSON --region eu-north-1 aws dynamodb list-exports --region eu-north-1 # ExportStatus COMPLETED al cabo de ~2,5 min aws s3 ls s3:///AWSDynamoDB/ --recursive # el código de la carpeta aws dynamodb import-table --region eu-north-1 --s3-bucket-source S3Bucket=,S3KeyPrefix=AWSDynamoDB//data/ --input-format DYNAMODB_JSON --input-compression-type GZIP --table-creation-parameters '{"TableName":"Usuarios-copia","AttributeDefinitions":[{"AttributeName":"UserID","AttributeType":"S"}],"KeySchema":[{"AttributeName":"UserID","KeyType":"HASH"}],"BillingMode":"PAY_PER_REQUEST"}' aws dynamodb list-imports --region eu-north-1 # ImportStatus COMPLETED al cabo de ~3 min ``` Son los mismos pasos que la consola. `import-table` crea siempre una tabla nueva; si `ImportStatus` queda en `FAILED`, mira `ItemValidationError` y borra la tabla vacía que queda. - **Si la importación falla** (por ejemplo, por escribir mal el nombre de la partition key): el estado queda en `FAILED` con `ItemValidationError`, el detalle está en CloudWatch Logs (grupo `/aws-dynamodb/imports`, `Missing the key ... in the item`) y **la tabla nueva queda creada y vacía**: bórrala. ## Errores frecuentes | Síntoma | Causa | Solución | |---|---|---| | `Permission denied` al lanzar `./dynamodb-cli.py` | El script no tiene permiso de ejecución | `chmod +x dynamodb-cli.py` (o `python3 dynamodb-cli.py ...`) | | `bad interpreter` o `$'\r': command not found` | Finales de línea de Windows | `sed -i 's/\r$//' *.py` y repite | | `ModuleNotFoundError: No module named 'boto3'` (o el mensaje `Falta la libreria boto3`) | Falta la librería | `pip3 install --user boto3` | | `pip3: command not found` (Amazon Linux 2023 limpia) | AL2023 no trae pip | `sudo dnf install -y python3-pip` y repite `pip3 install --user boto3`; un `PythonDeprecationWarning` sobre Python 3.9 es normal | | `Error al conectar o consultar DynamoDB:` con un mensaje de recurso no encontrado | La tabla no existe con ese nombre (distingue mayúsculas) o está en otra región. El script usa `eu-north-1`, no la de CloudShell | Revisa `aws dynamodb list-tables --region eu-north-1` y usa `--table` y `--region` | | El mismo error con `The provided key element does not match the schema` | La clave de partición de la tabla no se llama `UserID` o no es String, y no se lo has dicho al script | Añade `--key-name ` y `--key-type String` o `Number`, por ejemplo `--key-name id --key-type Number` | | `No hay credenciales de AWS validas para este script` | No hay credenciales: no estás en CloudShell y no has hecho `aws configure` | Usa CloudShell, o configura la AWS CLI en tu PC | | `list-tables` sale vacío con los comandos de la presentación | Esos comandos no llevan `--region` y CloudShell está en otra región | `echo "$AWS_REGION"` o añade `--region eu-north-1` | | En la consola, `ItemCount` de la tabla marca 0 aunque hayas cargado ítems | DynamoDB actualiza ese contador cada varias horas | Cuenta con `aws dynamodb scan --table-name --select COUNT --region eu-north-1` | | `scan` consume 2.0 y `get-item` 0.5 con tablas de 3 ítems | No es un error: el `scan` cuesta 0,5 por cada partición de la tabla | Nada que arreglar; es el argumento de coste de la presentación | | `PointInTimeRecoveryUnavailableException` al exportar | La tabla no tiene PITR activado | Actívalo (pestaña *Backups* o `update-continuous-backups`) y repite | | `Table already exists` al importar | Importaste sobre un nombre de tabla que ya existe | Usa un nombre de tabla nuevo | | La importación desde S3 queda en `FAILED` (`ItemValidationError`) y aparece una tabla nueva vacía | El nombre de la partition key de la tabla nueva no es el de los datos exportados (`UserID`) | Borra la tabla vacía y repite con `UserID`; el detalle está en CloudWatch Logs, grupo `/aws-dynamodb/imports` | ## Limpieza Hazla al terminar el ejercicio (y, en el caso de `Usuarios`, **después de terminar [03.08](../03.08-dynamodbvisor/README.md)**, que la usa para el visor). **1. Tablas de DynamoDB**: `Usuarios` y la tabla que creaste al importar desde S3 (`Usuarios-copia` si usaste la CLI). ```bash aws dynamodb list-tables --region eu-north-1 aws dynamodb delete-table --table-name Usuarios --region eu-north-1 ``` Repite `delete-table` con cada nombre. Si usas la consola y te ofrece crear una copia de seguridad antes de borrar, desmárcala. Si activaste PITR, se desactiva al borrar la tabla; si prefieres conservar la tabla, desactívalo antes: `aws dynamodb update-continuous-backups --table-name Usuarios --point-in-time-recovery-specification PointInTimeRecoveryEnabled=false --region eu-north-1` (PITR cuesta 0,21 USD por GB-mes). Una importación fallida deja además una tabla nueva vacía: bórrala también. **2. Logs de la importación** (la importación crea un grupo de logs que nadie borra): ```bash aws logs delete-log-group --log-group-name /aws-dynamodb/imports --region eu-north-1 ``` **3. S3 de la exportación**: en tu bucket, borra la carpeta `AWSDynamoDB/` que creó la exportación (consola de S3 > marca la carpeta > **Delete**, o `aws s3 rm s3:///AWSDynamoDB/ --recursive`). Si creaste el bucket solo para esto, vacíalo y bórralo (`aws s3 rb s3:// --force`). **4. Verificación:** ```bash aws dynamodb list-tables --region eu-north-1 aws logs describe-log-groups --log-group-name-prefix /aws-dynamodb --region eu-north-1 --query "logGroups[].logGroupName" ``` Qué debes ver: `list-tables` sin tus tablas de este tema y ningún grupo de logs `/aws-dynamodb/imports`. ## Cómo sabes que has terminado - [ ] La tabla `Usuarios` existe con clave `UserID` (String) y tiene tus ítems. - [ ] `dynamodb-cli.py` lee (`--mode get`) y escribe (`--mode put`) ítems de `Usuarios`, y los has comprobado con `aws dynamodb get-item`. - [ ] Has exportado la tabla a S3, importado en una tabla nueva y el `Count` de las dos coincide (8 ítems si seguiste las diapositivas en orden). - [ ] Has hecho la limpieza: tablas, grupo de logs y carpeta `AWSDynamoDB/` ya no existen (y PITR no queda activo). Anterior: [03.06 · ACID y rigidez de esquema](../03.06-acidrigidez/README.md) · Siguiente: [03.08 · Visor web de DynamoDB](../03.08-dynamodbvisor/README.md)