Home > Software engineering >  Does DynamoDB's UpdateItemCommand have an "update only" mode?
Does DynamoDB's UpdateItemCommand have an "update only" mode?

Time:01-19

I have some TypeScript code in a project that does a number of native DynamoDB update operations:

import { nativeDocumentClient, nativeDynamo } from '../utils/aws';

// snipped code

// updatedProducts is of type `{ id: string; siteId: string; state: ProductState }[]`

await Promise.all(
  updatedProducts.map(({ id, siteId, state }) =>
    nativeDocumentClient
      .update({
        TableName,
        Key: {
          id,
          siteId,
        },
        ExpressionAttributeNames: {
          '#s': 'state',
        },
        ExpressionAttributeValues: {
          ':state': state,
          ':updatedAt': new Date().toISOString(),
        },
        UpdateExpression: 'SET #s = :state, updatedAt = :updatedAt',
        ReturnValues: 'ALL_NEW',
      })
      .promise()
  )
);

Now we would always expect this record (with its composite Key) to exist, but we have found a rare situation where this is not the case (it's probably just poor data in a nonprod environment rather than a bug specifically). Unfortunately it looks like the underlying UpdateItemCommand does an upsert and not an update, despite its name. From the official docs:

Edits an existing item's attributes, or adds a new item to the table if it does not already exist

We can do a guard clause that does a get on the record, and skips the upsert if it does not exist. However that feels like an invitation for a race condition - is there an option on .update() that would get us what we want without separate conditionals?


Update - a guard clause might look like this in pseudocode:

object = get(TableName, Key: {
  id,
  siteId,
})
// Do update only if the document exists
if (object) {
    update(
      TableName,
      Key: {
        id,
        siteId,
      },
      ...
    )
 }

I think this could be susceptible to a race condition because the read and write operations are not wrapped in any sort of transaction or lock.

CodePudding user response:

All writes/updates to DynamoDB are strongly serializable.

Using a ConditionExpression for attribute_exists() would ensure you only update the item if it exists, if it does not exist the update will fail.

aws dynamodb update-item \
    --table-name MyTable \
    --key file://config.json \
    --update-expression "SET #Y = :y, #AT = :t" \
    --expression-attribute-names file://expression-attribute-names.json \
    --expression-attribute-values file://expression-attribute-values.json  \
    --condition-expression "attribute_exists(#PK)"
  • Related