DynamoDB - Indici secondari globali

Le applicazioni che richiedono vari tipi di query con attributi diversi possono utilizzare uno o più indici secondari globali nell'esecuzione di queste query dettagliate.

For example - Un sistema che tiene traccia degli utenti, del loro stato di accesso e del tempo di accesso. La crescita dell'esempio precedente rallenta le query sui suoi dati.

Gli indici secondari globali accelerano le query organizzando una selezione di attributi da una tabella. Impiegano chiavi primarie nell'ordinamento dei dati e non richiedono attributi di tabella chiave o schema di chiavi identico alla tabella.

Tutti gli indici secondari globali devono includere una chiave di partizione, con l'opzione di una chiave di ordinamento. Lo schema della chiave dell'indice può differire dalla tabella e gli attributi della chiave dell'indice possono utilizzare qualsiasi stringa, numero o attributo di tabella binaria di primo livello.

In una proiezione, è possibile utilizzare altri attributi di tabella, tuttavia, le query non vengono recuperate dalle tabelle padre.

Proiezioni degli attributi

Le proiezioni consistono in un set di attributi copiato dalla tabella all'indice secondario. Una proiezione si verifica sempre con la chiave di partizione della tabella e la chiave di ordinamento. Nelle query, le proiezioni consentono a DynamoDB di accedere a qualsiasi attributo della proiezione; esistono essenzialmente come il loro tavolo.

Nella creazione di un indice secondario, è necessario specificare gli attributi per la proiezione. DynamoDB offre tre modi per eseguire questa attività:

  • KEYS_ONLY- Tutti gli elementi dell'indice sono costituiti dalla partizione della tabella e dai valori delle chiavi di ordinamento e dai valori delle chiavi dell'indice. Questo crea l'indice più piccolo.

  • INCLUDE - Include attributi KEYS_ONLY e attributi non chiave specificati.

  • ALL - Include tutti gli attributi della tabella di origine, creando l'indice più grande possibile.

Notare i compromessi nella proiezione degli attributi in un indice secondario globale, che si riferiscono alla velocità effettiva e ai costi di archiviazione.

Considera i seguenti punti:

  • Se hai solo bisogno di accedere ad alcuni attributi, con bassa latenza, proietta solo quelli che ti servono. Ciò riduce i costi di archiviazione e scrittura.

  • Se un'applicazione accede frequentemente a determinati attributi non chiave, proiettarli perché i costi di archiviazione impallidiscono rispetto al consumo di scansione.

  • È possibile proiettare grandi set di attributi a cui si accede frequentemente, tuttavia, ciò comporta un costo di archiviazione elevato.

  • Utilizza KEYS_ONLY per query di tabella poco frequenti e scritture / aggiornamenti frequenti. Questo controlla le dimensioni, ma offre comunque buone prestazioni nelle query.

Query e scansioni dell'indice secondario globale

È possibile utilizzare query per accedere a uno o più elementi in un indice. È necessario specificare l'indice e il nome della tabella, gli attributi desiderati e le condizioni; con la possibilità di restituire i risultati in ordine crescente o decrescente.

È inoltre possibile utilizzare le scansioni per ottenere tutti i dati dell'indice. Richiede il nome della tabella e dell'indice. Si utilizza un'espressione di filtro per recuperare dati specifici.

Sincronizzazione dei dati di tabelle e indici

DynamoDB esegue automaticamente la sincronizzazione sugli indici con la loro tabella padre. Ogni operazione di modifica sugli elementi causa aggiornamenti asincroni, tuttavia, le applicazioni non scrivono direttamente negli indici.

È necessario comprendere l'impatto della manutenzione di DynamoDB sugli indici. Alla creazione di un indice, si specificano attributi chiave e tipi di dati, il che significa che in una scrittura, quei tipi di dati devono corrispondere ai tipi di dati dello schema chiave.

Alla creazione o all'eliminazione di un elemento, gli indici si aggiornano in modo eventualmente coerente, tuttavia, gli aggiornamenti ai dati si propagano in una frazione di secondo (a meno che non si verifichi un errore di sistema di qualche tipo). È necessario tenere conto di questo ritardo nelle applicazioni.

Throughput Considerations in Global Secondary Indexes- Più indici secondari globali influiscono sulla produttività. La creazione di un indice richiede specifiche di unità di capacità, che esistono separate dalla tabella, con il risultato che le operazioni consumano unità di capacità dell'indice anziché unità di tabella.

Ciò può causare la limitazione se una query o una scrittura supera la velocità effettiva fornita. Visualizza le impostazioni di velocità effettiva utilizzandoDescribeTable.

Read Capacity- Gli indici secondari globali forniscono un'eventuale coerenza. Nelle query, DynamoDB esegue calcoli di fornitura identici a quelli utilizzati per le tabelle, con l'unica differenza nell'utilizzo della dimensione della voce di indice anziché della dimensione dell'elemento. Il limite di una query restituita rimane 1 MB, che include la dimensione del nome dell'attributo e i valori in ogni elemento restituito.

Capacità di scrittura

Quando si verificano operazioni di scrittura, l'indice interessato consuma unità di scrittura. I costi della velocità effettiva di scrittura sono la somma delle unità di capacità di scrittura utilizzate nelle scritture della tabella e delle unità consumate negli aggiornamenti dell'indice. Un'operazione di scrittura riuscita richiede una capacità sufficiente o provoca una limitazione.

Anche i costi di scrittura dipendono da alcuni fattori, alcuni dei quali sono i seguenti:

  • I nuovi elementi che definiscono gli attributi indicizzati o gli aggiornamenti degli articoli che definiscono gli attributi indicizzati non definiti utilizzano una singola operazione di scrittura per aggiungere l'elemento all'indice.

  • Gli aggiornamenti che cambiano il valore dell'attributo chiave indicizzata utilizzano due scritture per eliminare un elemento e scriverne uno nuovo.

  • Una scrittura della tabella che attiva l'eliminazione di un attributo indicizzato utilizza una singola scrittura per cancellare la vecchia proiezione dell'elemento nell'indice.

  • Gli elementi assenti nell'indice prima e dopo un'operazione di aggiornamento non utilizzano scritture.

  • Gli aggiornamenti modificando solo il valore dell'attributo proiettato nello schema della chiave dell'indice e il valore dell'attributo della chiave non indicizzato, utilizzano una scrittura per aggiornare i valori degli attributi proiettati nell'indice.

Tutti questi fattori presuppongono una dimensione dell'elemento inferiore o uguale a 1 KB.

Archiviazione dell'indice secondario globale

Durante la scrittura di un articolo, DynamoDB copia automaticamente il set corretto di attributi in tutti gli indici in cui devono esistere gli attributi. Ciò influisce sul tuo account addebitandolo per l'archiviazione degli elementi della tabella e l'archiviazione degli attributi. Lo spazio utilizzato risulta dalla somma di queste quantità:

  • Dimensione in byte della chiave primaria della tabella
  • Dimensione in byte dell'attributo chiave di indice
  • Dimensione in byte degli attributi proiettati
  • 100 byte di overhead per elemento di indice

È possibile stimare le esigenze di archiviazione stimando la dimensione media degli elementi e moltiplicando per la quantità degli elementi della tabella con gli attributi chiave dell'indice secondario globale.

DynamoDB non scrive i dati dell'elemento per un elemento della tabella con un attributo non definito definito come partizione dell'indice o chiave di ordinamento.

Indice secondario globale Crud

Creare una tabella con indici secondari globali utilizzando il CreateTable operazione abbinata al GlobalSecondaryIndexesparametro. È necessario specificare un attributo che funga da chiave di partizione dell'indice o utilizzarne un altro per la chiave di ordinamento dell'indice. Tutti gli attributi della chiave dell'indice devono essere stringa, numero o scalari binari. È inoltre necessario fornire le impostazioni di velocità effettiva, costituite daReadCapacityUnits e WriteCapacityUnits.

Uso UpdateTable per aggiungere indici secondari globali alle tabelle esistenti utilizzando nuovamente il parametro GlobalSecondaryIndexes.

In questa operazione è necessario fornire i seguenti input:

  • Nome dell'indice
  • Schema chiave
  • Attributi proiettati
  • Impostazioni di velocità effettiva

Aggiungendo un indice secondario globale, potrebbe essere necessario molto tempo con tabelle di grandi dimensioni a causa del volume degli elementi, del volume degli attributi previsti, della capacità di scrittura e dell'attività di scrittura. UsoCloudWatch metriche per monitorare il processo.

Uso DescribeTableper recuperare le informazioni sullo stato per un indice secondario globale. Restituisce uno di quattroIndexStatus per GlobalSecondaryIndexes -

  • CREATING - Indica la fase di creazione dell'indice e la sua indisponibilità.

  • ACTIVE - Indica la disponibilità dell'indice per l'uso.

  • UPDATING - Indica lo stato di aggiornamento delle impostazioni di velocità effettiva.

  • DELETING - Indica lo stato di cancellazione dell'indice e la sua indisponibilità permanente per l'uso.

Aggiorna le impostazioni di throughput fornite dall'indice secondario globale durante la fase di caricamento / backfilling (DynamoDB scrive gli attributi in un indice e tiene traccia degli elementi aggiunti / eliminati / aggiornati). UsoUpdateTable per eseguire questa operazione.

Ricorda che non puoi aggiungere / eliminare altri indici durante la fase di riempimento.

Utilizzare UpdateTable per eliminare gli indici secondari globali. Consente l'eliminazione di un solo indice per operazione, tuttavia è possibile eseguire più operazioni contemporaneamente, fino a cinque. Il processo di eliminazione non influisce sulle attività di lettura / scrittura della tabella padre, ma non è possibile aggiungere / eliminare altri indici fino al completamento dell'operazione.

Utilizzo di Java per lavorare con indici secondari globali

Crea una tabella con un indice tramite CreateTable. Crea semplicemente un'istanza di classe DynamoDB, un fileCreateTableRequest istanza di classe per richiedere informazioni e passare l'oggetto richiesta al metodo CreateTable.

Il seguente programma è un breve esempio:

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

Recupera le informazioni sull'indice con DescribeTable. Innanzitutto, crea un'istanza di classe DynamoDB. Quindi creare un'istanza della classe Table per targetizzare un indice. Infine, passa la tabella al metodo di descrizione.

Ecco un breve esempio:

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

Usa Query per eseguire una query di indice come con una query di tabella. Crea semplicemente un'istanza della classe DynamoDB, un'istanza della classe Table per l'indice di destinazione, un'istanza della classe Index per l'indice specifico e passa l'indice e l'oggetto query al metodo query.

Dai un'occhiata al seguente codice per capire meglio:

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

Il seguente programma è un esempio più ampio per una migliore comprensione:

Note- Il seguente programma può assumere un'origine dati creata in precedenza. Prima di tentare l'esecuzione, acquisire le librerie di supporto e creare le origini dati necessarie (tabelle con caratteristiche richieste o altre fonti di riferimento).

Questo esempio utilizza anche Eclipse IDE, un file delle credenziali AWS e AWS Toolkit all'interno di un progetto 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()); 
      } 
   } 
}