DynamoDB: índices secundarios globales

Las aplicaciones que requieren varios tipos de consultas con diferentes atributos pueden utilizar uno o varios índices secundarios globales para realizar estas consultas detalladas.

For example - Un sistema que realiza un seguimiento de los usuarios, su estado de inicio de sesión y su tiempo de inicio de sesión. El crecimiento del ejemplo anterior ralentiza las consultas sobre sus datos.

Los índices secundarios globales aceleran las consultas al organizar una selección de atributos de una tabla. Emplean claves primarias en la clasificación de datos y no requieren atributos de tabla de claves ni esquemas de claves idénticos a los de la tabla.

Todos los índices secundarios globales deben incluir una clave de partición, con la opción de una clave de clasificación. El esquema de clave de índice puede diferir de la tabla y los atributos de clave de índice pueden usar cualquier cadena de nivel superior, número o atributos de tabla binaria.

En una proyección, puede utilizar otros atributos de tabla; sin embargo, las consultas no se recuperan de las tablas principales.

Proyecciones de atributos

Las proyecciones consisten en un conjunto de atributos copiado de la tabla al índice secundario. Una proyección siempre ocurre con la clave de partición de la tabla y la clave de clasificación. En las consultas, las proyecciones permiten que DynamoDB acceda a cualquier atributo de la proyección; esencialmente existen como su propia mesa.

En la creación de un índice secundario, debe especificar atributos para la proyección. DynamoDB ofrece tres formas de realizar esta tarea:

  • KEYS_ONLY- Todos los elementos del índice constan de la partición de la tabla y los valores clave de clasificación, y los valores clave del índice. Esto crea el índice más pequeño.

  • INCLUDE - Incluye atributos KEYS_ONLY y atributos no clave especificados.

  • ALL - Incluye todos los atributos de la tabla fuente, creando el índice más grande posible.

Tenga en cuenta las ventajas y desventajas de proyectar atributos en un índice secundario global, que se relacionan con el rendimiento y el costo de almacenamiento.

Considere los siguientes puntos:

  • Si solo necesita acceso a algunos atributos, con baja latencia, proyecte solo aquellos que necesite. Esto reduce los costos de almacenamiento y escritura.

  • Si una aplicación accede con frecuencia a ciertos atributos no clave, proyéctelos porque los costos de almacenamiento palidecen en comparación con el consumo de escaneo.

  • Puede proyectar grandes conjuntos de atributos a los que se accede con frecuencia, sin embargo, esto conlleva un alto costo de almacenamiento.

  • Utilice KEYS_ONLY para consultas de tablas poco frecuentes y escrituras / actualizaciones frecuentes. Esto controla el tamaño, pero aún ofrece un buen rendimiento en las consultas.

Consultas y análisis de índices secundarios globales

Puede utilizar consultas para acceder a uno o varios elementos de un índice. Debe especificar el índice y el nombre de la tabla, los atributos deseados y las condiciones; con la opción de devolver los resultados en orden ascendente o descendente.

También puede utilizar exploraciones para obtener todos los datos del índice. Requiere nombre de tabla e índice. Utiliza una expresión de filtro para recuperar datos específicos.

Sincronización de datos de tabla e índice

DynamoDB realiza automáticamente la sincronización en los índices con su tabla principal. Cada operación de modificación de elementos provoca actualizaciones asincrónicas; sin embargo, las aplicaciones no escriben directamente en los índices.

Debe comprender el impacto del mantenimiento de DynamoDB en los índices. Al crear un índice, usted especifica los atributos clave y los tipos de datos, lo que significa que en una escritura, esos tipos de datos deben coincidir con los tipos de datos del esquema clave.

En la creación o eliminación de elementos, los índices se actualizan de manera eventualmente consistente, sin embargo, las actualizaciones de los datos se propagan en una fracción de segundo (a menos que ocurra algún tipo de falla en el sistema). Debe tener en cuenta este retraso en las solicitudes.

Throughput Considerations in Global Secondary Indexes- Múltiples índices secundarios globales impactan en el rendimiento. La creación de índices requiere especificaciones de unidades de capacidad, que existen separadas de la tabla, lo que da como resultado que las operaciones consuman unidades de capacidad de índice en lugar de unidades de tabla.

Esto puede resultar en una limitación si una consulta o escritura excede el rendimiento aprovisionado. Ver la configuración de rendimiento medianteDescribeTable.

Read Capacity- Los índices secundarios globales brindan consistencia eventual. En las consultas, DynamoDB realiza cálculos de aprovisionamiento idénticos a los que se utilizan para las tablas, con la única diferencia de utilizar el tamaño de la entrada del índice en lugar del tamaño del elemento. El límite de devoluciones de una consulta sigue siendo de 1 MB, que incluye el tamaño del nombre de atributo y los valores en cada elemento devuelto.

Capacidad de escritura

Cuando se producen operaciones de escritura, el índice afectado consume unidades de escritura. Los costos de rendimiento de escritura son la suma de las unidades de capacidad de escritura consumidas en las escrituras de tablas y las unidades consumidas en las actualizaciones de índices. Una operación de escritura exitosa requiere suficiente capacidad o da lugar a una limitación.

Los costos de escritura también dependen de ciertos factores, algunos de los cuales son los siguientes:

  • Los elementos nuevos que definen atributos indexados o las actualizaciones de elementos que definen atributos indexados no definidos utilizan una sola operación de escritura para agregar el elemento al índice.

  • Las actualizaciones que cambian el valor del atributo de clave indexada utilizan dos escrituras para eliminar un elemento y escribir uno nuevo.

  • Una escritura de tabla que desencadena la eliminación de un atributo indexado utiliza una sola escritura para borrar la proyección del elemento antiguo en el índice.

  • Los elementos ausentes en el índice antes y después de una operación de actualización no utilizan escrituras.

  • Las actualizaciones que cambian solo el valor de atributo proyectado en el esquema de clave de índice, y no el valor de atributo de clave indexado, utilizan una escritura para actualizar los valores de los atributos proyectados en el índice.

Todos estos factores asumen un tamaño de artículo menor o igual a 1 KB.

Almacenamiento de índice secundario global

En la escritura de un elemento, DynamoDB copia automáticamente el conjunto correcto de atributos en cualquier índice donde deben existir los atributos. Esto afecta su cuenta al cobrarle por el almacenamiento de elementos de la tabla y el almacenamiento de atributos. El espacio utilizado resulta de la suma de estas cantidades:

  • Tamaño de bytes de la clave primaria de la tabla
  • Tamaño de byte del atributo de clave de índice
  • Tamaño de bytes de los atributos proyectados
  • 100 bytes de sobrecarga por elemento de índice

Puede estimar las necesidades de almacenamiento estimando el tamaño medio de los elementos y multiplicando por la cantidad de elementos de la tabla con los atributos clave del índice secundario global.

DynamoDB no escribe datos de elementos para un elemento de la tabla con un atributo indefinido definido como una partición de índice o una clave de clasificación.

Crud del índice secundario global

Cree una tabla con índices secundarios globales usando el CreateTable operación emparejada con el GlobalSecondaryIndexesparámetro. Debe especificar un atributo para que sirva como clave de partición de índice o utilizar otro para la clave de clasificación de índice. Todos los atributos de clave de índice deben ser escalares de cadena, número o binarios. También debe proporcionar la configuración de rendimiento, que consta deReadCapacityUnits y WriteCapacityUnits.

Utilizar UpdateTable para agregar índices secundarios globales a las tablas existentes utilizando el parámetro GlobalSecondaryIndexes una vez más.

En esta operación, debe proporcionar las siguientes entradas:

  • Nombre del índice
  • Esquema clave
  • Atributos proyectados
  • Configuración de rendimiento

Al agregar un índice secundario global, puede llevar un tiempo considerable con tablas grandes debido al volumen de elementos, el volumen de atributos proyectados, la capacidad de escritura y la actividad de escritura. UtilizarCloudWatch métricas para monitorear el proceso.

Utilizar DescribeTablepara obtener información de estado para un índice secundario global. Devuelve uno de cuatroIndexStatus para GlobalSecondaryIndexes -

  • CREATING - Indica la etapa de construcción del índice y su indisponibilidad.

  • ACTIVE - Indica la disponibilidad del índice para su uso.

  • UPDATING - Indica el estado de actualización de la configuración de rendimiento.

  • DELETING - Indica el estado de eliminación del índice y su indisponibilidad permanente para su uso.

Actualice la configuración de rendimiento aprovisionado del índice secundario global durante la etapa de carga / reabastecimiento (atributos de escritura de DynamoDB en un índice y seguimiento de elementos agregados / eliminados / actualizados). UtilizarUpdateTable para realizar esta operación.

Debe recordar que no puede agregar / eliminar otros índices durante la etapa de reposición.

Utilice UpdateTable para eliminar índices secundarios globales. Permite la eliminación de solo un índice por operación, sin embargo, puede ejecutar múltiples operaciones simultáneamente, hasta cinco. El proceso de eliminación no afecta las actividades de lectura / escritura de la tabla principal, pero no puede agregar / eliminar otros índices hasta que se complete la operación.

Uso de Java para trabajar con índices secundarios globales

Cree una tabla con un índice a través de CreateTable. Simplemente cree una instancia de clase DynamoDB, unaCreateTableRequest instancia de clase para obtener información sobre la solicitud y pasar el objeto de solicitud al método CreateTable.

El siguiente programa es un breve ejemplo:

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 la información del índice con DescribeTable. Primero, cree una instancia de clase DynamoDB. Luego, cree una instancia de la clase Table para apuntar a un índice. Finalmente, pase la tabla al método describe.

Aquí hay un breve ejemplo:

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

Utilice Consulta para realizar una consulta de índice como con una consulta de tabla. Simplemente cree una instancia de clase de DynamoDB, una instancia de clase de tabla para el índice de destino, una instancia de clase de índice para el índice específico y pase el índice y el objeto de consulta al método de consulta.

Eche un vistazo al siguiente código para comprenderlo mejor:

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

El siguiente programa es un ejemplo más grande para una mejor comprensión:

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

Este ejemplo también utiliza Eclipse IDE, un archivo de credenciales de AWS y AWS Toolkit dentro de un proyecto 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()); 
      } 
   } 
}