dynotable-orm benchmarks

These numbers measure the CPU that a call costs inside the process. A stub client returns canned responses, so they contain no network time. In a real call, DynamoDB's latency is usually larger than any of them.

How to run the benchmark, and the details of each contender and operation, are in packages/dynotable-orm/benchmarks/README.md.

Results

A create, get or update on a schema of about 60 attributes is 93 to 854 times faster than dynamoose 4.2.0, and as fast as hand-written AWS SDK code or faster. On a 5-attribute schema, hand-written SDK code is faster on some calls.

Bar chart, calls per second on a 60-attribute schema, higher is better. Create: dynotable-orm 23,300, hand-written AWS SDK 20,100, dynamoose 256. Get: dynotable-orm 107,000, hand-written AWS SDK 84,300, dynamoose 281. Update: dynotable-orm 69,400, hand-written AWS SDK 66,800, dynamoose 78.8. Query of 100 items: dynotable-orm 1,070, hand-written AWS SDK 836, dynamoose 3.76.

Measured on 2026-11-06, 20 interleaved rounds. Each panel of the chart has its own scale. Calls per second is the median over the rounds; 1,000,000 divided by it is the CPU µs per call. "Times faster" is the median of the per-round ratios.

Large schema (order, about 60 attributes)

Calls per second, higher is better:

Operationdynotable-ormHand-written SDKdynamoose 4.2.0Times faster than dynamoose
create23,30020,10025693×
get107,00084,300281385×
update69,40066,80078.8854×
nested update77,50068,900not comparable-
query (100 items)1,0708363.76288×

Nested update has no dynamoose number. The call is update(key, {$SET: {'notification.status': 'SENT', 'details.refundedAmount': 4250}}). dynamoose 4.2.0 writes each path as one top-level attribute with that name (notification.status), not as a change to the nested field. It does different work, so the benchmark does not time it.

On the large schema dynotable-orm is faster than hand-written SDK code on create, get and query, and the same on update. The reason: @aws-sdk/util-dynamodb's marshall and unmarshall cost more than this library's own conversion.

Other libraries

Bar chart, calls per second on a 5-attribute schema, higher is better. Create: dynotable-orm 625,000, hand-written AWS SDK 719,000, dynamoose 5,240, ElectroDB 29,100, dynamodb-toolbox 87,000. Get: dynotable-orm 1,490,000, hand-written AWS SDK 1,070,000, dynamoose 3,940, ElectroDB 46,300, dynamodb-toolbox 110,000. Update: dynotable-orm 671,000, hand-written AWS SDK 1,030,000, dynamoose 4,200, ElectroDB 28,800, dynamodb-toolbox 66,200. Query of 100 items: dynotable-orm 35,300, hand-written AWS SDK 37,700, dynamoose 159, ElectroDB 5,780, dynamodb-toolbox 2,170.

All five contenders ran in one process on the small schema (5 flat attributes, one global index), with 10 interleaved rounds of 200 ms. Compare numbers only within this table: they come from one run, and a different run gives different absolute numbers.

Calls per second, higher is better:

Operationdynotable-ormHand-written SDKdynamoose 4.2.0ElectroDB 3.9.3dynamodb-toolbox 2.11.1
create625,000719,0005,24029,10087,000
get1,490,0001,070,0003,94046,300110,000
update671,0001,030,0004,20028,80066,200
query (100 items)35,30037,7001595,7802,170
  • dynotable-orm is faster than dynamoose, ElectroDB and dynamodb-toolbox on every row. Against dynamoose, it is 119 to 379 times faster (the ratio of the medians).
  • Hand-written SDK code is faster than dynotable-orm on three rows: create (1.15 times), update (1.53 times) and query (1.07 times). A one-field update shows the fixed cost of dynotable-orm's update path: it checks the input and builds the expression, where hand-written code writes one SET. dynotable-orm is faster on get (1.39 times).
  • ElectroDB and dynamodb-toolbox talk to a DocumentClient. Its stub marshalls the input and unmarshalls the output, as the real one does, so every contender pays for the conversion. ElectroDB stores its own key templates and entity attributes; dynamodb-toolbox runs with its entity attribute and timestamps off.

These are CPU numbers for one small schema. They say nothing about features, about other schemas, or about network time.

Method

  • Contenders. dynotable-orm is the package's built dist. Hand-written SDK code is a Zod parse of the same input, then @aws-sdk/util-dynamodb marshall on a write and unmarshall on a read. dynamoose is dynamoose 4.2.0 from npm, making the same calls.
  • Same work. Before any timing, a smoke test checks that each contender sends the same literal request and returns the same items. Where a library cannot do the same work, it has no number on that row.
  • Schemas. order declares about 60 attributes: nested objects with checks, a list of objects with multi-type attributes, schemaless objects, a Set, enums, three global indexes and ISO timestamps. Its sample item has 35 top-level attributes and 153 leaf values. small has 5 flat attributes and one global index.
  • Operations. create, get, update (top-level fields), update-nested (one nested $SET, order only), and query returning 100 items.
  • Rounds. Every contender warms up. Each round then measures each contender for a fixed time, with gc() before each measurement, and the order of the contenders reverses every round. Inputs are made outside the timed part.

Updated