DynamoDB: índices secundarios locales

Algunas aplicaciones ejecutan consultas solo con la clave principal, pero en algunas situaciones es mejor utilizar una clave de clasificación alternativa. Dele a su aplicación una opción creando uno o más índices secundarios locales.

Los requisitos de acceso a datos complejos, como la combinación de millones de elementos, obligan a realizar consultas / análisis más eficientes. Los índices secundarios locales proporcionan una clave de clasificación alternativa para el valor de la clave de partición. También contienen copias de todos o algunos de los atributos de la tabla. Ordenan los datos por la clave de partición de la tabla, pero usan una clave de clasificación diferente.

El uso de un índice secundario local elimina la necesidad de escanear toda la tabla y permite una consulta simple y rápida usando la clave de clasificación.

Todos los índices secundarios locales deben cumplir ciertas condiciones:

  • Clave de partición y clave de partición idénticas de la tabla de origen.
  • La clave de clasificación de un solo atributo escalar.
  • La proyección de la clave de clasificación de la tabla de origen, que actúa como un atributo no clave.

Todos los índices secundarios locales contienen automáticamente claves de partición y clasificación de las tablas principales. En las consultas, esto significa recopilar atributos predecibles de manera eficiente, así como recuperar atributos no diseñados.

El límite de almacenamiento para el índice secundario local sigue siendo de 10 GB por valor de clave de partición, que incluye todas las entradas de la tabla y las entradas de índice que comparten el valor de la clave de partición.

Diseño de atributos

Algunas operaciones requieren lectura / recuperación redundante debido a su complejidad. Estas operaciones pueden consumir un ancho de banda significativo. La proyección evita el costoso muestreo y las consultas complejas al aislar estos atributos. Recuerde que las proyecciones se componen de atributos copiados a un índice secundario.

Cuando crea un índice secundario, especifica los atributos proyectados. Recuerde las tres opciones que ofrece DynamoDB: KEYS_ONLY, INCLUDE y ALL

Al elegir atributos específicos en la proyección, considere las compensaciones de costos asociadas:

  • Si proyecta solo un pequeño subconjunto de los atributos requeridos, puede reducir significativamente los costos de almacenamiento.

  • Si proyecta atributos que no son clave de uso común, compensa los costos de escaneo con los costos de almacenamiento.

  • Si proyecta la mayoría o todos los atributos que no son clave, esto maximiza la flexibilidad y reduce el ancho de banda (sin recuperación); sin embargo, los costos de almacenamiento están aumentando.

  • Si proyecta KEYS_ONLY para operaciones frecuentes de escritura / actualización y consultas poco frecuentes, minimiza el tamaño pero mantiene la preparación de consultas.

Creando un índice secundario local

Usar LocalSecondaryIndex el parámetro CreateTable para crear uno o más índices secundarios locales. Se debe especificar un atributo que no sea de clave para la clave de clasificación. Cuando crea una tabla, crea índices secundarios locales. Al eliminarlos, eliminas estos índices.

Las tablas con un índice secundario local deben cumplir con el límite de tamaño de 10 GB por valor de clave de partición, pero pueden almacenar cualquier cantidad de elementos.

Consultas y análisis de índices secundarios locales

Una operación de consulta en índices secundarios locales devuelve todos los elementos con el mismo valor de clave de partición cuando varios elementos del índice comparten valores de clave de clasificación comunes. Los artículos elegibles no se devolverán en un pedido específico. Las consultas de índice secundario local utilizan consistencia finita o fuerte, con lecturas de consistencia fuerte que brindan los valores más recientes.

La operación de escaneo devuelve todos los datos del índice secundario local. Para las exploraciones, debe especificar el nombre de la tabla y el índice, y habilitar el uso de una expresión de filtro para eliminar datos.

Artículos de escritura

Cuando crea un índice secundario local, especifica el atributo de clave de clasificación y su tipo de datos. Cuando escribe un elemento, su tipo debe coincidir con el tipo de datos del esquema de clave si el elemento define un atributo de clave de índice.

DynamoDB no impone una relación de uno a uno entre los elementos de la tabla y los elementos del índice secundario local. Las tablas con múltiples índices secundarios locales incurren en mayores costos de escritura que las tablas con índices más pequeños.

Consideraciones de rendimiento en índices secundarios locales

El consumo de capacidad de lectura para una solicitud depende de la naturaleza del acceso a los datos. Las consultas usan consistencia finita o estricta, con lecturas fuertemente consistentes usando una unidad versus media unidad para lecturas finalmente consistentes.

Las limitaciones de los resultados incluyen un tamaño máximo de 1 MB. Los tamaños de los resultados son la suma de los tamaños de los elementos de índice correspondientes, redondeados al 4 KB más cercano, y el tamaño del elemento de la tabla correspondiente también se redondea al 4 KB más cercano.

El consumo de capacidad de grabación permanece dentro de las unidades asignadas. Calcule el costo total asignado encontrando la suma de las unidades consumidas al escribir la tabla y las unidades consumidas al actualizar los índices.

También puede considerar los factores clave que afectan el costo, algunos de los cuales pueden ser:

  • Cuando escribe un elemento que define un atributo indexado o actualiza un elemento para definir un atributo indexado indefinido, se produce una única operación de escritura.

  • Cuando una actualización de una tabla cambia el valor de un atributo clave indexado, se realizan dos entradas para eliminar y luego agregar un elemento.

  • Cuando un registro hace que se elimine un atributo indexado, se produce un registro para eliminar la proyección anterior del elemento.

  • Si el elemento no existe en el índice antes o después de la actualización, no se realizan entradas.

Almacén de índice secundario local

Cuando escribe un elemento de tabla, DynamoDB copia automáticamente el conjunto correcto de atributos en los índices secundarios locales necesarios. Esto cargará su cuenta. El espacio utilizado es la suma del tamaño del byte de la clave principal de la tabla, el tamaño del byte del atributo de la clave del índice, cualquier tamaño del byte del atributo proyectado existente y 100 bytes de sobrecarga para cada entrada del índice.

La puntuación de almacenamiento se obtiene estimando el tamaño medio del elemento del índice y multiplicándolo por la cardinalidad de la tabla.

Uso de Java para trabajar con índices secundarios locales

Cree un índice secundario local creando primero una instancia de la clase DynamoDB. Luego, cree una instancia de la clase CreateTableRequest con la información de solicitud requerida. Finalmente, use el método createTable.

Ejemplo

DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient( 
   new ProfileCredentialsProvider()));
String tableName = "Tools";  
CreateTableRequest createTableRequest = new 
   CreateTableRequest().withTableName(tableName);
   
//Provisioned Throughput
createTableRequest.setProvisionedThroughput (
   new ProvisionedThroughput()
 .withReadCapacityUnits((long)5)
 .withWriteCapacityUnits(( long)5));
   
//Attributes 
ArrayList<AttributeDefinition> attributeDefinitions = 
   new ArrayList<AttributeDefinition>();
   attributeDefinitions.add(new AttributeDefinition()
 .withAttributeName("Make")
 .withAttributeType("S"));
   
attributeDefinitions.add(new AttributeDefinition()
 .withAttributeName("Model")
 .withAttributeType("S"));
   
attributeDefinitions.add(new AttributeDefinition()
 .withAttributeName("Line")
 .withAttributeType("S"));
   
createTableRequest.setAttributeDefinitions(attributeDefinitions);

//Key Schema 
ArrayList<KeySchemaElement> tableKeySchema = new 
   ArrayList<KeySchemaElement>();
   
tableKeySchema.add(new KeySchemaElement()
 .withAttributeName("Make")
 .withKeyType(KeyType.HASH));                    //Partition key
   
tableKeySchema.add(new KeySchemaElement()
 .withAttributeName("Model")
 .withKeyType(KeyType.RANGE));                   //Sort key
   
createTableRequest.setKeySchema(tableKeySchema);
ArrayList<KeySchemaElement> indexKeySchema = new 
   ArrayList<KeySchemaElement>();
   
indexKeySchema.add(new KeySchemaElement()
 .withAttributeName("Make")
 .withKeyType(KeyType.HASH));                   //Partition key
   
indexKeySchema.add(new KeySchemaElement()
 .withAttributeName("Line")
 .withKeyType(KeyType.RANGE));                   //Sort key
   
Projection projection = new Projection()
 .withProjectionType(ProjectionType.INCLUDE);

ArrayList<String> nonKeyAttributes = new ArrayList<String>(); 
nonKeyAttributes.add("Type"); 
nonKeyAttributes.add("Year"); 
projection.setNonKeyAttributes(nonKeyAttributes);  

LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex() 
 .withIndexName("ModelIndex")
 .withKeySchema(indexKeySchema)
 .withProjection(p rojection);  

ArrayList<LocalSecondaryIndex> localSecondaryIndexes = new 
   ArrayList<LocalSecondaryIndex>(); 

localSecondaryIndexes.add(localSecondaryIndex); 
createTableRequest.setLocalSecondaryIndexes(localSecondaryIndexes);  
Table table = dynamoDB.createTable(createTableRequest); 
System.out.println(table.getDescription());

Obtenga información sobre el índice secundario local mediante el método de descripción. Simplemente cree una instancia de la clase DynamoDB, cree una instancia de la clase Table y pase la tabla al método de descripción.

Ejemplo

DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient( 
   new ProfileCredentialsProvider()));
   
String tableName = "Tools";
Table table = dynamoDB.getTable(tableName);
TableDescription tableDescription = table.describe();

List<LocalSecondaryIndexDescription> localSecondaryIndexes = 
   tableDescription.getLocalSecondaryIndexes();
   
Iterator<LocalSecondaryIndexDescription> lsiIter = 
   localSecondaryIndexes.iterator();
   
while (lsiIter.hasNext()) {  
   LocalSecondaryIndexDescription lsiDescription = lsiIter.next(); 
   System.out.println("Index info " + lsiDescription.getIndexName() + ":"); 
   Iterator<KeySchemaElement> kseIter = lsiDescription.getKeySchema().iterator(); 
   
   while (kseIter.hasNext()) { 
      KeySchemaElement kse = kseIter.next(); 
      System.out.printf("t%s: %sn", kse.getAttributeName(), kse.getKeyType()); 
   }
   
   Projection projection = lsiDescription.getProjection(); 
   System.out.println("tProjection type: " + projection.getProjectionType()); 
   
   if (projection.getProjectionType().toString().equals("INCLUDE")) { 
      System.out.println("ttNon-key projected attributes: " + 
         projection.getNonKeyAttributes()); 
   } 
}

Ejecute la consulta siguiendo los mismos pasos que la consulta de la tabla. Simplemente cree una instancia de la clase DynamoDB, una instancia de una clase Table, una instancia de una clase Index, un objeto de consulta y utilice el método de consulta.

Ejemplo

DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient( 
   new ProfileCredentialsProvider()));
   
String tableName = "Tools";  
Table table = dynamoDB.getTable(tableName); 
Index index = table.getIndex("LineIndex");  
QuerySpec spec = new QuerySpec() 
 .withKeyConditionExpression("Make = :v_make and Line = :v_line") 
 .withValueMap(new ValueMap() 
 .withString(":v_make", "Depault") 
 .withString(":v_line", "SuperSawz"));
      
ItemCollection<QueryOutcome> items = index.query(spec);
Iterator<Item> itemsIter = items.iterator();

while (itemsIter.hasNext()) { 
   Item item = itemsIter.next(); 
   System.out.println(item.toJSONPretty()); 
}

También puede ver el siguiente ejemplo.

Nota – En el siguiente ejemplo, se puede utilizar una fuente de datos creada previamente. Antes de intentar ejecutar, compre bibliotecas de soporte y cree las fuentes de datos requeridas (tablas con las características requeridas u otras fuentes a las que se hace referencia).

El siguiente ejemplo también utiliza el IDE de Eclipse, el archivo de credenciales de AWS y el kit de herramientas de AWS como parte del proyecto Java de Eclipse AWS.

Ejemplo

import java.util.ArrayList;
import java.util.Iterator;

import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;

import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Index;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.ItemCollection;
import com.amazonaws.services.dynamodbv2.document.PutItemOutcome;
import com.amazonaws.services.dynamodbv2.document.QueryOutcome;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
import com.amazonaws.services.dynamodbv2.document.utils.ValueMap;

import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.LocalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.Projection;
import com.amazonaws.services.dynamodbv2.model.ProjectionType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodbv2.model.ReturnConsumedCapacity;
import com.amazonaws.services.dynamodbv2.model.Select;

public class LocalSecondaryIndexSample {  
   static DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient( 
      new ProfileCredentialsProvider()));  
   public static String tableName = "ProductOrders";  
   
   public static void main(String[] args) throws Exception {  
      createTable();
      query(null); 
      query("IsOpenIndex"); 
      query("OrderCreationDateIndex"); 
   }
   public static void createTable() { 
      CreateTableRequest createTableRequest = new CreateTableRequest() 
       .withTableName(tableName) 
       .withProvisionedThroughput(new ProvisionedThroughput() 
       .withReadCapacityUnits((long) 1) 
       .withWriteCapacityUnits((long) 1));
         
      // Table partition and sort keys attributes 
      ArrayList<AttributeDefinition> attributeDefinitions = new 
         ArrayList<AttributeDefinition>(); 
      
      attributeDefinitions.add(new AttributeDefinition() 
       .withAttributeName("CustomerID") 
       .withAttributeType("S"));
         
      attributeDefinitions.add(new AttributeDefinition() 
       .withAttributeName("OrderID") 
       .withAttributeType("N"));
         
      // Index primary key attributes 
      attributeDefinitions.add(new AttributeDefinition() 
       .withAttributeName("OrderDate") 
       .withAttributeType("N"));
         
      attributeDefinitions.add(new AttributeDefinition() 
       .withAttributeName("OpenStatus") 
       .withAttributeType("N"));  
      createTableRequest.setAttributeDefinitions(attributeDefinitions);
      
      // Table key schema 
      ArrayList<KeySchemaElement> tableKeySchema = new
         ArrayList<KeySchemaElement>(); 
      tableKeySchema.add(new KeySchemaElement()  
       .withAttributeName("CustomerID") 
       .withKeyType(KeyType.HASH));                    //Partition key
         
      tableKeySchema.add(new KeySchemaElement() 
       .withAttributeName("OrderID") 
       .withKeyType(KeyType.RANGE));                   //Sort key
         
      createTableRequest.setKeySchema(tableKeySchema);  
      ArrayList<LocalSecondaryIndex> localSecondaryIndexes = new 
         ArrayList<LocalSecondaryIndex>();  
      
      // OrderDateIndex 
      LocalSecondaryIndex orderDateIndex = new LocalSecondaryIndex() 
       .withIndexName("OrderDateIndex");
         
      // OrderDateIndex key schema 
      ArrayList<KeySchemaElement> indexKeySchema = new 
         ArrayList<KeySchemaElement>(); 
      indexKeySchema.add(new KeySchemaElement() 
       .withAttributeName("CustomerID") 
       .withKeyType(KeyType.HASH));                   //Partition key
         
      indexKeySchema.add(new KeySchemaElement() 
       .withAttributeName("OrderDate") 
       .withKeyType(KeyType.RANGE));                   //Sort key
      orderDateIndex.setKeySchema(indexKeySchema);
      
      // OrderCreationDateIndex projection w/attributes list 
      Projection projection = new Projection() 
       .withProjectionType(ProjectionType.INCLUDE); 
      
      ArrayList<String> nonKeyAttributes = new ArrayList<String>(); 
      nonKeyAttributes.add("ProdCat"); 
      nonKeyAttributes.add("ProdNomenclature"); 
      projection.setNonKeyAttributes(nonKeyAttributes);
      orderCreationDateIndex.setProjection(projection);  
      localSecondaryIndexes.add(orderDateIndex);  
      
      // IsOpenIndex 
      LocalSecondaryIndex isOpenIndex = new LocalSecondaryIndex() 
       .withIndexName("IsOpenIndex");  
      
      // OpenStatusIndex key schema 
      indexKeySchema = new ArrayList<KeySchemaElement>(); 
      indexKeySchema.add(new KeySchemaElement() 
       .withAttributeName("CustomerID") 
       .withKeyType(KeyType.HASH));                   //Partition key
         
      indexKeySchema.add(new KeySchemaElement() 
       .withAttributeName("OpenStatus") 
       .withKeyType(KeyType.RANGE));                   //Sort key
         
      // OpenStatusIndex projection 
      projection = new Projection().withProjectionType(ProjectionType.ALL);  
      OpenStatusIndex.setKeySchema(indexKeySchema); 
      OpenStatusIndex.setProjection(projection);  
      localSecondaryIndexes.add(OpenStatusIndex);  
      
      // Put definitions in CreateTable request 
      createTableRequest.setLocalSecondaryIndexes(localSecondaryIndexes);  
      System.out.println("Spawning table " + tableName + "..."); 
      System.out.println(dynamoDB.createTable(createTableRequest));  
      
      // Pause for ACTIVE status 
      System.out.println("Waiting for ACTIVE table:" + tableName); 
      try { 
         Table table = dynamoDB.getTable(tableName);
         table.waitForActive(); 
      } catch (InterruptedException e) { 
         e.printStackTrace(); 
      } 
   }
   public static void query(String indexName) {  
      Table table = dynamoDB.getTable(tableName);  
      System.out.println("n*************************************************n"); 
      System.out.println("Executing query on" + tableName);  
      QuerySpec querySpec = new QuerySpec() 
       .withConsistentRead(true) 
       .withScanIndexForward(true) 
       .withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);
      
      if (indexName == "OpenStatusIndex") {  
         System.out.println("nEmploying index: '" + indexName 
            + "' open orders for this customer.");
            
         System.out.println( 
            "Returns only user-specified attribute listn"); 
         Index index = table.getIndex(indexName); 
             
         querySpec.withKeyConditionExpression("CustomerID = :v_custmid and 
            OpenStatus = :v_openstat") 
          .withValueMap(new ValueMap() 
          .withString(":v_custmid", "[email protected]") 
          .withNumber(":v_openstat", 1));  
         
         querySpec.withProjectionExpression( 
            "OrderDate, ProdCat, ProdNomenclature, OrderStatus"); 
            ItemCollection<QueryOutcome> items = index.query(querySpec); 
            Iterator<Item> iterator = items.iterator();  
            System.out.println("Printing query results...");  
            
         while (iterator.hasNext()) { 
            System.out.println(iterator.next().toJSONPretty()); 
         }  
      } else if (indexName == "OrderDateIndex") { 
         System.out.println("nUsing index: '" + indexName 
            + "': this customer's orders placed after 05/22/2016."); 
         System.out.println("Projected attributes are returnedn"); 
         Index index = table.getIndex(indexName); 
             
         querySpec.withKeyConditionExpression("CustomerID = :v_custmid and OrderDate 
            >= :v_ordrdate") 
          .withValueMap(new ValueMap() 
          .withString(":v_custmid", "[email protected]") 
          .withNumber(":v_ordrdate", 20160522));
               
         querySpec.withSelect(Select.ALL_PROJECTED_ATTRIBUTES);  
         ItemCollection<QueryOutcome> items = index.query(querySpec); 
         Iterator<Item> iterator = items.iterator();  
         System.out.println("Printing query results...");  
            
         while (iterator.hasNext()) { 
            System.out.println(iterator.next().toJSONPretty()); 
         }  
      } else { 
         System.out.println("nNo index: All Jane's orders by OrderID:n"); 
         querySpec.withKeyConditionExpression("CustomerID = :v_custmid") 
          .withValueMap(new ValueMap()
          .withString(":v_custmid", "[email protected]"));  
         
         ItemCollection<QueryOutcome> items = table.query(querySpec); 
         Iterator<Item> iterator = items.iterator();  
         System.out.println("Printing query results...");  
         
         while (iterator.hasNext()) { 
            System.out.println(iterator.next().toJSONPretty()); 
         } 
      } 
   } 
}

🚫