Performance
What a call costs comes in two parts: what the client allocates and spends on the CPU, and what DynamoDB charges in capacity and time. The benchmarks measure the first. The list below says what drives both.
Benchmarks
Allocations are exact, and times depend on the machine. All clients run on the same canned responses with no network, so these numbers show the client's own cost.
The four tables are on the front page of the repository, which is also the home page of this site: the time and the bytes allocated per call by the mapped APIs of DynaSharp, EfficientDynamoDb and the AWS SDK's DynamoDBContext, the low-level APIs of the three in KB, and the API levels of DynaSharp in bytes.
Why the rows differ
- The item has a set of tags. The first two tables map it to a
HashSet<string>in all three clients, so that they compare. The fourth maps it to astring[], which is lighter, so a read costs less there, and aPutItem, which only walks the set, costs the same. - A read costs more than a write, because a read builds the item it returns and a write has only a request to fill. A query of 100 items is mostly 100 times what one item costs.
- A generated entity reads straight into its properties.
Table<Item>and the request API hold each attribute as anAttributeValue, which is the step between the first column of the fourth table and the other two. TheItemof aPutItemis the object of the caller, so that cell is n/a. - The third table and the last column of the fourth are the same request API, in KB and in bytes. They come from two benchmark classes, and the
Queryof the fourth runs in anasyncmethod of the benchmark, which adds itsTask.
Running them
To run them, from the repository root:
dotnet run -c Release --project benchmarks/DynaSharp.Benchmarks
The benchmarks live in benchmarks/DynaSharp.Benchmarks.
What costs what
In the client
- One client. A client is thread-safe and holds the connections, the retry quota and, without
Credentials, a provider with its own cache of credentials. Create one and keep it. A second client makes a second set of requests to STS or the metadata service, unless both get one provider. - A command writes its request as you call it, into buffers from a pool that go back when it is sent. That is why it is built and sent in one expression (
DYNA201). - A key.
Order.Keys.Of(...)holds its values and makes no string until the command writes it, andTryOfmakes none either.Order.Keys.Pk.Of(...)andFormatmake the string of the key. A plain string or number key costs nothing beyond itself. - A condition of handles,
Order.Attrs.Total.Gt(100m).And(...), is a struct and allocates nothing.Condition.Of(...)puts one on the heap, for a field. A sort key condition of constant text allocates nothing, and one that formats a value costs its strings. - A typed read goes from JSON straight into the entity.
Table<Item>and the request API build anAttributeValueper attribute first, andTo<T>()reads that again, see Without a model. - An
Itembuilt by hand is cheap at the usual sizes, and above 32 attributes costs more than aDictionaryof known capacity. - Telemetry without a listener costs a null check per call. With one, the tags of each span and measurement, see Observability.
ToRequestJson()renders the whole request into a string, for tests and debugging.
In DynamoDB
- A filter runs after the read, so a
Queryor aScanpays for every item it reads, whatever the filter leaves:ScannedCountagainstCount. A key condition is what reads less. The filter on_typethat a typed read of a shared table adds costs nothing beyond the read. - A
Scanreads the whole table. A lookup that runs often needs a key: an index, or a claim item. Limit(n)is per page and counts before the filter, so it bounds a request, not the result..ItemsAsync(ct).Take(n)stops reading once it hasn.ItemAsyncon a query follows empty filtered pages and adds no limit, so a read without a filter that wants one item says.Limit(1).CountAsyncreads like a query of the items and charges for them, without returning them.ProjectandSelectsave bandwidth and the cost of reading into the entity, not capacity: DynamoDB charges for the whole item.Consistent()reads cost twice the capacity of an eventually consistent read.- A transaction costs twice: a write is prepared and committed, a read is the same.
- A batch sends up to 25 writes or 100 gets per request, one request after the other, and sends again what DynamoDB left unprocessed. It saves round trips, not capacity.
- A fan-out query sends one query per partition, up to
MaxConcurrency, 8 by default, reads a partition only when the merge needs it, and holds at most one page per partition. Parallel(segments)scans that many segments at once, which is faster and charges the same.- A retry waits before it goes out, and a throttled table makes every call wait, see Errors and retries.
.ReturnCapacity()says what a call consumed, and the metricdynasharp.client.consumed_capacitysums it.