Skip to content

Performance regression in S3 URL signing #3528

@akx

Description

@akx

Describe the bug

While profiling our app, I noticed quite a bit of time being spent signing URLs with Boto3/Botocore, so I decided to dig a little deeper, and turns out that there's been pretty significant performance regressions (that aren't helped by awscrt) – namely, current versions of botocore are around 3x slower when calling S3.Client.get_presigned_url.

The below results were generated with this benchmark code on my macOS machine.

boto3 botocore awscrt count mean (ms) median (ms) std (ms) min (ms) max (ms) Visual Chart
1.20.54 1.23.54 - 10 680.1 676.1 20.3 658.0 720.9 ███████░░░░░░░░░░░░░
1.20.54 1.23.54 0.27.4 10 600.8 592.3 19.3 586.5 648.4 ██████░░░░░░░░░░░░░░
1.21.46 1.24.46 - 10 671.7 666.3 21.6 654.9 729.1 ███████░░░░░░░░░░░░░
1.21.46 1.24.46 0.27.4 10 590.9 589.0 8.3 582.1 611.0 ██████░░░░░░░░░░░░░░
1.22.13 1.25.13 - 10 657.2 656.1 10.0 646.1 678.2 ███████░░░░░░░░░░░░░
1.22.13 1.25.13 0.27.4 10 583.9 582.3 8.8 569.8 600.7 ██████░░░░░░░░░░░░░░
1.23.10 1.26.10 - 10 649.6 650.0 4.0 640.8 654.0 ███████░░░░░░░░░░░░░
1.23.10 1.26.10 0.27.4 10 588.5 587.0 11.5 571.8 614.7 ██████░░░░░░░░░░░░░░
1.24.96 1.27.96 - 10 658.7 651.8 27.3 643.8 735.5 ███████░░░░░░░░░░░░░
1.24.96 1.27.96 0.27.4 10 593.6 591.6 12.2 582.3 621.1 ██████░░░░░░░░░░░░░░
1.25.5 1.28.5 - 10 867.0 868.2 5.7 855.3 876.4 █████████░░░░░░░░░░░
1.25.5 1.28.5 0.27.4 10 791.7 790.6 4.3 786.1 798.0 █████████░░░░░░░░░░░
1.26.165 1.29.165 - 10 866.3 866.8 5.7 858.3 876.4 █████████░░░░░░░░░░░
1.26.165 1.29.165 0.27.4 10 799.9 798.7 10.6 788.5 822.9 █████████░░░░░░░░░░░
1.27.1 1.30.1 - 10 868.2 866.5 10.2 853.4 892.5 █████████░░░░░░░░░░░
1.27.1 1.30.1 0.27.4 10 813.1 804.6 26.9 796.0 885.0 █████████░░░░░░░░░░░
1.28.85 1.31.85 - 10 862.5 860.0 8.9 851.1 876.2 █████████░░░░░░░░░░░
1.28.85 1.31.85 0.27.4 10 803.9 803.9 10.3 789.3 822.7 █████████░░░░░░░░░░░
1.29.7 1.32.7 - 10 1567.2 1562.6 31.6 1534.6 1636.7 █████████████████░░░
1.29.7 1.32.7 0.27.4 10 1747.7 1696.8 173.2 1578.1 2121.9 ████████████████████
1.33.13 1.33.13 - 10 1615.0 1606.5 32.2 1589.0 1697.3 ██████████████████░░
1.33.13 1.33.13 0.27.4 10 1553.3 1547.6 24.4 1521.6 1607.0 █████████████████░░░
1.34.162 1.34.162 - 10 1622.3 1620.6 14.7 1594.0 1650.8 ██████████████████░░
1.34.162 1.34.162 0.27.4 10 1562.7 1564.1 21.5 1534.2 1596.0 █████████████████░░░
1.35.99 1.35.99 - 10 1632.7 1632.2 11.5 1615.9 1652.2 ██████████████████░░
1.35.99 1.35.99 0.27.4 10 1588.0 1577.0 30.7 1564.6 1652.2 ██████████████████░░
1.36.26 1.36.26 - 10 1648.2 1645.8 26.3 1617.4 1706.7 ██████████████████░░
1.36.26 1.36.26 0.27.4 10 1586.0 1578.0 19.0 1569.6 1624.0 ██████████████████░░
1.37.38 1.37.38 - 10 1692.2 1688.0 16.5 1676.7 1725.5 ███████████████████░
1.37.38 1.37.38 0.27.4 10 1591.2 1589.9 12.8 1571.4 1611.4 ██████████████████░░
1.38.46 1.38.46 - 10 1673.3 1667.4 17.4 1650.5 1703.1 ███████████████████░
1.38.46 1.38.46 0.27.4 10 1646.0 1624.9 65.5 1602.4 1824.1 ██████████████████░░

Of course it's not generally feasible to run old Botocore versions in production.

Regression Issue

  • Select this option if this issue appears to be a regression.

Expected Behavior

URL signing to be as fast as it has been with older versions of Botocore.

For the time being, I wrote a small alternative implementation of S3V4 signing that runs circles around the Botocore code.

Current Behavior

Please see above.

Reproduction Steps

Via https://github.com/akx/botocore-sign-perf:

import boto3

s3_client = boto3.client(
    "s3",
    aws_access_key_id="AKIAI" * 10,
    aws_secret_access_key="nom" * 10,
    region_name="eu-west-1",
    config=Config(
        signature_version="s3v4",
        s3={"addressing_style": "path"},
    ),
)

t0 = time.time()
for x in range(10_000):
    s3_client.generate_presigned_url(
        ClientMethod="get_object",
        Params={"Bucket": "bucket", "Key": str(x)},
        ExpiresIn=120,
    )
t1 = time.time()
sign_time_ms = (t1 - t0) * 1000

Possible Solution

Based on comparing the pyinstrument reports

uv run --with=pyinstrument --with=botocore==1.23.54 --with=boto3 pyinstrument --show='*/boto*' -o old.html main.py
uv run --with=pyinstrument --with=botocore==1.38.46 --with=boto3 pyinstrument --show='*/boto*' -o new.html main.py

it looks like the new versions spend more time in S3._resolve_endpoint_ruleset (that doesn't exist in 1.23.54) than in RequestSigner itself.

I suspect that has to do with evaluating the DSL in the 10k+ line JSON file https://github.com/boto/botocore/blob/develop/botocore/data/s3/2006-03-01/endpoint-rule-set-1.json 😄

Maybe someone should take a critical look at whether EndpointRulesetResolver could cache results, or if it can be sidestepped altogether.

Additional Information/Context

No response

SDK version used

Multiple, see above.

Environment details (OS name and version, etc.)

macOS, but this is reproducible anywhere.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis issue is a confirmed bug.p2This is a standard priority issuepotential-regressionMarking this issue as a potential regression to be checked by team member

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions