DynamoDB: índices secundarios locales

Algunas aplicaciones solo realizan consultas con la clave principal, pero algunas situaciones se benefician de una clave de clasificación alternativa. Permita que su aplicación elija mediante la creación de uno o varios índices secundarios locales.

Los requisitos de acceso a datos complejos, como combinar millones de elementos, hacen necesario realizar consultas / análisis más eficientes. Los índices secundarios locales proporcionan una clave de clasificación alternativa para un valor de clave de partición. También contienen copias de todos o algunos atributos de la tabla. Organizan los datos por clave de partición de 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 una clave de clasificación.

Todos los índices secundarios locales deben satisfacer ciertas condiciones:

  • Clave de partición idéntica y clave de partición de la tabla de origen.
  • Una clave de clasificación de un solo atributo escalar.
  • 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 las claves de partición y clasificación de las tablas principales. En las consultas, esto significa la recopilación eficiente de atributos proyectados y también la recuperación de atributos no proyectados.

El límite de almacenamiento para un índice secundario local sigue siendo de 10 GB por valor de clave de partición, que incluye todos los elementos de la tabla y los elementos de índice que comparten un valor de clave de partición.

Proyectar un atributo

Algunas operaciones requieren un exceso de lecturas / recuperación debido a la complejidad. Estas operaciones pueden consumir un rendimiento sustancial. La proyección le permite evitar la búsqueda costosa y realizar consultas enriquecidas al aislar estos atributos. Recuerde que las proyecciones consisten en atributos copiados en un índice secundario.

Al hacer un índice secundario, especifica los atributos proyectados. Recuerde las tres opciones proporcionadas por DynamoDB:KEYS_ONLY, INCLUDE, and ALL.

Al optar por ciertos atributos en la proyección, considere las compensaciones de costos asociadas:

  • Si proyecta solo un pequeño conjunto de atributos necesarios, reduce drásticamente los costos de almacenamiento.

  • Si proyecta atributos que no son clave a los que se accede con frecuencia, compensa los costos de escaneo con los costos de almacenamiento.

  • Si proyecta la mayoría o todos los atributos no clave, esto maximiza la flexibilidad y reduce el rendimiento (sin recuperaciones); sin embargo, los costos de almacenamiento aumentan.

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

Creación de índice secundario local

Utilizar el LocalSecondaryIndexparámetro de CreateTable para hacer uno o varios índices secundarios locales. Debe especificar un atributo que no sea de clave para la clave de clasificación. En la creación de tablas, creas índices secundarios locales. Al eliminarlos, eliminas estos índices.

Las tablas con un índice secundario local deben respetar un límite de 10 GB de tamaño 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 un valor de clave de partición coincidente cuando varios elementos del índice comparten valores de clave de clasificación. Los artículos coincidentes no se devuelven en un orden determinado. Las consultas de índices secundarios locales utilizan una coherencia fuerte o eventual, con lecturas muy coherentes que proporcionan los valores más recientes.

Una operación de exploración devuelve todos los datos del índice secundario local. Los escaneos requieren que proporciones una tabla y un nombre de índice, y permiten el uso de una expresión de filtro para descartar datos.

Escritura de artículos

Al crear un índice secundario local, especifica un 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 una clave de índice.

DynamoDB no impone requisitos de relación uno a uno en los elementos de la tabla y los elementos del índice secundario local. Las tablas con múltiples índices secundarios locales conllevan costos de escritura más altos que las que tienen menos.

Consideraciones de rendimiento en índices secundarios locales

El consumo de capacidad de lectura de una consulta depende de la naturaleza del acceso a los datos. Las consultas utilizan una consistencia fuerte o eventual, con lecturas muy consistentes usando una unidad en comparación con la mitad de una unidad en lecturas eventualmente consistentes.

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

El consumo de capacidad de escritura permanece dentro de las unidades aprovisionadas. Calcule el costo total aprovisionado encontrando la suma de las unidades consumidas en la escritura de la tabla y las unidades consumidas al actualizar los índices.

También puede considerar los factores clave que influyen en 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 no definido, se produce una única operación de escritura.

  • Cuando una actualización de la tabla cambia un valor de atributo de clave indexado, ocurren dos escrituras para eliminar y luego agregar un elemento.

  • Cuando una escritura provoca la eliminación de un atributo indexado, se produce una escritura para eliminar la proyección del elemento antiguo.

  • Cuando un elemento no existe dentro del índice antes o después de una actualización, no se producen escrituras.

Almacenamiento de índice secundario local

En la escritura de un elemento de tabla, DynamoDB copia automáticamente el conjunto de atributos correcto en los índices secundarios locales necesarios. Esto carga su cuenta. El espacio utilizado es el resultado de la suma del tamaño de bytes de la clave primaria de la tabla, el tamaño del byte de atributo de la clave de índice, cualquier tamaño de byte de atributo proyectado presente y 100 bytes de sobrecarga para cada elemento de índice.

El almacenamiento estimado se obtiene estimando el tamaño medio de los elementos del índice y multiplicando por la cantidad de elementos de la tabla.

Uso de Java para trabajar con índices secundarios locales

Cree un índice secundario local creando primero una instancia de clase DynamoDB. Luego, cree una instancia de la clase CreateTableRequest con la información de solicitud necesaria. 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());

Recupere información sobre un índice secundario local con el método describe. Simplemente cree una instancia de clase DynamoDB, cree una instancia de clase Table y pase la tabla al método describe.

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: %s\n", kse.getAttributeName(), kse.getKeyType()); 
   }
   
   Projection projection = lsiDescription.getProjection(); 
   System.out.println("\tProjection type: " + projection.getProjectionType()); 
   
   if (projection.getProjectionType().toString().equals("INCLUDE")) { 
      System.out.println("\t\tNon-key projected attributes: " + 
         projection.getNonKeyAttributes()); 
   } 
}

Realice una consulta siguiendo los mismos pasos que una consulta de tabla. Simplemente cree una instancia de la clase DynamoDB, una instancia de la clase Table, una instancia de la 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 revisar el siguiente ejemplo.

Note- El siguiente ejemplo puede asumir una fuente de datos creada previamente. Antes de intentar ejecutar, adquiera bibliotecas de soporte y cree las fuentes de datos necesarias (tablas con las características requeridas u otras fuentes referenciadas).

El siguiente ejemplo también usa Eclipse IDE, un archivo de credenciales de AWS y AWS Toolkit dentro de un proyecto Eclipse AWS Java.

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 list\n"); 
         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 returned\n"); 
         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()); 
         } 
      } 
   } 
}