stripe-python
stripe-python copied to clipboard
Stripe import is very slow
Describe the bug
I looked around, searched the issues and even googled. Surprised this hasn't come up.
Just importing the library adds 500ms to startup time. There's got to be a way to lazy load parts.
This is on a top of the line Mac with SSD.
To Reproduce
# Takes about 20 ms
time python -c "print('ok')";
# Takes bout 500ms
time python -c "print('ok'); import stripe"
Expected behavior
For serverless setups, startup time is pretty important.
Even for local development, django's runserver or uvicorn's reload are both much slower with imports like these.
Of course you can do the import locally everywhere stripe is used, but this can be problematic.
At the very least, some guidance in the docs on how to do imports and have a lazy-loaded client would help. Something like a central get_stripe_client() -> Stripe or the like. And cover thread-safety in here.
Code snippets
No response
OS
macOS
Language version
3.12.1
Library version
11.3.0
API version
2020-08-27
Additional context
No response
Out of curiosity what serverless platform are you deploying to? This would only be an issue on cold starts, is that correct?
For production, yes, it's mostly an issue with cold starts. If you use python's importtime you can see Stripe takes up a big, big chunk. See more here: https://adamj.eu/tech/2023/03/02/django-profile-and-improve-import-time/
Cold start platforms: Google Cloud Run in my case.
But it also has local dev implications. Django and FastAPI rely on an autoreloading server. Unlike frontend tools like vite, there's no concept of hot module reloading. So this library — with no usage, just importing — is responsible for a 500ms lag in restarts.
Imagine a few other libraries like this (Google storage does the same thing), and it starts to add up.
Thanks for reporting this. We will be looking into SDK optimizations in the new year.
I noticed that that ram usage has crept up to very high levels with just import stripe
- 4.2.0 - 11mb
- 7.14.0 - 28mb
- 11.5.0 - 57mb
I tested with Docker python:3.13 image. I ran pip install stripe==version. Run python. Measure process memory. Run import stripe. Measure again and take the difference.
I discovered this by noticing a major memory usage regression in my web application, which is attributed to upgrading stripe. This is likely the same issue, though I'd argue the memory usage is just as bad or worse as the slow start time. For comparison, my entire Celery process is only 80mb, this increase was enough to halt release and investigate for me.
I took a peek with memray. I see that it imports both aiohttp and requests, lovely. Those were installed in my testing environment. _test_helpers_service looks questionable for importing. https://gitlab.com/-/snippets/4802529
Some of the import time regression could be inside requests itself, depending on which version of requests you're getting with the different versions of stripe you tested: https://github.com/psf/requests/issues/6790
I had to increase the capacity of my Fargate ECS service after adding this SDK to my project. Some optimizations would be much appreciated.
Can confirm the memory usage has been an issue for us as well. We ended up refactoring out the stripe-python sdk. Claude makes short work out of building your own stripe.construct_event() method, dropped 50mb per worker from our memory use.
@chris-mui could you elaborate on this? Do you mean still installing the library and going more low level?
Or just actual API requests directly?
Completely removed stripe sdk. Copy pasted api spec into claude to build out pydantic models for handling the requests. Construct_event() which handles webhook signature verification built in a similar fashion (didn't need any context to build this one though).
Just upgraded Stripe from a very old version to the latest, and memory usage also peaked. If someone could share a working solution to import on demand, that would be nice!
@ludo-idemenager
I have a file named slow.py
In it:
from __future__ import annotations
from typing import TYPE_CHECKING, Protocol, Type
from django.conf import settings
if TYPE_CHECKING:
import stripe
class StripeTestHelpers(Protocol):
TestClock: Type[stripe.test_helpers.TestClock]
class StripeBilling(Protocol):
Session: Type[stripe.billing_portal.Session]
class StripeModule(Protocol):
billing_portal: StripeBilling
test_helpers: StripeTestHelpers
Customer: Type[stripe.Customer]
Invoice: Type[stripe.Invoice]
CardError: Type[stripe.CardError]
InvalidRequestError: Type[stripe.InvalidRequestError]
AuthenticationError: Type[stripe.AuthenticationError]
APIConnectionError: Type[stripe.APIConnectionError]
StripeError: Type[stripe.StripeError]
Subscription: Type[stripe.Subscription]
Token: Type[stripe.Token]
Price: Type[stripe.Price]
BalanceTransaction: Type[stripe.BalanceTransaction]
PaymentMethod: Type[stripe.PaymentMethod]
def get_stripe_client() -> StripeModule:
import stripe
stripe.api_key = settings.STRIPE_SECRET_KEY
stripe.api_version = "2019-12-03"
return stripe
Those are all the typings I need, otherwise they are broken as mypy has no way to just re-export all the types of a module unchanged.
Then in my code:
def create_subscription(
request: Request, form: forms.CreateSubscriptionForm
) -> SomeReturnType:
stripe = slow.get_stripe_client()
Thanks, that's what I ended up doing too!
I have a file named
slow.pyIn it:
from future import annotations
from typing import TYPE_CHECKING, Protocol, Type
from django.conf import settings
if TYPE_CHECKING: import stripe
class StripeTestHelpers(Protocol): TestClock: Type[stripe.test_helpers.TestClock]
class StripeBilling(Protocol): Session: Type[stripe.billing_portal.Session]
class StripeModule(Protocol): billing_portal: StripeBilling test_helpers: StripeTestHelpers Customer: Type[stripe.Customer] Invoice: Type[stripe.Invoice] CardError: Type[stripe.CardError] InvalidRequestError: Type[stripe.InvalidRequestError] AuthenticationError: Type[stripe.AuthenticationError] APIConnectionError: Type[stripe.APIConnectionError] StripeError: Type[stripe.StripeError] Subscription: Type[stripe.Subscription] Token: Type[stripe.Token] Price: Type[stripe.Price] BalanceTransaction: Type[stripe.BalanceTransaction] PaymentMethod: Type[stripe.PaymentMethod]
def get_stripe_client() -> StripeModule: import stripe
stripe.api_key = settings.STRIPE_SECRET_KEY stripe.api_version = "2019-12-03" return stripeThose are all the typings I need, otherwise they are broken as
mypyhas no way to just re-export all the types of a module unchanged.Then in my code:
def create_subscription( request: Request, form: forms.CreateSubscriptionForm ) -> SomeReturnType: stripe = slow.get_stripe_client()
Over 50% of start time for our app is by the stripe SDK, and like others have observed, RAM usage has crept up as the library version has matured
Upgraded from an old version and got a major increase of memory usage. Tried several versions from major 9 to 12. None seemed to be fixed.
When can a fixed version be expected?
I can add that on AWS Lambda with python3.10 I'm sometimes seeing 3 second imports 😱
The size of this library and its memory demands are a big problem for my project. I have relatively simple needs at the moment, and am stuck using v6.0.0, which has a disk footprint of 900KB. Sadly, v12.3.0 has a disk footprint of 15MB+. With respect, that size is out of control.
Why do I care? I, too, run in a "serverless" environment (Google App Engine). I'm less concerned about startup time than I am about absolute storage needs. I would have to increase the instance machine class (at significant expense) to be able to handle this library, which is rarely used.
I really don't understand why the library needs to be so large... presumably the complexity could be/should be handled at Stripe's servers. At a minimum, perhaps we could have different versions or complementary Stripe modules so we don't need such a large footprint. Or maybe a REST API so we don't need any of your software.
Maybe somone at Stripe can spend some time to investigate the performance problems, @ramya-stripe?
Maybe somone at Stripe can spend some time to investigate the performance problems, @ramya-stripe?
Incredibly overdue, this SDK is really beginning to feel like bloatware at this point. Adding payments to an app shouldn't require suffering to performance issues
Hey folks! David here; I'm the TL on Stripe's SDKs team.
First of all, thank you for all for the (mostly) constructive criticism here. Like you, we want the Python SDK to be best-in-class and it's frustrating for everyone when we don't live up to that. Good news though: we have a path forward!
After reading through the comments, 3 main categories of issues jump out. I'll walk through what we think of them and if/how we're going to address them.
import stripe is slow
This is the issue I've been focused the most on because I think it hurts the overall DX more than the others. It's annoying both during development and in production in short-lived environments. As a result, it's our top priority.
Luckily, fixing this is fairly straightforward. There are a few places in the codebase where we import every Stripe resource (namely init.py#L277, _object_classes.py, and _stripe_client.py). All the imports are cached after the first, but it still means that the first time any process calls import stripe, you get 100% of Stripe's API surface recursively loaded into memory. That's also partially why import speed has gotten worse over time. Our API has basically only grown, so code that used to be "performant enough" no longer cuts it.
To solve, we'll be leaning much more heavily on if TYPE_CHECKING and module-level __getattr__ to lazily load only the resources you're actually using at runtime. I've already got a proof of concept working, though it'll need some more time to bake before it's production ready. Users of StripeClient will see improvements here as well, since we'll initialize our services lazily too. The stretch goal is to have this work ready by our Sept. major release, but realistically it's likely to come out later this year. The goal will be to make everything backwards compatible, but we may do an out-of-band major if something about imports has to change.
Similarly, we're a little bit sloppy with the way we load all of our supported HTTP libraries. We'll be a little more judicious here. Users who don't bring their own HTTP libraries should get small improvements. Users who have multiple supported libraries in their environments (for instance, for testing their own compatibility) should see bigger improvements. That work is slated for our Aug. minor version (v12.5.0).
Memory usage is high
I'm hopeful that our lazy imports above will help alleviate this, though I'm not positive. Based on my reading, class/type definitions don't consume too much memory (as compared to actually initializing instances of those classes). If we don't get this "for free" as part of other work, we'll take a closer look.
If anyone has any memory profiling tips/tools they've used and can recommend, I'm happy to look for more of a smoking gun here.
The SDK is too big on disk
While our SDK has a pretty large surface (and a lot of types) it's not monstrous, so its 17MB weigh in was surprising to me. Digging in, it turns out we ship roughly 10MB of .py files. Here are the top ones:
616K stripe/_payment_intent.py
444K stripe/_payment_intent_service.py
376K stripe/_invoice.py
232K stripe/checkout/_session.py
228K stripe/_account.py
224K stripe/_invoice_service.py
212K stripe/data/ca-certificates.crt
196K stripe/_setup_intent.py
188K stripe/issuing/_card.py
176K stripe/_account_service.py
168K stripe/issuing/_cardholder.py
152K stripe/_setup_intent_service.py
148K stripe/_subscription.py
132K stripe/checkout/_session_service.py
128K stripe/_charge.py
124K stripe/_payment_method_configuration.py
116K stripe/issuing/_cardholder_service.py
116K stripe/_customer.py
If you look in them, you'll find the resoruce class itself, runtime code, type annotations, docstrings, params classes, etc.
On top of that, Python (on 3.12, at least) generates roughly 6.5MB of __pycache__ directories, which accounts for the full size of the SDK.
Ultimately, it just takes a lot of bytes to describe the full Stripe API. The API has also basically only gotten bigger over the years, which is part of why all of our performance metrics have gotten worse across the same period. Code that use to be "fast enough" doesn't work when there are 3x the number of classes.
All that said, disk size isn't something we're really trying to optimize around. The SDK basically exists to do 2 things:
- HTTP-related plumbing (error handling, retries, etc) (15% of SDK)
- strong typing for all available APIs. (~85% of SDK)
Because the SDK mostly exists to provide types and the SDK is itself mostly types, removing them to save space sort of defeats the purpose of the SDK. Plus most modern production environments have enough disk space that a 20MB package shouldn't be a burden.
So, that's our plan: small import speed / memory fixes for Aug and more significant fixes available later this year.
Lastly, I want to reiterate that we do pay attention to GH issue threads and care deeply about shipping the best SDKs we can. But our small team is also also juggling 7 languages, plus internal work to support everything. So we might not resolve specific issues as fast as you think we should, but we do prioritize them accordingly.
Thanks again for the discussion here and stay tuned for updates!
I'm glad to see you're tackling this issue. While I agree that optimizing for disk space isn't usually a great strategy, when Stripe is just a tiny fraction of the capability we're trying to deliver, and running in a serverless environment, it IS an issue. We get 384MB of space per instance, and are charged by the instance. Each worker in the instance gets its own copy of everything. You can see that Stripe 20MB can easily become the Stripe 60MB.
Because we run on App Engine, we benefit from not having to do much admin. But we also can't tweak things. For example, we can't manually pull out the unnecessary bits because App Engine just helpfully loads your library from public sources for us.
All that is preamble to a suggestion that you could offer a stripped down version: automatically remove docs, whatever, for sleek runtime deployment. You could automatically generate this in devops, and publish the module as stripe-sleek (feel free to take the name).
All that is preamble to a suggestion that you could offer a stripped down version
Your use case totally makes sense and we'll take it under consideration for future prioritization (especially if other users have similar constraints)
Though at some point, it may be worth going through the Rest API rather than the SDK at all if you don't care about the types. Our HTTP code is mostly in _api_requestor, so you'd be able to copy out that portion fairly easily.
Using the REST API sounds like it could be a good idea, but as wonderful as the Stripe Docs are (with the dropdown selector for language), it doesn't have one for REST. I'd like to avoid digging into source (that might change with version). Let me know if the REST API is documented somewhere - maybe that's another side project :-)
I think you're looking for "curl": https://docs.stripe.com/api/customers/create?lang=curl
The exact format will depend on what HTTP library you're using, but ultimately it shows the basic json inputs and outputs that the Stripe API expects. All the items in the docs on the left of the page describe the keys you send and the json you get back.
Hey-a! I noticed the draft PR #1645 was opened for this, and I checked it out locally (a03fa75) and it improved the total import time from ~2s -> ~.5s locally, and all our of test suite that interacted with Stripe still passed without any changes needed!
Mostly just wanted to give some signal that the proposed changes seem to work and are a massive improvement (at least for our use-cases!) and I'm super excited for them to land!
Amazing! The only reason it's not ready for review yet is that i'm actually making it even faster 😅
Glad to hear it's working as intended though! I might ask you (and anyone else) to give the final PR a shot before it's merged, if you're willing.
Ok, the PR is ready! initial testing is very positive. If anyone besides @ek247 wants to kick the tires on it, feel free to!
I tested on my fairly large company Django monolith, and I'm seeing somewhere around 500ms faster startup times running stripe from that PR!
We released a bunch of performance improvements in v13.1.0 which came out this week. You can see all the details in https://github.com/stripe/stripe-python/pull/1645.
Initial local testing and user feedback has been very positive, so i'm going to tentatively close this issue out for now. Feel free to reply if you're seeing more import-related slowness or open a new issue if you're having performance issues with other parts of the SDK.
Thanks a bunch everyone!