Ejemplo de modelado de datos relacionales en DynamoDB
En este ejemplo, se describe cómo se modelan los datos relacionales en Amazon DynamoDB. El diseño de las tablas de DynamoDB se corresponde con el esquema relacional de registro de pedidos que se muestra en Modelos relacionales. Este diseño utiliza varias tablas especializadas en lugar de una sola lista de adyacencias, lo que proporciona límites operativos claros y, al mismo tiempo, aprovecha los GSI estratégicos para atender todos los patrones de acceso de manera eficiente.
El enfoque del diseño utiliza principios orientados a los agregados, que agrupan los datos en función de los patrones de acceso en lugar de los límites rígidos de las entidades. Las principales decisiones de diseño incluyen el uso de tablas independientes para las entidades con una baja correlación de acceso, la incrustación de los datos relacionados cuando siempre se accede a ellos juntos y el uso de recopilaciones de elementos para identificar las relaciones.
Las siguientes tablas y los índices que las acompañan respaldan el esquema de registro de pedidos relacionales:
Diseño de tabla de empleados
La tabla de empleados almacena la información de los empleados como una sola entidad por elemento, optimizada para las búsquedas directas de los empleados y compatible con múltiples patrones de consulta a través de GSI estratégicos. En esta tabla, se muestra el principio de diseñar tablas independientes para entidades con características operativas independientes y una baja correlación de acceso entre entidades.
La tabla utiliza una clave de partición simple (employee_id) sin una clave de clasificación, ya que cada empleado es una entidad distinta. Cuatro GSI permiten realizar consultas eficientes por diferentes atributos:
EmployeeByName GSI: utiliza la proyección INCLUDE con todos los atributos de los empleados para facilitar la recuperación completa de los detalles de los empleados por nombre, gestiona los posibles nombres duplicados con employee_id como clave de clasificación.
EmployeeByWarehouse GSI: utiliza la proyección INCLUDE solo con los atributos esenciales (name, job_title, hire_date) para minimizar los costes de almacenamiento y, al mismo tiempo, permitir las consultas basadas en el almacén.
EmployeeByJobTitle GSI: permite realizar consultas basadas en roles con la proyección INCLUDE para la elaboración de informes y el análisis de la organización.
EmployeeByHireDate GSI: utiliza un valor de clave de partición estática “EMPLOYEE” con hire_date como clave de clasificación para permitir consultas eficientes por intervalos de fechas para las contrataciones recientes. Dado que las incorporaciones o actualizaciones de los empleados suelen ser inferiores a 1000 WCU, una sola partición puede gestionar la carga de escritura sin problemas relacionados con las particiones más frecuentes.
| employee_id (PK) | name | phone_numbers | warehouse_id | job_title | hire_date | entity_type |
|---|---|---|---|---|---|---|
| emp_001 | John Smith | [“+1-555-0101”] | wh_sea | Manager | 2024-03-15 | EMPLEADO |
| emp_002 | Jane Doe | [“+1-555-0102”, “+1-555-0103”] | wh_sea | Asociar | 2025-01-10 | EMPLEADO |
| emp_003 | Bob Wilson | [“+1-555-0104”] | wh_pdx | Asociar | 2025-06-20 | EMPLEADO |
| emp_004 | Alice Brown | [“+1-555-0105”] | wh_pdx | Supervisor | 2023-11-05 | EMPLEADO |
| emp_005 | Charlie Davis | [“+1-555-0106”] | wh_sea | Asociar | 2025-12-01 | EMPLEADO |
| nombre (GSI-PK) | employee_id (GSI-SK) | phone_numbers | warehouse_id | job_title | hire_date |
|---|---|---|---|---|---|
| Alice Brown | emp_004 | [“+1-555-0105”] | wh_pdx | Supervisor | 2023-11-05 |
| Bob Wilson | emp_003 | [“+1-555-0104”] | wh_pdx | Asociar | 2025-06-20 |
| Charlie Davis | emp_005 | [“+1-555-0106”] | wh_sea | Asociar | 2025-12-01 |
| Jane Doe | emp_002 | [“+1-555-0102”, “+1-555-0103”] | wh_sea | Asociar | 2025-01-10 |
| John Smith | emp_001 | [“+1-555-0101”] | wh_sea | Manager | 2024-03-15 |
| warehouse_id (GSI-PK) | employee_id (GSI-SK) | name | job_title | hire_date |
|---|---|---|---|---|
| wh_pdx | emp_003 | Bob Wilson | Asociar | 2025-06-20 |
| wh_pdx | emp_004 | Alice Brown | Supervisor | 2023-11-05 |
| wh_sea | emp_001 | John Smith | Manager | 2024-03-15 |
| wh_sea | emp_002 | Jane Doe | Asociar | 2025-01-10 |
| wh_sea | emp_005 | Charlie Davis | Asociar | 2025-12-01 |
| job_title (GSI-PK) | employee_id (GSI-SK) | name | warehouse_id | hire_date |
|---|---|---|---|---|
| Asociar | emp_002 | Jane Doe | wh_sea | 2025-01-10 |
| Asociar | emp_003 | Bob Wilson | wh_pdx | 2025-06-20 |
| Asociar | emp_005 | Charlie Davis | wh_sea | 2025-12-01 |
| Manager | emp_001 | John Smith | wh_sea | 2024-03-15 |
| Supervisor | emp_004 | Alice Brown | wh_pdx | 2023-11-05 |
| entity_type (GSI-PK) | hire_date (GSI-SK) | employee_id | name | warehouse_id |
|---|---|---|---|---|
| EMPLEADO | 2023-11-05 | emp_004 | Alice Brown | wh_pdx |
| EMPLEADO | 2024-03-15 | emp_001 | John Smith | wh_sea |
| EMPLEADO | 2025-01-10 | emp_002 | Jane Doe | wh_sea |
| EMPLEADO | 2025-06-20 | emp_003 | Bob Wilson | wh_pdx |
| EMPLEADO | 2025-12-01 | emp_005 | Charlie Davis | wh_sea |
Diseño de tabla de clientes
La tabla de clientes mantiene la información de los clientes con la desnormalización estratégica de account_rep_id para permitir consultas eficientes de los representantes de las cuentas. Esta opción de diseño reduce una pequeña sobrecarga de almacenamiento en beneficio del rendimiento de las consultas, lo que elimina la necesidad de unir los datos de los clientes y los de los representantes de la cuenta.
La tabla admite varios números de teléfono por cliente mediante un atributo de lista, lo que demuestra la flexibilidad del esquema de DynamoDB. El GSI único permite los flujos de trabajo de los representantes de cuentas:
CustomerByAccountRep GSI: utiliza la proyección INCLUDE con los atributos de nombre y correo electrónico para facilitar la administración de clientes del representante de cuentas sin necesidad de recuperar todos los registros de los clientes.
| customer_id (PK) | name | phone_numbers | correo electrónico | account_rep_id |
|---|---|---|---|---|
| cust_001 | Acme Corp | [“+1-555-1001”] | contact@acme.com | rep_001 |
| cust_002 | TechStart Inc | [“+1-555-1002”, “+1-555-1003”] | info@techstart.com | rep_001 |
| cust_003 | Global Traders | [“+1-555-1004”] | sales@globaltraders.com | rep_002 |
| cust_004 | BuildRight LLC | [“+1-555-1005”] | orders@buildright.com | rep_002 |
| cust_005 | FastShip Co | [“+1-555-1006”] | support@fastship.com | rep_003 |
| account_rep_id (GSI-PK) | customer_id (GSI-SK) | name | correo electrónico |
|---|---|---|---|
| rep_001 | cust_001 | Acme Corp | contact@acme.com |
| rep_001 | cust_002 | TechStart Inc | info@techstart.com |
| rep_002 | cust_003 | Global Traders | sales@globaltraders.com |
| rep_002 | cust_004 | BuildRight LLC | orders@buildright.com |
| rep_003 | cust_005 | FastShip Co | support@fastship.com |
Diseño de tabla de pedidos
La tabla de pedidos utiliza una partición vertical con elementos independientes para los encabezados de los pedidos y los elementos de los pedidos. Este diseño permite realizar consultas eficientes basadas en el producto y, al mismo tiempo, mantener todos los componentes del pedido dentro de la misma partición para un acceso eficiente. Cada pedido consta de varios elementos:
Encabezado del pedido: contiene los metadatos del pedido con PK=order_id, SK=order_id
Elementos del pedido: elementos de línea individuales con PK=order_id, SK=product_id, lo que permite realizar consultas directas de productos
nota
Este enfoque de partición vertical compensa la simplicidad de los elementos de pedido incrustados en favor de una mayor flexibilidad de consulta. Cada elementos del pedido se convierte en un elemento independiente de DynamoDB, lo que permite realizar consultas eficientes basadas en el producto y, al mismo tiempo, mantener todos los datos del pedido en la misma partición para poder recuperarlos de forma eficaz en una sola solicitud.
La tabla incluye la desnormalización estratégica de account_rep_id (un duplicado de la tabla de clientes) para permitir consultas directas a los representantes de las cuentas sin necesidad de buscar a los clientes. Para escenarios de escritura de alto rendimiento, los pedidos ABIERTOS incluyen los atributos de estado y partición para permitir la fragmentación de escritura en varias particiones.
Cuatro GSI admiten diferentes patrones de consulta con proyecciones optimizadas:
OrderByCustomerDate GSI: utiliza la proyección INCLUDE con el resumen del pedido y los detalles de los elementos para respaldar el historial de pedidos de los clientes con un filtrado por intervalos de fechas
OpenOrdersByDate GSI (disperso, particionado): utiliza una clave de partición con varios atributos (estado + partición) con 5 particiones para distribuir 5000 WPS (escrituras por segundo) entre las particiones (1000 WPS cada una, lo que equivale al límite de 1000 WCU por partición de DynamoDB). Solo indexa los pedidos ABIERTOS (el 20 % del total), lo que puede ayudar a reducir los costos de almacenamiento de GSI. Requiere consultas paralelas en las 5 particiones con la fusión de los resultados del cliente
OrderByAccountRep GSI: utiliza la proyección INCLUDE con los atributos del resumen del pedido para respaldar los flujos de trabajo representativos de las cuentas sin tener todos los detalles del pedido
ProductInOrders GSI: a partir de registros de OrderItem (PK=order_id, SK=product_id), este GSI permite realizar consultas para encontrar todos los pedidos que contengan un producto específico. Usa la proyección INCLUDE con el contexto del pedido (customer_id, order_date, quantity) para el análisis de la demanda de productos
| PK | SK | customer_id | order_date | status | account_rep_id | quantity | precio | partición |
|---|---|---|---|---|---|---|---|---|
| ord_001 | ord_001 | cust_001 | 2025-11-15 | CLOSED | rep_001 | |||
| ord_001 | prod_100 | 5 | 25.00 | |||||
| ord_002 | ord_002 | cust_001 | 2025-12-20 | OPEN | rep_001 | 0 | ||
| ord_002 | prod_101 | 10 | 15.00 | |||||
| ord_003 | ord_003 | cust_002 | 2026-01-05 | OPEN | rep_001 | 2 | ||
| ord_003 | prod_100 | 3 | 25.00 |
| customer_id (GSI-PK) | order_date (GSI-SK) | order_id | status | total_amount | order_items | partición |
|---|---|---|---|---|---|---|
| cust_001 | 2025-11-15 | ord_001 | CLOSED | 225.00 | [{product_id: "prod_100", qty: 5}] | |
| cust_001 | 2025-12-20 | ord_002 | OPEN | 150.00 | [{product_id: "prod_101", qty: 10}] | 0 |
| cust_002 | 2026-01-05 | ord_003 | OPEN | 175.00 | [{product_id: "prod_100", qty: 3}] | 2 |
| cust_003 | 2025-10-10 | ord_004 | CLOSED | 250.00 | [{product_id: "prod_101", qty: 5}] | |
| cust_004 | 2026-01-03 | ord_005 | OPEN | 200.00 | [{product_id: "prod_100", qty: 20}] | 1 |
| estado (GSI-PK-1) | partición (GSI-PK-2) | order_date (SK) | order_id | customer_id | account_rep_id | order_items | total_amount |
|---|---|---|---|---|---|---|---|
| OPEN | 0 | 2025-12-20 | ord_002 | cust_001 | rep_001 | [{product_id: "prod_101", qty: 10}] | 150.00 |
| OPEN | 1 | 2026-01-03 | ord_005 | cust_004 | rep_002 | [{product_id: "prod_100", qty: 20}] | 200.00 |
| OPEN | 2 | 2026-01-05 | ord_003 | cust_002 | rep_001 | [{product_id: "prod_100", qty: 3}] | 175.00 |
| account_rep_id (GSI-PK) | order_date (GSI-SK) | order_id | customer_id | status | total_amount |
|---|---|---|---|---|---|
| rep_001 | 2025-11-15 | ord_001 | cust_001 | CLOSED | 225.00 |
| rep_001 | 2025-12-20 | ord_002 | cust_001 | OPEN | 150.00 |
| rep_001 | 2026-01-05 | ord_003 | cust_002 | OPEN | 175.00 |
| rep_002 | 2025-10-10 | ord_004 | cust_003 | CLOSED | 250.00 |
| rep_002 | 2026-01-03 | ord_005 | cust_004 | OPEN | 200.00 |
| product_id (GSI-PK) | order_id (GSI-SK) | customer_id | order_date | quantity |
|---|---|---|---|---|
| prod_100 | ord_001 | cust_001 | 2025-11-15 | 5 |
| prod_100 | ord_003 | cust_002 | 2026-01-05 | 3 |
| prod_101 | ord_002 | cust_001 | 2025-12-20 | 10 |
Diseño de tabla de productos
La tabla de productos utiliza el patrón de recopilación de elementos para almacenar los metadatos del producto y los datos de inventario en la misma partición. Este diseño aprovecha la relación de identificación entre los productos y el inventario: el inventario no puede existir sin un producto principal. El uso de PK=product_id con SK=product_id para los metadatos del producto y SK=warehouse_id para los elementos del inventario elimina la necesidad de tener una tabla de inventario y un GSI independientes, lo que reduce los costos en aproximadamente un 50 %.
Este patrón permite realizar consultas eficientes tanto para el inventario de almacén individual (GetItem con clave compuesta) como para todo el inventario de almacén de un producto (consulta en clave de partición). El atributo total_inventory del elemento de metadatos del producto proporciona una agregación desnormalizada para realizar búsquedas rápidas del inventario total.
| product_id (PK) | warehouse_id (SK) | product_name | categoría | unit_price | inventory_quantity | total_inventory |
|---|---|---|---|---|---|---|
| prod_100 | prod_100 | Widget A | Hardware de | 25.00 | 500 | |
| prod_100 | wh_sea | 200 | ||||
| prod_100 | wh_pdx | 150 | ||||
| prod_100 | wh_atl | 150 | ||||
| prod_101 | prod_101 | Gadget B | Electronics | 50.00 | 300 | |
| prod_101 | wh_sea | 100 | ||||
| prod_101 | wh_pdx | 200 |
Cada tabla está diseñada con índices secundarios globales (GSI) específicos para respaldar los patrones de acceso requeridos de manera eficiente. El diseño utiliza principios orientados a los agregados con una desnormalización estratégica y una indexación dispersa para optimizar tanto el rendimiento como los costos.
Las principales optimizaciones de diseño incluyen:
-
GSI disperso: OpenOrdersByDate solo indexa los pedidos ABIERTOS (20 % del total), que puede ayudar a reducir los costos de almacenamiento de GSI
-
Patrón de recolección de elementos: la tabla de productos almacena el inventario mediante PK=product_id y SK=warehouse_id para eliminar la tabla de inventario independiente
-
Agregación de pedido + OrderItems: incrustado como un solo elemento debido a una correlación de acceso del 100 %
-
Desnormalización estratégica: account_rep_id duplicado en la tabla de pedidos para facilitar las consultas
Por último, puede volver a consultar los patrones de acceso que se definieron anteriormente. En la tabla siguiente, se muestra cómo se admite cada patrón de acceso de manera eficiente mediante el diseño de varias tablas con GSI estratégicos. Cada patrón utiliza búsquedas de claves directas o consultas de GSI únicas, lo que evita análisis costosos y proporciona un rendimiento coherente a cualquier escala.
| S. No. | Patrones de acceso | Condiciones de la consulta |
|---|---|---|
|
1 |
Buscar detalles de empleado por ID de empleado |
Tabla de empleados: GetItem(employee_id="emp_001") |
|
2 |
Consultar detalles de empleado por nombre de empleado |
EmployeeByName GSI: Query(name="John Smith") |
|
3 |
Encontrar los números de teléfono de un empleado |
Tabla de empleados: GetItem(employee_id="emp_001") |
|
4 |
Encontrar los números de teléfono de un cliente |
Tabla de clientes: GetItem(customer_id="cust_001") |
|
5 |
Obtener los pedidos de los clientes dentro del rango de fechas |
OrderByCustomerDate GSI: Query(customer_id="cust_001", order_date BETWEEN "2025-01-01" AND "2025-12-31") |
|
6 |
Mostrar todos los pedidos abiertos dentro del rango de fechas |
OpenOrdersByDate GSI: consulte 5 particiones en paralelo con PK de varios atributos (status="OPEN" + shard=0-4), SK=order_date BETWEEN "2025-01-01" AND "2025-12-31", resultados de combinación |
|
7 |
Ver todos los empleados contratados recientemente |
EmployeeByHireDate GSI: Query(entity_type="EMPLOYEE", hire_date >= "2025-01-01") |
|
8 |
Encontrar a todos los empleados en un almacén |
EmployeeByWarehouse GSI: Query(warehouse_id="wh_sea") |
|
9 |
Obtener todos los artículos del pedido por producto |
ProductInOrders GSI: Query(product_id="prod_100") |
|
10 |
Obtener inventarios de productos en todos los almacenes |
Tabla de productos: Query(product_id="prod_100") |
|
11 |
Obtener clientes por representante de cuenta |
CustomerByAccountRep GSI: Query(account_rep_id="rep_001") |
|
12 |
Obtener pedidos por representante de cuenta |
OrderByAccountRep GSI: Query(account_rep_id="rep_001") |
|
13 |
Obtener empleados con título de trabajo |
EmployeeByJobTitle GSI: Query(job_title="Manager") |
|
14 |
Obtener inventario por producto y almacén |
Tabla de productos: GetItem(product_id="prod_100", warehouse_id="wh_sea") |
|
15 |
Obtener inventario de productos total |
Tabla de productos: GetItem(product_id="prod_100", warehouse_id="prod_100") |