DynamoDB - Índices Secundários Locais

Alguns aplicativos realizam consultas apenas com a chave primária, mas algumas situações se beneficiam de uma chave de classificação alternativa. Permita que seu aplicativo escolha criando um ou vários índices secundários locais.

Requisitos complexos de acesso a dados, como combinar milhões de itens, tornam necessário realizar consultas / varreduras mais eficientes. Os índices secundários locais fornecem uma chave de classificação alternativa para um valor de chave de partição. Eles também mantêm cópias de todos ou alguns atributos da tabela. Eles organizam os dados por chave de partição de tabela, mas usam uma chave de classificação diferente.

Usar um índice secundário local elimina a necessidade de uma varredura de toda a tabela e permite uma consulta simples e rápida usando uma chave de classificação.

Todos os índices secundários locais devem satisfazer certas condições -

  • Chave de partição idêntica e chave de partição da tabela de origem.
  • Uma chave de classificação de apenas um atributo escalar.
  • Projeção da chave de classificação da tabela de origem agindo como um atributo não chave.

Todos os índices secundários locais mantêm automaticamente as chaves de partição e classificação das tabelas pai. Em consultas, isso significa coleta eficiente de atributos projetados e também recuperação de atributos não projetados.

O limite de armazenamento para um índice secundário local permanece 10 GB por valor de chave de partição, que inclui todos os itens de tabela e itens de índice que compartilham um valor de chave de partição.

Projetando um Atributo

Algumas operações exigem leituras / buscas em excesso devido à complexidade. Essas operações podem consumir uma taxa de transferência substancial. A projeção permite evitar buscas caras e realizar consultas valiosas, isolando esses atributos. Lembre-se de que as projeções consistem em atributos copiados em um índice secundário.

Ao fazer um índice secundário, você especifica os atributos projetados. Lembre-se das três opções fornecidas pelo DynamoDB:KEYS_ONLY, INCLUDE, and ALL.

Ao optar por certos atributos na projeção, considere as compensações de custo associadas -

  • Se você projetar apenas um pequeno conjunto de atributos necessários, reduzirá drasticamente os custos de armazenamento.

  • Se você projeta atributos não essenciais acessados ​​com frequência, compensa os custos de digitalização com os custos de armazenamento.

  • Se você projetar a maioria ou todos os atributos não-chave, isso maximiza a flexibilidade e reduz o rendimento (sem recuperações); no entanto, os custos de armazenamento aumentam.

  • Se você projetar KEYS_ONLY para gravações / atualizações frequentes e consultas não frequentes, ele minimiza o tamanho, mas mantém a preparação da consulta.

Criação de índice secundário local

Use o LocalSecondaryIndexparâmetro de CreateTable para fazer um único ou vários índices secundários locais. Você deve especificar um atributo não chave para a chave de classificação. Na criação da tabela, você cria índices secundários locais. Na exclusão, você exclui esses índices.

As tabelas com um índice secundário local devem obedecer a um limite de 10 GB de tamanho por valor de chave de partição, mas podem armazenar qualquer quantidade de itens.

Consultas e varreduras de índice secundário local

Uma operação de consulta em índices secundários locais retorna todos os itens com um valor de chave de partição correspondente quando vários itens no índice compartilham valores de chave de classificação. Os itens correspondentes não retornam em uma determinada ordem. As consultas para índices secundários locais usam consistência eventual ou forte, com leituras consistentes fornecendo os valores mais recentes.

Uma operação de varredura retorna todos os dados de índice secundário local. As varreduras exigem que você forneça um nome de tabela e índice e permita o uso de uma expressão de filtro para descartar dados.

Escrita de Item

Na criação de um índice secundário local, você especifica um atributo de chave de classificação e seu tipo de dados. Quando você escreve um item, seu tipo deve corresponder ao tipo de dados do esquema de chave, se o item definir um atributo de uma chave de índice.

O DynamoDB não impõe requisitos de relacionamento um a um nos itens da tabela e nos itens de índice secundário local. As tabelas com vários índices secundários locais acarretam custos de gravação mais altos do que aquelas com menos.

Considerações de taxa de transferência em índices secundários locais

O consumo da capacidade de leitura de uma consulta depende da natureza do acesso aos dados. As consultas usam consistência eventual ou forte, com leituras fortemente consistentes usando uma unidade em comparação com a metade de uma unidade em leituras eventualmente consistentes.

As limitações de resultados incluem um tamanho máximo de 1 MB. Os tamanhos dos resultados vêm da soma do tamanho do item do índice correspondente arredondado para o 4 KB mais próximo e o tamanho do item da tabela correspondente também arredondado para o 4 KB mais próximo.

O consumo da capacidade de gravação permanece dentro das unidades provisionadas. Calcule o custo total provisionado encontrando a soma das unidades consumidas na escrita da tabela e as unidades consumidas nos índices de atualização.

Você também pode considerar os principais fatores que influenciam o custo, alguns dos quais podem ser -

  • Quando você escreve um item definindo um atributo indexado ou atualiza um item para definir um atributo indexado indefinido, ocorre uma única operação de gravação.

  • Quando uma atualização de tabela altera um valor de atributo de chave indexado, duas gravações ocorrem para excluir e então - adicionar um item.

  • Quando uma gravação causa a exclusão de um atributo indexado, uma gravação ocorre para remover a projeção do item antigo.

  • Quando um item não existe no índice antes ou depois de uma atualização, nenhuma gravação ocorre.

Armazenamento de índice secundário local

Em uma gravação de item de tabela, o DynamoDB copia automaticamente o conjunto de atributos correto para os índices secundários locais necessários. Isso cobra sua conta. O espaço usado resulta da soma do tamanho do byte da chave primária da tabela, tamanho do byte do atributo da chave do índice, qualquer tamanho do byte do atributo projetado presente e 100 bytes de sobrecarga para cada item do índice.

O armazenamento estimado é obtido estimando o tamanho médio do item do índice e multiplicando pela quantidade do item da tabela.

Usando Java para trabalhar com índices secundários locais

Crie um índice secundário local criando primeiro uma instância de classe DynamoDB. Em seguida, crie uma instância de classe CreateTableRequest com as informações de solicitação necessárias. Finalmente, use o método createTable.

Exemplo

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 informações sobre um índice secundário local com o método describe. Basta criar uma instância da classe DynamoDB, criar uma instância da classe Table e passar a tabela para o método describe.

Exemplo

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()); 
   } 
}

Execute uma consulta usando as mesmas etapas de uma consulta de tabela. Basta criar uma instância de classe DynamoDB, uma instância de classe Table, uma instância de classe Index, um objeto de consulta e utilizar o método de consulta.

Exemplo

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()); 
}

Você também pode revisar o exemplo a seguir.

Note- O exemplo a seguir pode assumir uma fonte de dados criada anteriormente. Antes de tentar executar, adquira bibliotecas de suporte e crie as fontes de dados necessárias (tabelas com as características necessárias ou outras fontes referenciadas).

O exemplo a seguir também usa Eclipse IDE, um arquivo de credenciais AWS e o AWS Toolkit em um projeto Eclipse AWS Java.

Exemplo

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()); 
         } 
      } 
   } 
}