Exemplo de modelagem de dados relacionais no DynamoDB
Esse exemplo descreve como modelar dados relacionais no Amazon DynamoDB. O design de tabela do DynamoDB corresponde ao esquema relacional de entrada de pedidos que é mostrado em Modelagem relacional. Esse design usa várias tabelas especializadas em vez de uma única lista de adjacências, oferecendo limites operacionais claros e utilizando GSIs estratégicos para atender a todos os padrões de acesso com eficiência.
Uma abordagem usa princípios de agregação, agrupando dados com base em padrões de acesso em vez de limites rígidos de entidade. As principais decisões de design incluem o uso de tabelas separadas para entidades com baixa correlação de acesso, a incorporação de dados relacionados quando sempre acessados em conjunto e o uso de coleções de itens para identificar relações.
As seguintes tabelas e os índices que as acompanham permitem usar o esquema de entrada de pedidos relacionais:
Design de tabela de funcionários
A tabela de funcionários armazena informações sobre os funcionários como uma única entidade por item, é otimizada para consultas diretas sobre funcionários e permite vários padrões de consulta por meio de GSIs estratégicos. Ela demonstra o princípio de criar tabelas separadas para entidades com características operacionais independentes e baixa correlação de acesso entre entidades.
A tabela usa uma chave de partição simples (employee_id) sem uma chave de classificação, pois cada funcionário é uma entidade distinta. Quatro GSIs permitem consultas eficientes por atributos diferentes:
GSI EmployeeByName: usa a projeção INCLUDE com todos os atributos dos funcionários para permitir a recuperação completa de detalhes do funcionário por nome e trata possíveis nomes duplicados com employee_id como chave de classificação.
GSI EmployeeByWarehouse: usa a projeção INCLUDE apenas com atributos essenciais (name, job_title, hire_date) para minimizar os custos de armazenamento e, ao mesmo tempo, permitir consultas baseadas em armazém.
GSI EmployeeByJobTitle: permite consultas baseadas em funções com projeção INCLUDE para geração de relatórios e análises organizacionais.
GSI EmployeeByHireDate: usa um valor de chave de partição estática “EMPLOYEE” com hire_date como chave de classificação para permitir consultas eficientes de intervalo de datas referentes a contratações recentes. Como as adições/atualizações de funcionários geralmente são inferiores a 1.000 WCUs, uma única partição pode lidar com a carga de gravação sem problemas de partição com acesso frequente.
| employee_id (PK) | name | phone_numbers | warehouse_id | job_title | hire_date | entity_type |
|---|---|---|---|---|---|---|
| emp_001 | John | ["+1-555-0101"] | wh_sea | Gerente | 2024-03-15 | EMPLOYEES |
| emp_002 | Jane Doe | ["+1-555-0102", "+1-555-0103"] | wh_sea | Associate | 2025-01-10 | EMPLOYEES |
| emp_003 | Bob Wilson | ["+1-555-0104"] | wh_pdx | Associate | 2025-06-20 | EMPLOYEES |
| emp_004 | Alice Brown | ["+1-555-0105"] | wh_pdx | Supervisor | 2023-11-05 | EMPLOYEES |
| emp_005 | Mike Davis | ["+1-555-0106"] | wh_sea | Associate | 2025-12-01 | EMPLOYEES |
| name (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 | Associate | 2025-06-20 |
| Mike Davis | emp_005 | ["+1-555-0106"] | wh_sea | Associate | 2025-12-01 |
| Jane Doe | emp_002 | ["+1-555-0102", "+1-555-0103"] | wh_sea | Associate | 2025-01-10 |
| John | emp_001 | ["+1-555-0101"] | wh_sea | Gerente | 2024-03-15 |
| warehouse_id (GSI-PK) | employee_id (GSI-SK) | name | job_title | hire_date |
|---|---|---|---|---|
| wh_pdx | emp_003 | Bob Wilson | Associate | 2025-06-20 |
| wh_pdx | emp_004 | Alice Brown | Supervisor | 2023-11-05 |
| wh_sea | emp_001 | John | Gerente | 2024-03-15 |
| wh_sea | emp_002 | Jane Doe | Associate | 2025-01-10 |
| wh_sea | emp_005 | Mike Davis | Associate | 2025-12-01 |
| job_title (GSI-PK) | employee_id (GSI-SK) | name | warehouse_id | hire_date |
|---|---|---|---|---|
| Associate | emp_002 | Jane Doe | wh_sea | 2025-01-10 |
| Associate | emp_003 | Bob Wilson | wh_pdx | 2025-06-20 |
| Associate | emp_005 | Mike Davis | wh_sea | 2025-12-01 |
| Gerente | emp_001 | John | 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 |
|---|---|---|---|---|
| EMPLOYEES | 2023-11-05 | emp_004 | Alice Brown | wh_pdx |
| EMPLOYEES | 2024-03-15 | emp_001 | John | wh_sea |
| EMPLOYEES | 2025-01-10 | emp_002 | Jane Doe | wh_sea |
| EMPLOYEES | 2025-06-20 | emp_003 | Bob Wilson | wh_pdx |
| EMPLOYEES | 2025-12-01 | emp_005 | Mike Davis | wh_sea |
Design de tabelas de clientes
A tabela de clientes mantém informações sobre o cliente com a desnormalização estratégica de account_rep_id para permitir consultas eficientes sobre representantes de conta. Essa opção de design opta por uma pequena sobrecarga de armazenamento em prol do desempenho das consultas, eliminando a necessidade de junções entre os dados do cliente e do representante da conta.
A tabela comporta vários números de telefone por cliente usando um atributo de lista, o que demonstra a flexibilidade do esquema do DynamoDB. O GSI único permite fluxos de trabalho de representantes de contas:
GSI CustomerByAccountRep: usa a projeção INCLUDE com atributos de nome e e-mail para facilitar o gerenciamento de clientes de representantes de contas sem exigir a recuperação completa dos registros dos clientes.
| customer_id (PK) | name | phone_numbers | 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 | |
|---|---|---|---|
| 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 |
Design de tabela de pedidos
A tabela de pedidos usa particionamento vertical com itens separados para cabeçalhos de pedidos e itens de pedidos. Esse design permite consultas eficientes baseadas em produtos, mantendo todos os componentes do pedido na mesma partição para que o acesso seja eficiente. Cada pedido consiste em vários itens:
Cabeçalho do pedido: contém metadados do pedido com PK=order_id, SK=order_id.
Itens do pedido: itens de linha individuais com PK=order_id, SK=product_id, permitindo consultas diretas sobre produtos.
nota
Essa abordagem de particionamento vertical opta pela simplicidade dos itens de pedido incorporados em prol de uma maior flexibilidade de consulta. Cada item do pedido se torna um item separado do DynamoDB, permitindo consultas eficientes baseadas em produtos e mantendo todos os dados do pedido na mesma partição para aumentar a eficiência da recuperação em uma única solicitação.
A tabela inclui a desnormalização estratégica de account_rep_id (duplicada da tabela de clientes) para permitir consultas diretas aos representantes de contas sem exigir consultas sobre clientes. Em cenários de gravação de alto throughput, os pedidos OPEN incluem atributos de status e fragmento para permitir a fragmentação de gravação em várias partições.
Quatro GSIs permitem diferentes padrões de consulta com projeções otimizadas:
GSI OrderByCustomerDate: usa a projeção INCLUDE com resumo do pedido e detalhes do item para permitir a filtragem por intervalo de datas no histórico de pedidos do cliente.
GSI OpenOrdersByDate (esparso, fragmentado): usa chave de partição de vários atributos (status + fragmento) com 5 fragmentos para distribuir 5.000 WPS (gravações por segundo) entre partições (1.000 WPS cada, o que corresponde ao limite de 1.000 WCUs por partição do DynamoDB). Indexa somente pedidos OPEN (20% do total), o que pode ajudar a reduzir os custos de armazenamento do GSI. Requer consultas paralelas em todos os cinco fragmentos com mesclagem de resultados do lado do cliente.
GSI OrderByAccountRep: usa a projeção INCLUDE com atributos de resumo do pedido para permitir fluxos de trabalho de representantes de contas sem detalhes completos do pedido.
GSI ProductInOrders: criado com base nos registros de OrderItem (PK=order_id, SK=product_id), este GSI permite que as consultas encontrem todos os pedidos que contêm um produto específico. Usa a projeção INCLUDE com o contexto do pedido (customer_id, order_date, quantity) para análise da demanda do produto.
| PK | SK | customer_id | order_date | status | account_rep_id | quantidade | preço | shard (fragmento) |
|---|---|---|---|---|---|---|---|---|
| ord_001 | ord_001 | cust_001 | 2025-11-15 | FECHADO | 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 | shard (fragmento) |
|---|---|---|---|---|---|---|
| cust_001 | 2025-11-15 | ord_001 | FECHADO | 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 | FECHADO | 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 |
| status (GSI-PK-1) | shard (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 | FECHADO | 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 | FECHADO | 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 | quantidade |
|---|---|---|---|---|
| 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 |
Design de tabela de produtos
A tabela de produtos usa o padrão de coleção de itens para armazenar metadados do produto e dados de inventário na mesma partição. Esse design utiliza a relação de identificação entre produtos e inventário: não pode haver inventário sem um produto principal. Usar PK=product_id com SK=product_id para metadados do produto e SK=warehouse_id para itens de inventário elimina a necessidade de uma tabela de inventário e GSI separados e reduz os custos em aproximadamente 50%.
Esse padrão permite consultas eficientes tanto para o inventário individual do armazém (GetItem com chave composta) quanto para todo o inventário do armazém de um produto (consulta na chave de partição). O atributo total_inventory no item de metadados do produto oferece agregação desnormalizada para pesquisas rápidas de inventário total.
| product_id (PK) | warehouse_id (SK) | product_name | categoria | unit_price | inventory_quantity | total_inventory |
|---|---|---|---|---|---|---|
| prod_100 | prod_100 | Widget A | Hardware da | 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 tabela é projetada com índices secundários globais (GSIs) específicos para atender de forma eficiente aos padrões de acesso necessários. O design usa princípios de agregação com desnormalização estratégica e indexação esparsa para otimizar o desempenho e o custo.
As principais otimizações de design incluem:
-
GSI esparso: OpenOrdersByDate indexa somente pedidos OPEN (20% do total), o que pode ajudar a reduzir os custos de armazenamento do GSI.
-
Padrão de coleção de itens: a tabela de produtos armazena o inventário usando PK=product_id, SK=warehouse_id para eliminar uma tabela de inventário separada.
-
Agregação de Order + OrderItems: incorporada como item único devido à correlação de acesso de 100%.
-
Desnormalização estratégica: account_rep_id é duplicado na tabela de pedidos melhorar a eficiência das consultas.
Por fim, você pode reanalisar os padrões de acesso que foram definidos anteriormente. A tabela a seguir mostra como cada padrão de acesso é atendido de forma eficiente usando o design de várias tabelas com GSIs estratégicos. Cada padrão usa pesquisas de chave diretas ou consultas de GSI único, o que evita verificações caras e oferece desempenho consistente em qualquer escala.
| S. Não. | Padrões de acesso | Condições de consulta |
|---|---|---|
|
1 |
Procurar os detalhes do funcionário por ID do funcionário |
Tabela de funcionários: GetItem(employee_id="emp_001") |
|
2 |
Consultar detalhes do funcionário por nome do funcionário |
GSI EmployeeByName: Query(name="John Smith") |
|
3 |
Encontrar um ou mais números de telefone de um funcionário |
Tabela de funcionários: GetItem(employee_id="emp_001") |
|
4 |
Encontrar um ou mais números de telefone de um cliente |
Tabela de clientes: GetItem(customer_id="cust_001") |
|
5 |
Receber pedidos de clientes dentro do intervalo de datas |
GSI OrderByCustomerDate: Query(customer_id="cust_001", order_date BETWEEN "2025-01-01" AND "2025-12-31") |
|
6 |
Mostrar todos os pedidos em aberto dentro do intervalo de datas |
GSI OpenOrdersByDate: Query 5 shards in parallel with multi-attribute PK (status="OPEN" + shard=0-4), SK=order_date BETWEEN "2025-01-01" AND "2025-12-31", merge results |
|
7 |
Ver todos os funcionários contratados recentemente |
GSI EmployeeByHireDate: Query(entity_type="EMPLOYEE", hire_date >= "2025-01-01") |
|
8 |
Encontrar todos os funcionários no armazém |
GSI EmployeeByWarehouse: Query(warehouse_id="wh_sea") |
|
9 |
Obter todos os itens do pedido do produto |
GSI ProductInOrders: Query(product_id="prod_100") |
|
10 |
Obter inventários de produtos em todos os armazéns |
Tabela de produtos: Query(product_id="prod_100") |
|
11 |
Obter clientes por representante de conta |
GSI CustomerByAccountRep: Query(account_rep_id="rep_001") |
|
12 |
Obter pedidos por representante de conta |
GSI OrderByAccountRep: Query(account_rep_id="rep_001") |
|
13 |
Obter funcionários com o cargo |
GSI EmployeeByJobTitle: Query(job_title="Manager") |
|
14 |
Obter inventário por produto e armazém |
Tabela de produtos: GetItem(product_id="prod_100", warehouse_id="wh_sea") |
|
15 |
Obter o inventário total de produtos |
Tabela de produtos: GetItem(product_id="prod_100", warehouse_id="prod_100") |