> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensor9.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tuning AWS DynamoDB

> Size Cosmos DB containers, tune database connections, and measure DynamoDB workloads across Firestore, Spanner, Bigtable and PostgreSQL backends.

A DynamoDB table's performance depends on the backend that stores its items, the requests your application sends, and the capacity shared with other tables. Use this article to choose the right tuning surface, run a controlled comparison, and verify the native result.

Start with the [DynamoDB service profile](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table). It describes the supported operations, numeric precision, transactions, indexes and streams for each backend. A larger database cannot add an operation that the mapping does not support.

## Choose the setting and its scope

| Backend                     | Capacity to size                               | Adapter setting               | Important scope                                                                     |
| --------------------------- | ---------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------- |
| Provisioned Azure Cosmos DB | Dedicated container autoscale maximum, in RU/s | Table-creation tuning tag     | Capacity belongs to the physical container.                                         |
| Serverless Azure Cosmos DB  | Consumption capacity and service limits        | Select the serverless backend | There is no dedicated autoscale RU band to set.                                     |
| Google Cloud Firestore      | Document operations, index work and storage    | Optional blind-update setting | The setting changes eligible update validation and applies when the backend starts. |
| Google Cloud Spanner        | Instance processing units and placement        | `SPANNER_NUM_CHANNELS`        | Instance capacity is shared by its databases and tables.                            |
| Google Cloud Bigtable       | Cluster nodes, storage and row distribution    | `BIGTABLE_NUM_CHANNELS`       | Channels belong to the adapter; nodes belong to the shared cluster.                 |
| PostgreSQL                  | Instance CPU, memory, storage and connections  | `T9_POSTGRES_POOL_SIZE`       | Each adapter pool consumes part of the database's total connection budget.          |

Three different mechanisms appear below:

* **Resource tuning tags** set a supported target field for a resource created through the origin API.
* **Adapter configuration** controls the process that translates requests, such as its connection pool.
* **Native infrastructure settings** size a shared database or cluster through the system that owns that infrastructure.

Keep those scopes in the change record. Setting a table tag must not unexpectedly resize a shared Spanner instance or PostgreSQL database used by unrelated tables.

## Try provisioned Cosmos DB locally

This example uses a local Cloud Adapter process and an Azure test environment. The process is local; its Cosmos DB resources and capacity are real Azure resources.

<Steps>
  <Step title="Install the CLI and inspect the mapping">
    If needed, install the Tensor9 CLI:

    ```bash theme={null}
    brew tap tensor9ine/tensor9
    brew install tensor9
    tensor9 --version
    ```

    Inspect the mapping before creating the test table:

    ```bash theme={null}
    tensor9 explain \
      -origin aws::1.0.0::dynamodb::table \
      -target azure::1.0.0::cosmosdb::provisioned \
      -fmt Human
    ```

    Prepare the Azure subscription, resource group, location and runtime identity described in [Configuration Examples](/cloud-adapter/configuration/examples#dynamodb-to-provisioned-azure-cosmos-db). The identity needs the target access required for both resource provisioning and item operations.
  </Step>

  <Step title="Start the DynamoDB adapter">
    Run the service in a terminal you can leave open:

    ```bash theme={null}
    tensor9 adapt svc run \
      --origin aws::1.0.0::dynamodb::table \
      --backend azure::1.0.0::cosmosdb::provisioned
    ```

    This starts a local endpoint for DynamoDB table and item requests.
  </Step>

  <Step title="Point your test client at the printed endpoint">
    In a second terminal, copy the listening endpoint:

    ```bash theme={null}
    export T9_CLOUD_ADAPTER_ENDPOINT="<endpoint printed by tensor9>"
    export AWS_ENDPOINT_URL_DYNAMODB="$T9_CLOUD_ADAPTER_ENDPOINT"
    export AWS_REGION="us-east-1"
    ```

    The service-specific endpoint directs DynamoDB calls from this process to the adapter. Keep the origin credentials appropriate to the adapter endpoint; the adapter's Azure identity is separate.

    To configure a single client programmatically instead:

    ```python theme={null}
    import os
    import boto3

    dynamodb = boto3.client(
        "dynamodb",
        endpoint_url=os.environ["T9_CLOUD_ADAPTER_ENDPOINT"],
        region_name="us-east-1",
    )
    ```
  </Step>

  <Step title="Create, inspect and exercise a test table">
    Use the complete request in the next section. Wait for the table to become available, inspect its native Cosmos capacity, then run the point-write and update examples. Record the table name, native container identity, effective throughput and test time together.
  </Step>

  <Step title="Finish the experiment">
    Delete only the disposable data created for the experiment. Follow the deployment's resource-ownership policy when removing containers and accounts. Stopping the local process does not stop Azure capacity charges or delete backend data.

    Press **Control-C** in the adapter terminal when finished.
  </Step>
</Steps>

## Provisioned Cosmos DB: give a table its own capacity

For a table that owns a dedicated Cosmos container, send the backend selector and autoscale maximum in the DynamoDB `CreateTable` tags. Save this complete request as `create-orders.json`:

```json theme={null}
{
  "TableName": "tuning-orders",
  "AttributeDefinitions": [
    { "AttributeName": "tenant_id", "AttributeType": "S" },
    { "AttributeName": "order_id", "AttributeType": "S" }
  ],
  "KeySchema": [
    { "AttributeName": "tenant_id", "KeyType": "HASH" },
    { "AttributeName": "order_id", "KeyType": "RANGE" }
  ],
  "BillingMode": "PAY_PER_REQUEST",
  "Tags": [
    {
      "Key": "t9:backend",
      "Value": "azure::1.0.0::cosmosdb::provisioned"
    },
    {
      "Key": "t9:tuning:container.properties.options.autoscaleSettings.maxThroughput",
      "Value": "40000"
    },
    { "Key": "environment", "Value": "tuning-test" }
  ]
}
```

```bash theme={null}
aws dynamodb create-table --cli-input-json file://create-orders.json
aws dynamodb wait table-exists --table-name tuning-orders
aws dynamodb describe-table --table-name tuning-orders
```

The backend selector chooses provisioned Cosmos DB. The tuning path sets the target container's autoscale maximum to **40,000 RU/s**. The value is a string in the AWS tag carrier and a numeric field in the target request.

`BillingMode: PAY_PER_REQUEST` remains an origin-facing DynamoDB setting. It does not make the selected Cosmos account serverless. Likewise, changing DynamoDB provisioned read or write units through `UpdateTable` does not resize the Cosmos container.

40,000 RU/s is an example capacity budget. Choose the value from measurements of your own item sizes, queries, writes and indexes. The target field uses 1,000-RU/s increments and a minimum of 1,000 RU/s; the actual container's storage, throughput history and account constraints can require a higher minimum.

<Note>
  The container must belong to this table for the example to provide a per-table capacity budget. Several logical tables sharing a pre-provisioned container share its throughput. Manage that shared container's capacity with its infrastructure owner; do not treat a logical table tag as an isolated budget.
</Note>

### Inspect the native result

Set these values from the deployment's actual Azure resource coordinates. An origin table name is not proof of a native container name:

```bash theme={null}
export AZURE_RESOURCE_GROUP="rg-cloud-adapter-dev"
export COSMOS_ACCOUNT="acme-adapter-dev"
export COSMOS_DATABASE="cloud-adapter"
export COSMOS_CONTAINER="<native container for tuning-orders>"

az cosmosdb sql container throughput show \
  --resource-group "$AZURE_RESOURCE_GROUP" \
  --account-name "$COSMOS_ACCOUNT" \
  --database-name "$COSMOS_DATABASE" \
  --name "$COSMOS_CONTAINER" \
  --query resource.autoscaleSettings.maxThroughput \
  --output tsv
```

For this request, the observed maximum should be `40000`. If it differs, keep the create response and the target inspection output and follow [Understanding Behavior with Explain](/cloud-adapter/debugging/explain). A successful `DescribeTable` does not independently verify Azure capacity.

### Size a second table independently

A small lookup table and a busy orders table need not have the same budget. In a second copy of `create-orders.json`, change the table name to `tuning-reference` and the maximum to `4000`. Keep a dedicated target container for each table.

| Table              | Example maximum | Workload to test                                           |
| ------------------ | --------------- | ---------------------------------------------------------- |
| `tuning-orders`    | 40,000 RU/s     | Concurrent order writes, status updates and tenant queries |
| `tuning-reference` | 4,000 RU/s      | Small point reads with occasional updates                  |

Run each workload alone, then run both together. Inspect both native containers. This checks table-specific sizing and exposes shared account, adapter or network constraints that separate RU budgets cannot remove.

Treat global secondary indexes as additional capacity consumers. The provisioned Cosmos mapping uses Azure-managed index containers. Include index storage, index maintenance and index-query traffic in the budget, and use the [provisioned Cosmos profile](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table#azure-cosmos-db-provisioned) when choosing the table layout.

### Compare a point operation with a conditional update

First write and read a small item:

```bash theme={null}
aws dynamodb put-item \
  --table-name tuning-orders \
  --item '{"tenant_id":{"S":"tenant-001"},"order_id":{"S":"order-001"},"status":{"S":"new"},"version":{"N":"1"}}'

aws dynamodb get-item \
  --table-name tuning-orders \
  --key '{"tenant_id":{"S":"tenant-001"},"order_id":{"S":"order-001"}}' \
  --consistent-read
```

Then exercise a conditional write with a returned image:

```bash theme={null}
aws dynamodb update-item \
  --table-name tuning-orders \
  --key '{"tenant_id":{"S":"tenant-001"},"order_id":{"S":"order-001"}}' \
  --update-expression 'SET #status = :status, #version = :next' \
  --condition-expression '#version = :expected' \
  --expression-attribute-names '{"#status":"status","#version":"version"}' \
  --expression-attribute-values '{":status":{"S":"paid"},":next":{"N":"2"},":expected":{"N":"1"}}' \
  --return-values ALL_NEW
```

The first conditional update should succeed. Repeating it should fail the version condition because the item now has version 2. That failure is an application concurrency result, not evidence of insufficient RU capacity.

Measure unconditional writes, conditional writes, queries and returned images separately. They can perform different native work. Compare latency and request-unit use for the operations the application actually issues.

### Diagnose throttling before increasing the maximum

| Observation                                                              | Next experiment                                                                                                      |
| ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| High utilization and throttling spread across many partition keys        | Increase the dedicated container budget in the system that owns its throughput, then repeat the same workload.       |
| One tenant is slow while other tenants have spare capacity               | Inspect hot-partition distribution. More total RU/s does not necessarily remove a concentrated partition bottleneck. |
| Native utilization is low but application latency rises with concurrency | Check adapter CPU, connection queueing and client retries before buying more capacity.                               |
| Writes get more expensive after adding indexes                           | Measure index maintenance and query benefit together.                                                                |
| Origin capacity metadata changes but native throughput does not          | Inspect the Cosmos throughput resource. DynamoDB billing metadata is not the native resizing interface.              |

For an existing container, change throughput through its infrastructure owner and inspect the effective value afterward. Do not assume adding a tag later, repeating `CreateTable`, or calling `UpdateTable` is a capacity-update command. Keep the desired configuration synchronized with any native change so a later deployment does not restore an older value.

## Serverless Cosmos DB: choose consumption capacity

Use the serverless backend when its consumption model fits the workload. The selector is:

```json theme={null}
{
  "Key": "t9:backend",
  "Value": "azure::1.0.0::cosmosdb::serverless"
}
```

This is a tag entry to use in a complete `CreateTable` request. Omit the provisioned-container autoscale tag. Serverless Cosmos does not have a dedicated autoscale band to reserve, and DynamoDB `ProvisionedThroughput` does not create one.

Compare a quiet period followed by a representative burst with the same workload on provisioned Cosmos. Record successful operations, throttled requests, total retries, end-to-end latency and consumed request units. A low average request rate can hide a burst that exceeds the target's limits.

Choose provisioned Cosmos when the table needs the mapping's managed global secondary indexes. Do not switch an existing table's backend tag to move data or change account mode. Create the destination, migrate the data and validate queries and consumers before changing traffic. See [Cosmos serverless behavior](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table#azure-cosmos-db-serverless).

## Firestore: tune the update shape

Firestore bills native document work and storage. The adapter's request shape matters because a write that needs the old item can perform more work than an unconditional write. DynamoDB provisioned capacity settings do not reserve a Firestore read/write budget.

### Start with the normal validation behavior

Keep `FIRESTORE_ALLOW_OVERSIZED_BLIND_UPDATES` absent or set it to the string `"false"` in the selected service's configuration. The adapter enforces the resulting DynamoDB item's 400-KiB limit. Unconditional puts and deletes without return values or Streams can already avoid an item read; eligible top-level REMOVE-only updates can also do so.

For a Firestore-backed test table with the same key schema as `tuning-orders`, compare:

```bash theme={null}
aws dynamodb update-item \
  --table-name tuning-orders \
  --key '{"tenant_id":{"S":"tenant-001"},"order_id":{"S":"order-001"}}' \
  --update-expression 'REMOVE transient_note' \
  --return-values NONE
```

with a SET update:

```bash theme={null}
aws dynamodb update-item \
  --table-name tuning-orders \
  --key '{"tenant_id":{"S":"tenant-001"},"order_id":{"S":"order-001"}}' \
  --update-expression 'SET #status = :status' \
  --expression-attribute-names '{"#status":"status"}' \
  --expression-attribute-values '{":status":{"S":"packed"}}' \
  --return-values NONE
```

Use the same initial item in each test. Compare native document reads and writes as well as application latency. A single terminal invocation is a correctness check; repeat the operation under the application's normal concurrency for a performance comparison.

### Opt in to blind SET updates where the size tradeoff fits

This adapter-configuration fragment enables eligible SET/REMOVE requests to use a masked Firestore write without reading the item first:

```json theme={null}
{
  "config": {
    "FIRESTORE_ALLOW_OVERSIZED_BLIND_UPDATES": "true"
  }
}
```

Merge the field into the selected Firestore adapter's configuration and restart that backend with the updated configuration. This is a process setting, not a `CreateTable` tag or a process environment variable. Do not add an origin-to-backend declaration merely to repeat the installation's normal mapping; [Configuration Files](/cloud-adapter/configuration/files) explains the compact configuration and service-specific settings.

The optimized request must meet all these conditions:

* Only top-level literal SET and/or REMOVE actions.
* No condition expression and `ReturnValues=NONE`.
* Streams disabled.
* No changes to the configured TTL attribute, primary key or secondary-index keys.
* No local secondary indexes that require checking the combined item and projected index-entry size.

Other request shapes retain their guarded behavior. Enabling this setting does not make arithmetic updates, nested changes or conditional writes into blind writes.

The tradeoff is explicit: an eligible patch can grow an item beyond DynamoDB's 400-KiB limit. Firestore's native document and index limits still apply. Later validated writes can reject that item. Before restoring strict mode, inspect and shrink oversized items; setting the option back to `"false"` does not rewrite existing data.

Run the same SET workload with the setting off and on. Include a condition-expression case and a returned-image case in both runs to verify that application behavior remains correct. See the [Firestore profile](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table#firestore) for the detailed request restrictions.

## Spanner: separate channels from instance capacity

Spanner instance processing units are shared infrastructure capacity. The adapter's gRPC channels are a separate concurrency control. A table's DynamoDB RCU/WCU settings do not allocate processing units to that table.

The adapter defaults to 32 channels. A configuration fragment for a comparison at 16 channels is:

```json theme={null}
{
  "config": {
    "SPANNER_NUM_CHANNELS": "16"
  }
}
```

Apply this field to the selected Spanner adapter configuration and restart the process. Compare 16 with 32 channels at the same instance size, workload and client concurrency. Each process has its own client resources, so include adapter replica count in the experiment.

### Batch independent work

For a Spanner-backed test table with the example key schema, save this as `batch-orders.json`:

```json theme={null}
{
  "tuning-orders": [
    {
      "PutRequest": {
        "Item": {
          "tenant_id": { "S": "tenant-001" },
          "order_id": { "S": "batch-001" },
          "status": { "S": "new" }
        }
      }
    },
    {
      "PutRequest": {
        "Item": {
          "tenant_id": { "S": "tenant-002" },
          "order_id": { "S": "batch-002" },
          "status": { "S": "new" }
        }
      }
    }
  ]
}
```

```bash theme={null}
aws dynamodb batch-write-item --request-items file://batch-orders.json
```

Inspect `UnprocessedItems` and retry only the remaining work using the application's retry policy. Batching can amortize native work across independent puts and deletes. It does not provide cross-item atomicity, express conditional writes or replace `TransactWriteItems`. Include time spent waiting to assemble a batch in end-to-end latency.

### Decide when to add processing units

Increase client concurrency gradually while measuring Spanner service utilization and application p95/p99 latency. Add instance capacity when service utilization is the bottleneck. If utilization is low, investigate channels, client queueing, network placement and the operation's commit path first.

Ordinary reads on this mapping are strong even when `ConsistentRead=false`. Changing that flag does not buy a cheaper consistency mode. Conditional and complex updates can read the item before committing, so a benchmark of simple assignments does not predict their latency.

Choose regional or multi-region placement for the failure coverage you need, then measure the application's actual locations. Connection tuning cannot eliminate cross-region latency. See [Spanner behavior and transactions](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table#cloud-spanner).

## Bigtable: distinguish a hot row from a full cluster

Bigtable capacity belongs to the cluster. The adapter uses a connection pool and a single-cluster-routing app profile so its strong-read and conditional-write behavior has a consistent target.

The pool defaults to 64 channels and accepts values from 1 through 64. For a smaller workload, compare this configuration with the default:

```json theme={null}
{
  "config": {
    "BIGTABLE_NUM_CHANNELS": "16"
  }
}
```

Restart the selected backend after changing the setting. Hold the cluster size and workload constant during the channel comparison. More channels consume process resources; fewer can queue requests before Bigtable receives them.

Use two workload cases:

| Case              | Example keys                                    | What it reveals                                                             |
| ----------------- | ----------------------------------------------- | --------------------------------------------------------------------------- |
| Independent items | Many tenants and order IDs                      | Whether throughput grows with useful concurrency and distributed row access |
| Contended item    | Many clients updating the same tenant/order key | Conflict and retry cost on one row                                          |

Measure unconditional puts separately from conditional or returned-image updates. Guarded read-modify-write can repeat when another client changes the row. Adding cluster nodes cannot split a single item's contention across machines.

Use cluster utilization, hottest-node utilization and native service latency to decide whether to add nodes. Adding clusters does not itself enable transparent failover in the adapter's single-cluster route. The [Bigtable profile](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table#cloud-bigtable) also identifies the transaction, index, stream and TTL differences that capacity tuning cannot change.

## PostgreSQL: budget the shared connection pool

The PostgreSQL backend is available through [Cloud SQL](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table#cloud-sql-for-postgresql), [Scaleway Managed PostgreSQL](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table#scaleway-managed-database-for-postgresql), and [PostgreSQL operated with the deployment](/cloud-adapter/service-catalog/aws/databases-storage/dynamodb-table#cloudnativepg). They share the DynamoDB adapter's PostgreSQL request behavior; their database provisioning and operations differ.

Provision the database dependency before starting the adapter, and keep its connection coordinates and credentials in their appropriate configuration and secret stores. The compact installation file needs extra service configuration for this dependency, not a redundant declaration of every automatically derived service mapping.

### Increase the pool only within the database budget

`T9_POSTGRES_POOL_SIZE` is a process environment setting. Its default is 8 connections per pool. To compare a 16-connection pool on the Cloud SQL backend:

```bash theme={null}
T9_POSTGRES_POOL_SIZE=16 tensor9 adapt svc run \
  --origin aws::1.0.0::dynamodb::table \
  --backend google::1.0.0::cloudsql::postgresql
```

Use the database dependency and identity already configured for that backend. The command changes the pool limit; it does not provision or resize Cloud SQL.

Calculate the total budget before increasing it:

| Consumer                                            | Example allocation   |
| --------------------------------------------------- | -------------------- |
| Four adapter processes, one 16-connection pool each | Up to 64 connections |
| Other applications using the database               | 20 connections       |
| Administrative and monitoring headroom              | 16 connections       |
| Planned total                                       | 100 connections      |

This example assumes one pool per process. If a process opens several backend pools, count each one. Include temporary overlap during rolling replacements and connection refresh. Compare the total with the database's actual connection limit and memory budget.

### Match the remedy to the bottleneck

* If requests wait for a pool slot while database CPU and storage have headroom, try a modest pool increase.
* If database CPU or storage latency is saturated, a larger pool can make tail latency worse. Size the database or reduce work per request.
* If many requests contend on the same item, inspect lock waits and retry rates. More connections do not remove the conflict.
* If table or index creation overlaps a load spike, include that schema work in capacity planning.

DynamoDB tables become physical PostgreSQL relations. This mapping has a 500-table guardrail per namespace; increasing the database size does not remove it. DynamoDB `UpdateTable` capacity changes also do not resize the PostgreSQL instance.

Back up table metadata, items, index state and stream state together. A throughput improvement is not sufficient if the recovery procedure cannot restore a consistent application view.

## Run a repeatable comparison

For every backend, keep one experiment record with the mapping, target identity, resource ownership, requested setting, effective setting and workload. Include the application-visible outcome and the native observation.

| Keep fixed                                              | Measure                                            |
| ------------------------------------------------------- | -------------------------------------------------- |
| Initial data, item sizes and key distribution           | Successful operations per second                   |
| Read/write/query mix and condition expressions          | End-to-end p50, p95 and p99 latency                |
| Return values, indexes, streams and TTL                 | Errors, conflicts, throttling and retries          |
| Client location, adapter replicas and SDK configuration | Adapter CPU, memory and queueing                   |
| Warm-up and measurement duration                        | Native capacity use, storage work and billed units |

Change one setting at a time. Repeat the baseline after the tuned run to check whether a warm cache, a background operation or a different load level explains the result. Record the warm-up separately rather than dropping slow startup behavior without explanation.

Use [request explanations](/cloud-adapter/debugging/using-explain) and [diagnostic response headers](/cloud-adapter/debugging/response-headers) to inspect representative requests. Preserve the explanation reference alongside the measurements. For throughput runs, keep the diagnostic mode and sampling policy consistent so the instrumentation is not another changing variable.

## Change and rollback checklist

1. Identify the owner: table container, shared database, or adapter process.
2. Save the previous requested and effective settings.
3. Check whether the change requires a process restart, a native infrastructure operation, or a new destination and data migration.
4. Apply the change through the owning configuration and wait for readiness.
5. Verify both the origin request behavior and native target settings.
6. Compare the same workload and restore the earlier configuration if the result is worse.

Rollback has data consequences in some cases. Disabling Firestore's relaxed-size updates does not shrink items. Moving between Cosmos account modes or changing backends does not move data. Reducing capacity also does not necessarily remove storage or throughput-history constraints on the native resource.

Continue with [Tuning Examples](/cloud-adapter/tuning/examples), [Configuration Examples](/cloud-adapter/configuration/examples), and the [AWS Service Catalog](/cloud-adapter/service-catalog/aws/catalog) for related mappings.
