This report presents YCSB (Yahoo! Cloud Serving Benchmark) benchmark results for PolarDB for PostgreSQL's DynamoDB-compatible API across five workload types and four data scales. Use these results for technology selection, application design, and capacity planning.
Point query performance peaks at 109,808 OPS (Operations Per Second) at 100 GB data volume.
Test result summary
The following table shows peak OPS for each workload at different data volumes. Each value is the highest OPS observed across all concurrency levels tested.
| Test scenario | 1 GB | 10 GB | 100 GB | 1 TB |
|---|---|---|---|---|
| 100% write (Insert) | 41,430 | 39,861 | 33,357 | 36,248 |
| 100% update (Update) | 44,177 | 41,486 | 38,062 | 30,782 |
| 100% read (Read) | 80,573 | 82,856 | 109,808 | 75,108 |
| 50% read + 50% update | 45,010 | 42,962 | 39,805 | 32,021 |
| 100% range scan (Scan) | 1,073 | 1,089 | 1,075 | 922 |
All values are peak OPS obtained by running each workload at multiple concurrency levels and recording the highest result.
Test environment
Environment configuration
| Component | Specifications |
|---|---|
| Test cluster (PolarDB) | 1 cluster; PostgreSQL 14 kernel; Enterprise Edition; Dedicated, 16-core 64 GB |
| Stress testing client (ECS) | 1 client; 16-core 64 GB; Alibaba Cloud Linux 3.2104 LTS 64-bit |
| Deployment region | Beijing, Zone K |
Benchmark tool
Metric: OPS — the number of operations the database processes per second
Workload model
All workloads use YCSB CoreWorkload. The test data model uses records that each contain 10 fields, with each field value at 100 bytes, making each record approximately 1 KB.
| YCSB workload | Scenario | Core parameter |
|---|---|---|
workload_insert_only | 100% write | insertproportion=1.0 |
workload_update_only | 100% update | updateproportion=1.0 |
workload_read_only | 100% point query | readproportion=1.0 |
workload_read_update | 50% read + 50% update | readproportion=0.5, updateproportion=0.5 |
workload_scan_only | 100% range scan | scanproportion=1.0 |
Appendix: Reproduce the tests
Follow these steps to reproduce the performance tests for secondary validation or custom testing.
Prerequisites
Before you begin, make sure you have:
A PolarDB for PostgreSQL cluster (Enterprise Edition, Dedicated 16-core 64 GB)
An ECS instance (16-core 64 GB, Alibaba Cloud Linux 3.2104 LTS 64-bit) in the same region as the cluster
A dedicated DynamoDB account created in the PolarDB console — see Create a dedicated DynamoDB account and Configure a DynamoDB endpoint
YCSB downloaded and built on the ECS instance
Step 1: Configure YCSB
Configure identity credentials
Edit dynamodb/conf/AWSCredentials.properties and enter the credentials for your dedicated DynamoDB account:
# The account name is the AccessKey ID
accessKey = <YOUR_ACCESS_KEY_ID>
# Secret key
secretKey = <YOUR_SECRET_ACCESS_KEY>Configure connection properties
Edit dynamodb/conf/dynamodb.properties and specify the PolarDB cluster connection:
# The absolute path of the authentication file
dynamodb.awsCredentialsFile = /path/to/your/AWSCredentials.properties
# Create a usertable table in the cluster in advance.
# This example creates a usertable table with only a partition key named pk.
dynamodb.primaryKey = pk
# The DynamoDB endpoint for PolarDB. Must include the http:// prefix.
dynamodb.endpoint = http://<your-polardb-ddb-endpoint>:<port>
# Leave the region parameter blank.
dynamodb.region =
# The primary key name and type for the test table. Must match the test table.
dynamodb.primaryKey = HASHStep 2: Patch the YCSB update operation
The official YCSB DynamoDB client uses the deprecated AttributeUpdates parameter by default. PolarDB requires the UpdateExpression parameter. You must apply the following change before running any update workloads.
File to modify: dynamodb/src/main/java/site/ycsb/db/DynamoDBClient.java Method to modify: update()
Replace the original implementation:
@Override
public Status update(String table, String key, Map<String, ByteIterator> values) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("updatekey: " + key + " from table: " + table);
}
Map<String, AttributeValueUpdate> attributes = new HashMap<>(values.size());
for (Entry<String, ByteIterator> val : values.entrySet()) {
AttributeValue v = new AttributeValue(val.getValue().toString());
attributes.put(val.getKey(), new AttributeValueUpdate().withValue(v).withAction("PUT"));
}
UpdateItemRequest req = new UpdateItemRequest(table, createPrimaryKey(key), attributes);
try {
dynamoDB.updateItem(req);
} catch (AmazonServiceException ex) {
LOGGER.error(ex);
return Status.ERROR;
} catch (AmazonClientException ex) {
LOGGER.error(ex);
return CLIENT_ERROR;
}
return Status.OK;
}With the UpdateExpression-based implementation:
@Override
public Status update(String table, String key, Map<String, ByteIterator> values) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("updatekey: " + key + " from table: " + table);
}
StringBuilder updateExp = new StringBuilder("SET ");
Map<String, String> attrNames = new HashMap<>();
Map<String, AttributeValue> attrValues = new HashMap<>();
boolean first = true;
for (Map.Entry<String, ByteIterator> entry : values.entrySet()) {
String attr = entry.getKey();
if (!first) {
updateExp.append(", ");
}
String attrName = "#" + attr;
String valueName = ":" + attr;
updateExp.append(attrName).append(" = ").append(valueName);
attrNames.put(attrName, attr);
attrValues.put(valueName, new AttributeValue(entry.getValue().toString()));
first = false;
}
UpdateItemRequest req = new UpdateItemRequest()
.withTableName(table)
.withKey(createPrimaryKey(key))
.withUpdateExpression(updateExp.toString())
.withExpressionAttributeNames(attrNames)
.withExpressionAttributeValues(attrValues);
try {
dynamoDB.updateItem(req);
} catch (AmazonServiceException ex) {
LOGGER.error(ex);
return Status.ERROR;
} catch (AmazonClientException ex) {
LOGGER.error(ex);
return CLIENT_ERROR;
}
return Status.OK;
}Step 3: Run the test
Each test run has two phases: Load Phase (populate test data) and Run Phase (execute the workload). The examples below use a 1 GB dataset (1 million records), 128 concurrent threads, and the read-only workload.
To test different data volumes, adjust therecordcountandoperationcountparameters.
Load Phase — populate test data
# -s: Displays status updates during loading.
# -P workloads/workload_read_only: Specifies the workload file.
# -P /path/to/dynamodb.properties: Specifies the database connection configuration.
# -p recordcount=1000000: Total number of records to load (1 GB).
# -p operationcount=1000000: Total number of operations.
# -threads 128: Number of concurrent threads.
nohup ./bin/ycsb load dynamodb -s \
-P workloads/workload_read_only \
-P /path/to/dynamodb.properties \
-p recordcount=1000000 \
-p operationcount=1000000 \
-threads 128 \
> load.log 2>&1 &Run Phase — execute the workload
After loading completes, run the workload:
nohup ./bin/ycsb run dynamodb -s \
-P workloads/workload_read_only \
-P /path/to/dynamodb.properties \
-p recordcount=1000000 \
-p operationcount=1000000 \
-threads 128 \
> run.log 2>&1 &Read the results
When the Run Phase completes, run.log contains a summary block at the end. The key metric is Throughput(ops/sec) under [OVERALL]. To find the peak OPS, run the same workload at different concurrency levels and compare the throughput values.
If you reproduce the tests with the same environment configuration, your peak throughput should be comparable to the values in the Test result summary table. Significant deviations may indicate resource contention, network latency, or a configuration mismatch — verify your cluster specifications, client placement, and connection settings.