DynamoDB performance drop? #1292
Replies: 3 comments 1 reply
|
Hi @javafanboy, |
|
@javafanboy your suspicion holds up. I measured it on Setup: one table with
The nightly build gives the same numbers, with either
Loading 50k items took 268 s on 2.3.0 in Here's what's going on in the code (
What helps right now:
The benchmark script, in case anyone wants to rerun it: ddb_bench.py"""Measure floci DynamoDB latency as a table grows.
usage: ddb_bench.py ENDPOINT LABEL SIZE[,SIZE...]
"""
import random
import statistics
import sys
import time
from concurrent.futures import ThreadPoolExecutor
import boto3
from botocore.config import Config
endpoint, label, sizes = sys.argv[1], sys.argv[2], [int(s) for s in sys.argv[3].split(",")]
OPS = 300
PAYLOAD = "x" * 200
cfg = Config(retries={"max_attempts": 10, "mode": "standard"}, max_pool_connections=16)
ddb = boto3.client("dynamodb", endpoint_url=endpoint, region_name="us-east-1",
aws_access_key_id="test", aws_secret_access_key="test", config=cfg)
TABLE = "bench"
ddb.create_table(TableName=TABLE, BillingMode="PAY_PER_REQUEST",
AttributeDefinitions=[{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"}])
# A fixed 20-item partition, so Query cost doesn't grow with the partition itself.
for i in range(20):
ddb.put_item(TableName=TABLE, Item={"pk": {"S": "q"}, "sk": {"S": f"{i:04d}"}, "d": {"S": PAYLOAD}})
def key(i):
return {"pk": {"S": f"p{i % 1000}"}, "sk": {"S": f"{i:08d}"}}
def write_batch(start, end):
reqs = [{"PutRequest": {"Item": {**key(i), "d": {"S": PAYLOAD}}}} for i in range(start, end)]
while reqs:
resp = ddb.batch_write_item(RequestItems={TABLE: reqs})
reqs = resp.get("UnprocessedItems", {}).get(TABLE, [])
def timed(fn):
out = []
for _ in range(OPS):
t = time.perf_counter()
fn()
out.append((time.perf_counter() - t) * 1000)
return out
def summary(ms):
ms = sorted(ms)
return f"{statistics.median(ms):7.2f} {ms[int(len(ms) * 0.95)]:7.2f}"
loaded = 0
print(f"# {label}")
print(f"{'items':>7} {'load_s':>7} | {'GetItem p50 p95':>15} | {'PutItem p50 p95':>15} | "
f"{'UpdateItem p50 p95':>18} | {'Query p50 p95':>15}")
for size in sizes:
t = time.perf_counter()
with ThreadPoolExecutor(8) as pool:
list(pool.map(lambda s: write_batch(s, min(s + 25, size)), range(loaded, size, 25)))
load_s = time.perf_counter() - t
loaded = size
get = timed(lambda: ddb.get_item(TableName=TABLE, Key=key(random.randrange(size))))
put = timed(lambda: ddb.put_item(TableName=TABLE,
Item={**key(random.randrange(size)), "d": {"S": PAYLOAD}}))
upd = timed(lambda: ddb.update_item(TableName=TABLE, Key=key(random.randrange(size)),
UpdateExpression="SET n = if_not_exists(n, :z) + :o",
ExpressionAttributeValues={":z": {"N": "0"}, ":o": {"N": "1"}}))
qry = timed(lambda: ddb.query(TableName=TABLE, KeyConditionExpression="pk = :p",
ExpressionAttributeValues={":p": {"S": "q"}}))
print(f"{size:7d} {load_s:7.1f} | {summary(get):>15} | {summary(put):>15} | "
f"{summary(upd):>18} | {summary(qry):>15}", flush=True) |
|
Thanks a lot @Mohith1612 for putting this together. The tables, the code pointers and especially the script make it easy for us to reproduce, and they turn @javafanboy's suspicion into something we can measure and fix. And thank you @javafanboy for raising it back in June; you were right. Where things stand on
Once those land we'll rerun your script at the same sizes and post the numbers here, so we can see the change against your baseline. If you'd like to pick one of them up, let us know and we'll coordinate so nobody duplicates work. |
Uh oh!
There was an error while loading. Please reload this page.
I think I am seeing a tendency of the curent DDB implementation in floci to have increasing latency (also for the basic operations that "should be O(1)" as the dataset increase in size - is this known or should I try confurming my suspicion with some reproducible test?
All reactions