Skip to content

Reduce StripeClient() cold start latency for serverless environments#1834

Draft
jar-stripe wants to merge 4 commits into
masterfrom
jar/lazy-imports
Draft

Reduce StripeClient() cold start latency for serverless environments#1834
jar-stripe wants to merge 4 commits into
masterfrom
jar/lazy-imports

Conversation

@jar-stripe

@jar-stripe jar-stripe commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Why?

Users in serverless environments (AWS Lambda with 128MB memory / 3s timeout) report that stripe.StripeClient() initialization alone can exceed their invocation timeout budget. A major contributor to this is the fact that HTTP client library imports (requests, httpx) happen inside StripeClient.__init__(), putting their cost in the invoke phase (3s budget) rather than the module init phase (10s budget).

Additionally, uuid (used only for generating idempotency keys) pulls in 20 transitive modules including re, enum, and platform, adding 300-700ms at Lambda-scale CPU.

What?

  • Move HTTP client library detection to module load time. The available client class is resolved once when stripe._http_client is first imported, so new_default_http_client() and new_http_client_async_fallback() just instantiate the pre-resolved class without any imports.
  • Replace uuid.uuid4() with os.urandom(16).hex() for idempotency key generation. Produces an equivalent 32-char cryptographically random hex string without importing the uuid module. os is already imported in _api_requestor.py.

See Also

Changelog

  • Moves HTTP library imports to module load time to better accommodate AWS Lambda and other serverless environments that have separate Init phase and Invoke phase time budgets.

jar-stripe and others added 4 commits June 22, 2026 15:53
asyncio was imported at module level in _http_client.py but only used
by AIOHTTPClient.sleep_async(). Since _http_client is loaded eagerly
by stripe/__init__.py, every user paid ~1.8s on CPU-constrained
environments (e.g. Lambda cold starts) for a module they likely never
use. Move the import into AIOHTTPClient.__init__ so only users with
aiohttp installed incur the cost.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
The uuid module pulls in 20 transitive modules (including re, enum,
platform) and takes 300-700ms to import under Lambda-like CPU
constraints. Since os is already imported in this file and
os.urandom(16).hex() produces an equivalent 32-char hex string with
cryptographic randomness, we can drop the uuid dependency entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Instead of detecting and importing HTTP libraries (requests, httpx, etc.)
inside StripeClient.__init__, resolve them once at module load time. This
shifts the expensive import cost from the invoke phase (3s Lambda timeout)
to the init phase (10s budget).

The resolved class is stored directly — new_default_http_client() and
new_http_client_async_fallback() just instantiate it. Adding a new client
means defining the class and adding one entry to the resolution cascade
at the bottom of the file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe

jar-stripe commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Benchmark Results

Methodology: Each measurement is a separate Docker container (python:3.11-slim) with --cpus=0.1 (approximating a 128-256MB AWS Lambda). Each container does a fresh pip install -e . then runs a single cold-start measurement of import stripe and stripe.StripeClient('sk_test_fake'). 10 iterations per configuration.

Baseline (master)

import stripe: 1.100s  StripeClient(): 2.495s  Total: 3.595s
import stripe: 0.608s  StripeClient(): 0.989s  Total: 1.597s
import stripe: 1.195s  StripeClient(): 1.905s  Total: 3.101s
import stripe: 1.294s  StripeClient(): 2.307s  Total: 3.601s
import stripe: 1.490s  StripeClient(): 2.003s  Total: 3.493s
import stripe: 0.911s  StripeClient(): 1.585s  Total: 2.495s
import stripe: 1.296s  StripeClient(): 2.298s  Total: 3.593s
import stripe: 0.706s  StripeClient(): 2.193s  Total: 2.898s
import stripe: 1.499s  StripeClient(): 2.203s  Total: 3.702s
import stripe: 1.004s  StripeClient(): 2.501s  Total: 3.505s

Mean: import stripe=1.110s  StripeClient()=2.048s  Total=3.158s

Fixed (this PR)

import stripe: 1.705s  StripeClient(): 0.105s  Total: 1.809s
import stripe: 3.893s  StripeClient(): 0.108s  Total: 4.002s
import stripe: 2.797s  StripeClient(): 0.206s  Total: 3.003s
import stripe: 2.698s  StripeClient(): 0.099s  Total: 2.797s
import stripe: 2.099s  StripeClient(): 0.114s  Total: 2.214s
import stripe: 2.904s  StripeClient(): 0.108s  Total: 3.012s
import stripe: 2.705s  StripeClient(): 0.205s  Total: 2.910s
import stripe: 3.098s  StripeClient(): 0.111s  Total: 3.208s
import stripe: 3.009s  StripeClient(): 0.194s  Total: 3.203s
import stripe: 2.800s  StripeClient(): 0.205s  Total: 3.005s

Mean: import stripe=2.771s  StripeClient()=0.145s  Total=2.916s

Summary

Metric Baseline Fixed Delta
import stripe 1.110s 2.771s +1.661s (moved to init phase, 10s budget)
StripeClient() 2.048s 0.145s -1.903s (invoke phase, 3s budget)
Total 3.158s 2.916s -0.242s

The key result: StripeClient() drops from ~2s to ~0.15s. The import cost for HTTP libraries such as requests shifts to import stripe, which is more friendly to environments where import stripe may be run in a different phase than StripeClient(...).

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.

1 participant