amazon web services - pricing - Clave principal compuesta de 3 campos(elemento único) en Dynamodb
dynamodb tutorial español (3)
Estoy intentando crear una tabla para almacenar artículos de línea de factura en DynamoDB. Digamos que el artículo está definido por CompanyCode
, InvoiceNumber
y LineItemId
, la cantidad y otros detalles de los artículos de línea.
Un elemento único se define por la combinación de los primeros 3 atributos. Cualquier 2 de esos atributos pueden ser iguales para los diferentes elementos. ¿Qué debo seleccionar como Atributo de hash y Atributo de rango?
Algunos Intro
Por eficiencia propondría un diseño totalmente diferente. Con las bases de datos NoSQL (y DynamoDB no es diferente) siempre debemos considerar los patrones de acceso primero. Además, si es posible, deberíamos esforzarnos por ajustar todos nuestros datos dentro de la misma tabla y varios índices. De lo que tenemos de OP y sus comentarios, estos son los dos patrones de acceso:
- Para una empresa X, obtenga la factura completa Y (incluidos todos los artículos o rango de artículos) [basado en este comment ]
- Obtenga todas las facturas para la empresa X [basado en este comment ]
Ahora nos preguntamos ¿qué es una buena clave primaria? ¿Se traduce en la pregunta de qué es una buena clave de partición (PK) y qué es una buena clave de clasificación (SK) y qué índices secundarios necesitamos crear y de qué tipo (local o global)? Algunos recordatorios:
- La clave principal puede estar en una columna o compuesto
- La clave primaria compuesta consta de la clave de partición y la clave de clasificación
- La clave de partición se usa como entrada para la función de hash que determinará la partición de los elementos
- La clave de clasificación también puede ser compuesta, lo que nos permite modelar relaciones de uno a varios en DynamoDB como se indica en uno de los enlaces de comentarios: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html
- Al crear una consulta en la tabla o índice, siempre debe usar el operador ''='' en la Clave de partición
- Al consultar rangos en Clave de clasificación, tiene la opción
KeyConditionExpression
que le proporciona un conjunto de operadores para ordenar y todo lo que está en medio (uno de ellos es la funciónbegins_with (a, substr)
) - También se le permite usar
FilterExpression
si necesita refinar más los resultados de la consulta (filtre en los atributos proyectados) - Los índices secundarios locales (LSI) tienen la misma clave de partición pero la clave de clasificación es diferente a la tabla original y le ofrecen una vista diferente de sus datos, organizada según una clave de clasificación alternativa
- Los índices secundarios globales (GSI) tienen una clave de partición diferente y una clave de clasificación diferente a la de su tabla original y le ofrecen una vista completamente diferente de los datos
- Todos los elementos con la misma clave de partición se almacenan juntos, y para claves primarias compuestas, se ordenan por el valor de la clave de clasificación. DynamoDB divide las particiones por clave de clasificación si el tamaño de la colección crece más de 10 GB.
Volver a modelar
Es obvio que estamos tratando con múltiples entidades que necesitan ser modeladas y ajustadas en la misma tabla. Para satisfacer la condición de que la Clave de partición sea única en la tabla, CustomerCode
presenta como una Clave de partición natural, así que me aseguraré de que sea único. Si no es así, debe preguntarse cómo puede modelar el segundo patrón de acceso.
Suponiendo que hayamos establecido unicidad en CompanyCode
simplifiquemos y digamos que viene en forma de correo electrónico (o podría ser un dominio o simplemente un código, pero usaré el correo electrónico para la demostración).
- La relación entre la empresa y las facturas es siempre 1: muchos.
- La relación entre la factura y los artículos es siempre 1: muchos.
Propongo diseño como en la imagen de abajo:
- Con PK siendo
CompanyCode
y SK siendoInvoiceNumber
puede almacenar todos los atributos de esa factura para esa compañía. - Nada me impide agregar también un registro donde el SK es el
Customer
que me permite almacenar todos los atributos de la compañía. - Con GSI1, crearemos una búsqueda inversa donde GSI1PK es mis tablas SK (
InvoiceNumber
) y mi GSI1SK es mis tablas PK (CompanyCode
). - Estoy usando la misma tabla para almacenar artículos de línea con PK siendo
LineItemId
y SK siendoCompanyCode
(aún único) - Para los elementos de la entidad del elemento, mi GSI1PK sigue siendo el número de
InvoiceNumber
y mi GSI1SK es elLineItemId
que es tablas PK, por lo que es igual que para los elementos de la entidad de la factura.
Ahora los patrones de acceso soportados con esto:
- Si quiero obtener la factura Y para la compañía X y todos los artículos (patrón de acceso 1): consulte la tabla donde
CompanyCode=X
y useKeyConditionExpression
con el operador=
en el número deInvoiceNumber
clave deInvoiceNumber
. Si quiero obtener todos los artículos vinculados a esa factura, proyectaré el atributoItems
utilizandoProjectionExpression
. - Al recuperar todos los artículos con la consulta previa para la compañía X y la factura Y, ahora puedo ejecutar la
BatchGetItem
APIBatchGetItem
(usando mi clave compuesta únicaLineItemId+CompanyCode
) en la tabla para obtener todos los artículos que pertenecen a esa factura particular de ese cliente en particular. (Esto viene con algunas restricciones de la API BatchGetItem ) - Para admitir el patrón de acceso 2, haré una consulta con
CompanyCode=X
en PK yKeyConditionExpression
en el SK conbegins_with (a, substr)
function / operator para obtener solo las facturas de la compañía X y no los metadatos de esa compañía. Eso me dará todas las facturas para una empresa / cliente determinado. - Además, con el GSI1 anterior, para cualquier
InvoiceNumber
dado, puedo seleccionar fácilmente todas las partidas que pertenecen a esa factura en particular. RECUERDE: los valores clave en un índice secundario global no tienen que ser únicos , por lo que en mi GSI1 podría haber tenido fácilmente factura_1 -> (item_1, item_2) y luego otra factura_1 -> (item_1, item_2) pero la diferencia entre dos Los elementos en GSI estarían en el SK (estaría asociado con unCompanyCode
diferente (pero para fines de demostración usé invoice_1 y invoice_2).
Como estoy seguro de que ha descubierto que no puede tener más de dos atributos de su clave principal (hash + rango). Por lo tanto, dependiendo del tipo de consultas que realizará y del tamaño de sus datos, puede estructurar su tabla de diferentes maneras.
(Optimizado para el tipo de consulta que mencionó anteriormente: solo CompanyCode
y los 3)
La mejor solución para conjuntos de datos de tamaño pequeño / mediano:
- Hash Key:
CompanyCode
- Realice la consulta usando solo
CompanyCode
y luego filtre sus resultados en los otros dos atributos
Solución óptima para grandes conjuntos de datos:
- Hash Key:
CompanyCode
- Clave de rango: Número de
InvoiceNumber
+LineItemId
- Esto le permite consultar solo en un índice, pero la estructura de la tabla es bastante fea
Creo que la primera opción que ofrece @ georgeaf99 no funcionará, porque si lo haces de esa manera, CompanyCode
tiene que ser único en la tabla. Por lo tanto, solo se permitiría un artículo por empresa. Creo que la segunda solución es la única forma real de hacerlo.
Puede usar CompanyCode
como clave de hash, y luego todos los demás campos que se combinan para hacer que el elemento sea único (en este caso, InvoiceNumber
y LineItemId
) deben combinarse de alguna manera en un solo valor (como concatenación con un delimitador de campo), que sería su clave de rango. Desafortunadamente, eso es algo feo, pero esa es la naturaleza de una base de datos NoSQL como DynamoDB. Sin embargo, le permitirá almacenar con éxito los registros con la singularidad correcta. Al volver a leer los registros, si no desea volver a analizar el campo combinado en sus partes individuales, tendrá que agregar campos separados adicionales para InvoiceNumber
y LineItemID
.
Si no tiene un gran número de facturas por compañía, puede consultar solo con la clave Hash y hacer el filtrado en el lado del cliente. Si tiene una gran cantidad de facturas por compañía y necesita poder consultar solo los artículos para una sola factura, entonces crearía un índice secundario en CompanyCode y InvoiceNumber.