Front-End Web & Mobile

Amazon DynamoDB on Mobile – Part 2: Advanced Data Loading Options

Version 2 of the AWS Mobile SDK

  • This article and sample apply to Version 1 of the AWS Mobile SDK. If you are building new apps, we recommend you use Version 2. For details, please visit the AWS Mobile SDK page.
  • This content is being maintained for historical reference.

In our previous post, we discussed a number of ways in which we could load data from an Amazon DynamoDB table, either using the GetItem or Query API actions. In this post, we discuss a number of ways we can modify these requests in the AWS Mobile SDKs to either increase performance or gain further insight into how our requests effect our usage of DynamoDB.

Consistent Reads

By default, all read operations, GetItem and Query included, use eventually consistent reads. This means that if we read the data immediately after another process modifies it, we may get the older stale data. In the case where this is not acceptable—for instance, if we always wanted to make sure we have the absolute freshest data when first loading our application—GetItem and Query support consistent reads. Enabling consistent reads is simple for both GetItem and Query.

iOS

// GetItem
DynamoDBGetItemRequest *getItemRequest = [DynamoDBGetItemRequest new];
getItemRequest.tableName = @"UserTableExample";

// Ask for consistent reads
getItemRequest.consistentRead = YES;

// Configure the rest of the request
...

// Query
DynamoDBQueryRequest *queryRequest = [DynamoDBQueryRequest new];
queryRequest.tableName = @"UserTableExample";

// Ask for consistent reads
queryRequest.consistentRead = YES;

// Configure the rest of the request
...

Android

// GetItem with consistent reads
GetItemRequest getItemRequest = new GetItemRequest("UserTableExample",key)
    .withConsistentRead(true);

// Configure the rest of the request
...

// Query with consistent reads
QueryRequest queryRequest = new QueryRequest()
    .withTableName("UserTableExample")
    .withConsistentRead(true);

// Configure the rest of the request
...

Consistent reads are twice as expensive in terms of consumed capacity units (see the DynamoDB FAQ for an explanation of capacity units), so we recommend that you use them only when absolutely necessary.

Consumed Capacity Information

If we want to see exactly how many capacity units are consumed for our read requests, we can set an additional optional parameter on our requests, and the response/result object will contain that information.

iOS

// Construct our request
DynamoDBGetItemRequest *getItemRequest = [DynamoDBGetItemRequest new];
getItemRequest.tableName = @"UserTableExample";
DynamoDBAttributeValue *userId = [[DynamoDBAttributeValue alloc] initWithN:@"1234"];
DynamoDBAttributeValue *recordId = [[DynamoDBAttributeValue alloc] initWithS:@"name"];
getItemRequest.key = [NSMutableDictionary dictionaryWithObjectsAndKeys:userId, @"UserId", recordId, @"RecordId", nil];

// Ask for our total consumed capacity
getItemRequest.returnConsumedCapacity = @"TOTAL";  

// Execute the request
DynamoDBGetItemResponse *getItemResponse = [self.ddb getItem:request];

// Log our consumed capacity (implied casting down to int)
NSLog(@"Consumed capacity for getItem: %d", [getItemResponse.consumedCapacity.capacityUnits integerValue]);

Android

// Construct our request
HashMap<String, AttributeValue> key = new HashMap<String, AttributeValue>();
key.put("UserId", new AttributeValue().withN("1234"));
key.put("RecordId", new AttributeValue().withS("name"));
GetItemRequest getItemRequest = new GetItemRequest("UserTableExample",key);

// Ask for our total consumed capacity
getItemRequest.setReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);

// Execute the request
GetItemResult result = ddbClient.getItem(getItemRequest);

// Log our consumed capacity (casting to int for whole number)
Log.i(LOG_TAG, "Consumed capacity for getItem: = " + result.getConsumedCapacity().getCapacityUnits().intValue());

Restricting Returned Attributes

If in looking at our read requests, we see that we are using more capacity units than we’d like per client, we can optimize our requests by asking only for the attributes we care about. Consider the following potential use case of our user data table:

  • At application start, we load a list of available records for the user.
  • Display a list of those record IDs in a table or list view.
  • When selected, show the latest copy of the record data.

Because at application start time we only need the record IDs, and we can load an individual record on demand, we can optimize our read requests as appropriate to minimize our consumed capacity.

iOS

// Generate our query ("UserId" == 1234)
DynamoDBCondition *condition = [DynamoDBCondition new];
condition.comparisonOperator = @"EQ";
DynamoDBAttributeValue *userId = [[[DynamoDBAttributeValue alloc] initWithN:@"1234"] autorelease];
[condition addAttributeValueList:userId];
DynamoDBQueryRequest *queryRequest = [DynamoDBQueryRequest new];
queryRequest.tableName = @"UserTableExample";
queryRequest.keyConditions = [NSMutableDictionary dictionaryWithObject:condition forKey:@"UserId"];

// Specify that we want only the RecordId
queryRequest.attributesToGet = [NSArray arrayWithObject: @"RecordId"];

// Process the request and store the list of RecordIds
...

// Construct our request to load record data, use consistent read
DynamoDBGetItemRequest *getItemRequest = [DynamoDBGetItemRequest new];
getItemRequest.tableName = @"UserTableExample";
DynamoDBAttributeValue *userId = [[DynamoDBAttributeValue alloc] initWithN:@"1234"];
DynamoDBAttributeValue *recordId = [[DynamoDBAttributeValue alloc] initWithS:@"name"];
getItemRequest.key = [NSMutableDictionary dictionaryWithObjectsAndKeys:userId, @"UserId", recordId, @"RecordId", nil];
getItemRequest.consistentRead = YES;

// We care only about our data
getitemRequest.attributesToGet = [NSArray arrayWithObject: @"Data"];

// Process the request and display the data to the user
...

Android

// Generate our query ("UserId" == 1234)
Condition hashKeyCondition = new Condition()
    .withComparisonOperator(ComparisonOperator.EQ.toString())
    .withAttributeValueList(new AttributeValue().withN("1234"));
Map<String, Condition> keyConditions = new HashMap<String, Condition>();
keyConditions.put("UserId", hashKeyCondition);
QueryRequest queryRequest = new QueryRequest()
            .withTableName("UserTableExample")
            .withKeyConditions(keyConditions);

// Specify that we want only the RecordId
queryRequest.withAttributesToGet("RecordId");

// Process the request and store the list of RecordIds
...
            
// Construct our request to load record data, use consistent read
HashMap<String, AttributeValue> key = new HashMap<String, AttributeValue>();
key.put("UserId", new AttributeValue().withN("1234"));
key.put("RecordId", new AttributeValue().withS("name"));
GetItemRequest getItemRequest = new GetItemRequest("UserTableExample",key);

// We care only about our data
getitemRequest.withAttributesToReturn("Data");

// Process the request and display the data to the user
...

We hope this illustrates the basics of loading and using data from DynamoDB with the AWS Mobile SDKs. Stay tuned for future posts in which we discuss how we can update data in tables.

The code samples included in this post are for the DynamoDB API version 2012-08-10 and will work only with the AWSDynamoDB.framework on iOS and the com.amazonaws.services.dynamodbv2 package on Android. See our previous post on how to convert your iOS application to use the new service-level frameworks.

Further Reading