Skip to main content

· 3 min read

It has been a long time since the latest stable release of EfficientDynamoDb, but I'm glad to announce that v0.9.15 is finally here.

info

This release brings support for the ReturnValuesOnConditionCheckFailure parameter in write requests, more predefined region endpoints, improved performance of error parsing, and XML docs for UpdateExpression builder.

Support for ReturnValuesOnConditionCheckFailure feature

This parameter allows you to receive an item as it existed during the failed write attempt. It aids in reducing RCU usage and latency for initiating an additional GetItem request to understand why exactly your condition check failed.

Let's consider the following example. You are developing a warehouse management system or internet store that allows employees to adjust item quantity. A simplified data model for an item might look similar to this:

[DynamoDbTable("items")]
public class Item
{
[DynamoDbProperty("pk", DynamoDbAttributeType.PartitionKey)]
public string WarehouseId { get; set; }

[DynamoDbProperty("sk", DynamoDbAttributeType.SortKey)]
public string ItemId { get; set; }

[DynamoDbProperty("quantity")]
public int Quantity { get; set; }
}

Now, imagine a situation where two employees are trying to sell the same item to customers at the same time. In this case, you need to ensure that there are enough items in the warehouse before you finalize the deal. And if there are not enough items for sale, inform the employee about how many items are currently available.

Before ReturnValuesOnConditionCheckFailure, the method that performs this function might look similar to this:

public async Task SellItemsAsync(string warehouseId, string itemId, int quantity)
{
try
{
await _context.UpdateItem<Item>()
.WithPrimaryKey(warehouseId, itemId)
.WithCondition(cond => cond.On(x => x.Balance).GreaterThanOrEqualTo(10))
.ExecuteAsync();
}
catch (ConditionalCheckFailedException e)
{
// Not enough items available but no way to tell how many are left.
// So we need to perform an additional GetItem request.
var item = await _context.GetItemAsync<Item>(warehouseId, itemId);
throw new InsufficientInventoryException(item.Balance);
}
}

There are two main issues with this approach:

  1. Additional RSU is used for the GetItem request, which increases your DynamoDB bill. It becomes worse if you consider eventual consistency and opt for strongly consistent reads as they are twice as costly.
  2. Additional latency is added due to a full round trip required for the GetItem request.

ReturnValuesOnConditionCheckFailure addresses both these problems:

public async Task SellItemsAsync(string warehouseId, string itemId, int quantity)
{
try
{
await _context.UpdateItem<Item>()
.WithPrimaryKey(warehouseId, itemId)
.WithCondition(cond => cond.On(x => x.Balance).GreaterThanOrEqualTo(10))
.WithReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.AllOld)
.ExecuteAsync();
}
catch (ConditionalCheckFailedException e)
{
// Now the exception contains `Item` property.
// It will be set only if WithReturnValuesOnConditionCheckFailure is set.
// Note that it's a `Document` type that you can convert to an entity.
var item = _context.ToEntity<Wallet>(e.Item!);
throw new InsufficientInventoryException(item.Balance);
}
}

Using this approach, you receive the balance as it existed at the exact moment of the failed update request, without incurring additional costs or latency in your system.

note

ReturnValuesOnConditionCheckFailure is available for all single-item write operations and transactional writes.

More predefined region endpoints

Since the initial release of EfficientDynamoDb, several new AWS regions have been introduced. While it was always possible to create regions dynamically, all regions supported by DynamoDB are now available as static properties of RegionEndpoint.

List of new regions added in EfficientDynamoDb 0.9.15:

  • ap-south-2 - Asia Pacific (Hyderabad)
  • ap-southeast-3 - Asia Pacific (Jakarta)
  • ap-southeast-4 - Asia Pacific (Melbourne)
  • eu-central-2 - Europe (Zurich)
  • eu-south-2 - Europe (Spain)
  • me-central-1 - Middle East (UAE)
  • il-central-1 - Israel (Tel Aviv)

Other improvements

  • Error parsing was tuned to handle the most frequent DynamoDB errors more efficiently.
  • Added XML docs for UpdateExpression builder to improve developer experience.

· 2 min read

We're thrilled to announce our first publicly available Release Candidate version of EfficientDynamoDb.

It aims to simplify major pain points of interacting with DynamoDB in C# that we faced in more than 4 years working with it:

  1. Unreasonably slow response processing. In some cases, parsing the result is slower than DynamoDB latency.
  2. Expressions syntax (query, update, etc.) is not suitable for C# tooling. Using strings for expressions is highly error-prone, hides usages, makes refactoring challenging, and has no validation whatsoever.
  3. Missing high-level APIs for transactions, batches, updates.
  4. Poor out-of-the-box data types support (especially collections) and limited extensibility.

We've put dozens of hours optimizing hot paths in our library to ensure that every single benchmark outperforms competitors. In some popular scenarios, EfficientDynamoDb can be up to 21x times faster and allocate 26x times less memory.

It's possible to build DDB expressions entirely in C# without using clumsy DDB expressions syntax and plain strings. Complicated operations like transactions, batches, updates, and parallel scans are easy to use via high-level API.

We have many ideas moving forward, like integrating composite keys, smart retry policies, supporting get-only properties, and so on. We'd love to hear the feedback from the community, so feel free to create an issue on GitHub or post your questions and suggestions here in the comments.