Skip to content

Skip the full service scan when loading a known service model - #3822

Open
jonathan343 wants to merge 1 commit into
developfrom
perf/lazy-service-discovery
Open

jonathan343 wants to merge 1 commit into
developfrom
perf/lazy-service-discovery

Conversation

@jonathan343

@jonathan343 jonathan343 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Every create_client call goes through Loader.load_service_model, which first checks that the service exists by calling list_available_services(type_name). That call walks every service directory on the search path (437 services) looking for a matching file like service-2.json for a specific service. The result is cached per type, but create_client needs two types, service-2 and endpoint-rule-set-1. So the first client in each session does two full scans, which is roughly 16 of the 33 ms it takes to create an S3 client. Short-lived processes that only create a client or two pay that cost on every run.

With this change, the loader looks in the requested service's own directory first. Names still have to match a directory name exactly, so S3, s3/, and ec2/../s3 are still rejected. If the service isn't there, the loader falls back to the full scan, so an unknown name raises the same UnknownServiceError, with the same list of valid names, as before.

Before / After

I ran this script from the repo root on develop and on this branch:

import os
import time
from unittest import mock

import botocore.session

session = botocore.session.get_session()
start = time.perf_counter()
session.create_client('s3', region_name='us-east-1')
print(f'create_client: {(time.perf_counter() - start) * 1000:.1f} ms')

with mock.patch('os.listdir', wraps=os.listdir) as listdir:
    botocore.session.get_session().create_client('s3', region_name='us-east-1')
print(f'os.listdir calls: {listdir.call_count}')

Before (develop):

create_client: 30.8 ms
os.listdir calls: 878

After:

create_client: 16.2 ms
os.listdir calls: 6

Client creation takes about half as long, and the directory listings drop from 878 to 6. Both ran locally on an Apple M5 Max with Python 3.12. Across five runs on each side, the times varied by less than 1.5 ms.

Additional Testing

I also compared load_service_model results between develop and this branch for every bundled service, using service-2, paginators-1, and waiters-2, plus a few odd names like S3, s3/, and ec2/../s3. The loaded models and error messages were identical in every case. This was a local check, not part of CI.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

load_service_model no longer lists every available service to confirm
a service exists, cutting filesystem calls for a cold s3 load from
~5,600 to 70. Unknown names still fall back to the full listing.
@jonathan343
jonathan343 marked this pull request as ready for review October 13, 2026 00:58
@jonathan343
jonathan343 requested a review from a team as a code owner October 13, 2026 00:58

@alexgromero alexgromero left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Comment thread botocore/loaders.py
service_name=service_name,
known_service_names=', '.join(known_services),
)
if not self._is_known_service(service_name, type_name):

@arandito arandito Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before, the result of list_available_services was cached per model type. So subsequent model loads after the first client in a session would be fast.

However, _is_known_service lists the entire data directory every time a model is loaded without caching it. The first client is faster, but subsequent clients now slow down.

I confirmed this locally by creating a client for each service we support and it took about ~1ms more per new client (Macbook M3 Pro, 3.14). This was the mean amongst all 400+ though, the first couple clients in the iteration saw +2ms.

Something to confirm to see if we need to cache the listdir result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants