DynamoDB - índices secundários globais

Os aplicativos que exigem vários tipos de consulta com atributos diferentes podem usar um único ou vários índices secundários globais na execução dessas consultas detalhadas.

For example - Um sistema que mantém o controle dos usuários, seu status de login e o tempo de login. O crescimento do exemplo anterior retarda as consultas em seus dados.

Os índices secundários globais aceleram as consultas organizando uma seleção de atributos de uma tabela. Eles empregam chaves primárias na classificação de dados e não requerem atributos de tabela-chave ou esquema de chave idêntico ao da tabela.

Todos os índices secundários globais devem incluir uma chave de partição, com a opção de uma chave de classificação. O esquema da chave do índice pode ser diferente da tabela e os atributos da chave do índice podem usar qualquer string de nível superior, número ou atributos de tabela binários.

Em uma projeção, você pode usar outros atributos da tabela, no entanto, as consultas não são recuperadas das tabelas pai.

Projeções de Atributos

As projeções consistem em um conjunto de atributos copiado da tabela para o índice secundário. Uma projeção sempre ocorre com a chave de partição da tabela e a chave de classificação. Em consultas, as projeções permitem ao DynamoDB acessar qualquer atributo da projeção; eles existem essencialmente como sua própria mesa.

Em uma criação de índice secundário, você deve especificar atributos para projeção. O DynamoDB oferece três maneiras de realizar essa tarefa -

  • KEYS_ONLY- Todos os itens de índice consistem em partição de tabela e valores de chave de classificação e valores de chave de índice. Isso cria o menor índice.

  • INCLUDE - Inclui atributos KEYS_ONLY e atributos não chave especificados.

  • ALL - Inclui todos os atributos da tabela de origem, criando o maior índice possível.

Observe as vantagens e desvantagens de projetar atributos em um índice secundário global, que se relaciona ao rendimento e ao custo de armazenamento.

Considere os seguintes pontos -

  • Se você precisar acessar apenas alguns atributos, com baixa latência, projete apenas aqueles que você precisa. Isso reduz os custos de armazenamento e gravação.

  • Se um aplicativo acessa com frequência certos atributos não-chave, projete-os porque os custos de armazenamento são menores em comparação ao consumo de varredura.

  • Você pode projetar grandes conjuntos de atributos acessados ​​com frequência, no entanto, isso acarreta um alto custo de armazenamento.

  • Use KEYS_ONLY para consultas de tabela não frequentes e gravações / atualizações frequentes. Isso controla o tamanho, mas ainda oferece um bom desempenho nas consultas.

Consultas e varreduras de índice secundário global

Você pode utilizar consultas para acessar um único ou vários itens em um índice. Você deve especificar o índice e o nome da tabela, os atributos desejados e as condições; com a opção de retornar resultados em ordem crescente ou decrescente.

Você também pode utilizar varreduras para obter todos os dados de índice. Requer um nome de tabela e índice. Você utiliza uma expressão de filtro para recuperar dados específicos.

Sincronização de dados de tabela e índice

O DynamoDB executa automaticamente a sincronização de índices com sua tabela pai. Cada operação de modificação em itens causa atualizações assíncronas; no entanto, os aplicativos não gravam nos índices diretamente.

Você precisa entender o impacto da manutenção do DynamoDB nos índices. Na criação de um índice, você especifica atributos-chave e tipos de dados, o que significa que em uma gravação, esses tipos de dados devem corresponder aos tipos de dados do esquema principal.

Na criação ou exclusão do item, os índices são atualizados de maneira eventualmente consistente; no entanto, as atualizações dos dados se propagam em uma fração de segundo (a menos que ocorra falha do sistema de algum tipo). Você deve levar em conta esse atraso nas inscrições.

Throughput Considerations in Global Secondary Indexes- Vários índices secundários globais afetam a taxa de transferência. A criação de índice requer especificações de unidade de capacidade, que existem separadas da tabela, resultando em operações que consomem unidades de capacidade de índice em vez de unidades de tabela.

Isso pode resultar em limitação se uma consulta ou gravação exceder a taxa de transferência provisionada. Visualize as configurações de taxa de transferência usandoDescribeTable.

Read Capacity- Os índices secundários globais fornecem consistência eventual. Em consultas, o DynamoDB executa cálculos de provisão idênticos aos usados ​​para tabelas, com a única diferença de usar o tamanho da entrada do índice em vez do tamanho do item. O limite de retornos de uma consulta permanece 1 MB, que inclui o tamanho do nome do atributo e os valores em cada item retornado.

Capacidade de gravação

Quando ocorrem operações de gravação, o índice afetado consome unidades de gravação. Os custos de transferência de gravação são a soma das unidades de capacidade de gravação consumidas em gravações de tabela e unidades consumidas em atualizações de índice. Uma operação de gravação bem-sucedida requer capacidade suficiente ou resulta em limitação.

Os custos de gravação também dependem de certos fatores, alguns dos quais são os seguintes -

  • Novos itens que definem atributos indexados ou atualizações de itens que definem atributos indexados indefinidos usam uma única operação de gravação para adicionar o item ao índice.

  • As atualizações que alteram o valor do atributo de chave indexado usam duas gravações para excluir um item e gravar um novo.

  • Uma gravação de tabela que dispara a exclusão de um atributo indexado usa uma única gravação para apagar a projeção do item antigo no índice.

  • Os itens ausentes no índice antes e depois de uma operação de atualização não usam gravações.

  • As atualizações que mudam apenas o valor do atributo projetado no esquema da chave do índice, e não o valor do atributo da chave indexado, usam uma gravação para atualizar os valores dos atributos projetados no índice.

Todos esses fatores pressupõem um tamanho de item menor ou igual a 1 KB.

Armazenamento de índice secundário global

Na gravação de um item, o DynamoDB copia automaticamente o conjunto correto de atributos para quaisquer índices onde os atributos devam existir. Isso afeta sua conta, cobrando pelo armazenamento de itens de mesa e armazenamento de atributos. O espaço utilizado resulta da soma dessas quantidades -

  • Tamanho do byte da chave primária da tabela
  • Tamanho do byte do atributo da chave do índice
  • Tamanho de byte dos atributos projetados
  • 100 byte-overhead por item de índice

Você pode estimar as necessidades de armazenamento estimando o tamanho médio do item e multiplicando pela quantidade dos itens da tabela com os atributos-chave do índice secundário global.

O DynamoDB não grava dados de item para um item de tabela com um atributo indefinido definido como uma partição de índice ou chave de classificação.

Global Secondary Index Crud

Crie uma tabela com índices secundários globais usando o CreateTable operação emparelhada com o GlobalSecondaryIndexesparâmetro. Você deve especificar um atributo para servir como a chave de partição do índice ou usar outro para a chave de classificação do índice. Todos os atributos da chave do índice devem ser string, número ou escalares binários. Você também deve fornecer configurações de taxa de transferência, consistindo emReadCapacityUnits e WriteCapacityUnits.

Usar UpdateTable para adicionar índices secundários globais às tabelas existentes usando o parâmetro GlobalSecondaryIndexes mais uma vez.

Nesta operação, você deve fornecer as seguintes entradas -

  • Nome do índice
  • Esquema de chave
  • Atributos projetados
  • Configurações de rendimento

Ao adicionar um índice secundário global, pode levar um tempo substancial com tabelas grandes devido ao volume de itens, volume de atributos projetados, capacidade de gravação e atividade de gravação. UsarCloudWatch métricas para monitorar o processo.

Usar DescribeTablepara buscar informações de status para um índice secundário global. Ele retorna um de quatroIndexStatus para GlobalSecondaryIndexes -

  • CREATING - Indica o estágio de construção do índice e sua indisponibilidade.

  • ACTIVE - Indica a prontidão do índice para uso.

  • UPDATING - Indica o status de atualização das configurações de rendimento.

  • DELETING - Indica o status de exclusão do índice e sua indisponibilidade permanente para uso.

Atualize as configurações de rendimento provisionado do índice secundário global durante o estágio de carregamento / preenchimento (atributos de gravação do DynamoDB em um índice e rastreamento de itens adicionados / excluídos / atualizados). UsarUpdateTable para realizar esta operação.

Você deve se lembrar que não pode adicionar / excluir outros índices durante o estágio de preenchimento.

Use UpdateTable para excluir índices secundários globais. Ele permite a exclusão de apenas um índice por operação, no entanto, você pode executar várias operações simultaneamente, até cinco. O processo de exclusão não afeta as atividades de leitura / gravação da tabela pai, mas você não pode adicionar / excluir outros índices até que a operação seja concluída.

Usando Java para trabalhar com índices secundários globais

Crie uma tabela com um índice por meio de CreateTable. Basta criar uma instância de classe DynamoDB, umCreateTableRequest instância de classe para solicitar informações e passar o objeto de solicitação para o método CreateTable.

O programa a seguir é um pequeno exemplo -

DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient ( 
   new ProfileCredentialsProvider()));
   
// Attributes 
ArrayList<AttributeDefinition> attributeDefinitions = new 
   ArrayList<AttributeDefinition>();  
attributeDefinitions.add(new AttributeDefinition() 
   .withAttributeName("City") 
   .withAttributeType("S"));
   
attributeDefinitions.add(new AttributeDefinition() 
   .withAttributeName("Date") 
   .withAttributeType("S"));
   
attributeDefinitions.add(new AttributeDefinition() 
   .withAttributeName("Wind") 
   .withAttributeType("N"));
   
// Key schema of the table 
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>(); 
tableKeySchema.add(new KeySchemaElement()
   .withAttributeName("City") 
   .withKeyType(KeyType.HASH));              //Partition key
   
tableKeySchema.add(new KeySchemaElement() 
   .withAttributeName("Date") 
   .withKeyType(KeyType.RANGE));             //Sort key
   
// Wind index 
GlobalSecondaryIndex windIndex = new GlobalSecondaryIndex() 
   .withIndexName("WindIndex") 
   .withProvisionedThroughput(new ProvisionedThroughput() 
   .withReadCapacityUnits((long) 10) 
   .withWriteCapacityUnits((long) 1)) 
   .withProjection(new Projection().withProjectionType(ProjectionType.ALL));
   
ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>(); 
indexKeySchema.add(new KeySchemaElement() 
   .withAttributeName("Date") 
   .withKeyType(KeyType.HASH));              //Partition key
   
indexKeySchema.add(new KeySchemaElement() 
   .withAttributeName("Wind") 
   .withKeyType(KeyType.RANGE));             //Sort key
   
windIndex.setKeySchema(indexKeySchema);  
CreateTableRequest createTableRequest = new CreateTableRequest() 
   .withTableName("ClimateInfo") 
   .withProvisionedThroughput(new ProvisionedThroughput() 
   .withReadCapacityUnits((long) 5) 
   .withWriteCapacityUnits((long) 1))
   .withAttributeDefinitions(attributeDefinitions) 
   .withKeySchema(tableKeySchema) 
   .withGlobalSecondaryIndexes(windIndex); 
Table table = dynamoDB.createTable(createTableRequest); 
System.out.println(table.getDescription());

Recupere as informações do índice com DescribeTable. Primeiro, crie uma instância de classe DynamoDB. Em seguida, crie uma instância da classe Table para direcionar um índice. Finalmente, passe a tabela para o método de descrição.

Aqui está um pequeno exemplo -

DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient ( 
   new ProfileCredentialsProvider()));
   
Table table = dynamoDB.getTable("ClimateInfo"); 
TableDescription tableDesc = table.describe();  
Iterator<GlobalSecondaryIndexDescription> gsiIter = 
   tableDesc.getGlobalSecondaryIndexes().iterator(); 

while (gsiIter.hasNext()) { 
   GlobalSecondaryIndexDescription gsiDesc = gsiIter.next(); 
   System.out.println("Index data " + gsiDesc.getIndexName() + ":");  
   Iterator<KeySchemaElement> kse7Iter = gsiDesc.getKeySchema().iterator(); 
   
   while (kseIter.hasNext()) { 
      KeySchemaElement kse = kseIter.next(); 
      System.out.printf("\t%s: %s\n", kse.getAttributeName(), kse.getKeyType()); 
   }
   Projection projection = gsiDesc.getProjection(); 
   System.out.println("\tProjection type: " + projection.getProjectionType()); 
   
   if (projection.getProjectionType().toString().equals("INCLUDE")) { 
      System.out.println("\t\tNon-key projected attributes: " 
         + projection.getNonKeyAttributes()); 
   } 
}

Use Query para realizar uma consulta de índice como uma consulta de tabela. Basta criar uma instância de classe DynamoDB, uma instância de classe Table para o índice de destino, uma instância de classe Index para o índice específico e passar o índice e o objeto de consulta para o método de consulta.

Dê uma olhada no código a seguir para entender melhor -

DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient ( 
   new ProfileCredentialsProvider()));
   
Table table = dynamoDB.getTable("ClimateInfo"); 
Index index = table.getIndex("WindIndex");  
QuerySpec spec = new QuerySpec() 
   .withKeyConditionExpression("#d = :v_date and Wind = :v_wind") 
   .withNameMap(new NameMap() 
   .with("#d", "Date"))
   .withValueMap(new ValueMap() 
   .withString(":v_date","2016-05-15") 
   .withNumber(":v_wind",0));
   
ItemCollection<QueryOutcome> items = index.query(spec);
Iterator<Item> iter = items.iterator();

while (iter.hasNext()) {
   System.out.println(iter.next().toJSONPretty()); 
}

O programa a seguir é um exemplo maior para melhor compreensão -

Note- O programa 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).

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

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.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.GlobalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.Projection;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

public class GlobalSecondaryIndexSample {  
   static DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient ( 
      new ProfileCredentialsProvider()));  
   public static String tableName = "Bugs";   
   public static void main(String[] args) throws Exception {  
      createTable(); 
      queryIndex("CreationDateIndex"); 
      queryIndex("NameIndex"); 
      queryIndex("DueDateIndex"); 
   }
   public static void createTable() {  
      // Attributes 
      ArrayList<AttributeDefinition> attributeDefinitions = new 
         ArrayList<AttributeDefinition>();  
      attributeDefinitions.add(new AttributeDefinition()
         .withAttributeName("BugID") 
         .withAttributeType("S")); 
         
      attributeDefinitions.add(new AttributeDefinition() 
         .withAttributeName("Name")
         .withAttributeType("S"));
         
      attributeDefinitions.add(new AttributeDefinition() 
         .withAttributeName("CreationDate")
         .withAttributeType("S"));
         
      attributeDefinitions.add(new AttributeDefinition() 
         .withAttributeName("DueDate") 
         .withAttributeType("S"));
         
      // Table Key schema
      ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>(); 
      tableKeySchema.add (new KeySchemaElement() 
         .withAttributeName("BugID") 
         .withKeyType(KeyType.HASH));              //Partition key 
      
      tableKeySchema.add (new KeySchemaElement() 
         .withAttributeName("Name") 
         .withKeyType(KeyType.RANGE));             //Sort key
         
      // Indexes' initial provisioned throughput
      ProvisionedThroughput ptIndex = new ProvisionedThroughput()
         .withReadCapacityUnits(1L)
         .withWriteCapacityUnits(1L);
         
      // CreationDateIndex 
      GlobalSecondaryIndex creationDateIndex = new GlobalSecondaryIndex() 
         .withIndexName("CreationDateIndex") 
         .withProvisionedThroughput(ptIndex) 
         .withKeySchema(new KeySchemaElement() 
         .withAttributeName("CreationDate") 
         .withKeyType(KeyType.HASH),               //Partition key 
         new KeySchemaElement()
         .withAttributeName("BugID") 
         .withKeyType(KeyType.RANGE))              //Sort key 
         .withProjection(new Projection() 
         .withProjectionType("INCLUDE") 
         .withNonKeyAttributes("Description", "Status"));
         
      // NameIndex 
      GlobalSecondaryIndex nameIndex = new GlobalSecondaryIndex() 
         .withIndexName("NameIndex") 
         .withProvisionedThroughput(ptIndex) 
         .withKeySchema(new KeySchemaElement()  
         .withAttributeName("Name")  
         .withKeyType(KeyType.HASH),                  //Partition key 
         new KeySchemaElement()  
         .withAttributeName("BugID")  
         .withKeyType(KeyType.RANGE))                 //Sort key 
         .withProjection(new Projection() 
         .withProjectionType("KEYS_ONLY"));
         
      // DueDateIndex 
      GlobalSecondaryIndex dueDateIndex = new GlobalSecondaryIndex() 
         .withIndexName("DueDateIndex") 
         .withProvisionedThroughput(ptIndex) 
         .withKeySchema(new KeySchemaElement() 
         .withAttributeName("DueDate") 
         .withKeyType(KeyType.HASH))               //Partition key 
         .withProjection(new Projection() 
         .withProjectionType("ALL"));
         
      CreateTableRequest createTableRequest = new CreateTableRequest() 
         .withTableName(tableName) 
         .withProvisionedThroughput( new ProvisionedThroughput() 
         .withReadCapacityUnits( (long) 1) 
         .withWriteCapacityUnits( (long) 1)) 
         .withAttributeDefinitions(attributeDefinitions)
         .withKeySchema(tableKeySchema)
         .withGlobalSecondaryIndexes(creationDateIndex, nameIndex, dueDateIndex);  
         System.out.println("Creating " + tableName + "..."); 
         dynamoDB.createTable(createTableRequest);  
      
      // Pause for active table state 
      System.out.println("Waiting for ACTIVE state of " + tableName); 
      try { 
         Table table = dynamoDB.getTable(tableName); 
         table.waitForActive(); 
      } catch (InterruptedException e) { 
         e.printStackTrace(); 
      } 
   }
   public static void queryIndex(String indexName) { 
      Table table = dynamoDB.getTable(tableName);  
      System.out.println 
      ("\n*****************************************************\n"); 
      System.out.print("Querying index " + indexName + "...");  
      Index index = table.getIndex(indexName);  
      ItemCollection<QueryOutcome> items = null; 
      QuerySpec querySpec = new QuerySpec();  
      
      if (indexName == "CreationDateIndex") { 
         System.out.println("Issues filed on 2016-05-22"); 
         querySpec.withKeyConditionExpression("CreationDate = :v_date and begins_with
            (BugID, :v_bug)") 
            .withValueMap(new ValueMap() 
            .withString(":v_date","2016-05-22")
            .withString(":v_bug","A-")); 
         items = index.query(querySpec); 
      } else if (indexName == "NameIndex") { 
         System.out.println("Compile error"); 
         querySpec.withKeyConditionExpression("Name = :v_name and begins_with
            (BugID, :v_bug)") 
            .withValueMap(new ValueMap() 
            .withString(":v_name","Compile error") 
            .withString(":v_bug","A-")); 
         items = index.query(querySpec); 
      } else if (indexName == "DueDateIndex") { 
         System.out.println("Items due on 2016-10-15"); 
         querySpec.withKeyConditionExpression("DueDate = :v_date") 
         .withValueMap(new ValueMap() 
         .withString(":v_date","2016-10-15")); 
         items = index.query(querySpec); 
      } else { 
         System.out.println("\nInvalid index name"); 
         return; 
      }  
      Iterator<Item> iterator = items.iterator(); 
      System.out.println("Query: getting result..."); 
      
      while (iterator.hasNext()) { 
         System.out.println(iterator.next().toJSONPretty()); 
      } 
   } 
}