stripe-python icon indicating copy to clipboard operation
stripe-python copied to clipboard

Stripe import is very slow

Open silviogutierrez opened this issue 1 year ago • 16 comments

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

silviogutierrez avatar Nov 25 '24 01:11 silviogutierrez

Out of curiosity what serverless platform are you deploying to? This would only be an issue on cold starts, is that correct?

devraj avatar Nov 25 '24 04:11 devraj

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.

silviogutierrez avatar Nov 25 '24 05:11 silviogutierrez

Thanks for reporting this. We will be looking into SDK optimizations in the new year.

prathmesh-stripe avatar Nov 25 '24 18:11 prathmesh-stripe

demoh2019 avatar Jan 21 '25 11:01 demoh2019

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.

bufke avatar Feb 04 '25 16:02 bufke

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

bufke avatar Feb 04 '25 16:02 bufke

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

stianjensen avatar Feb 04 '25 16:02 stianjensen

I had to increase the capacity of my Fargate ECS service after adding this SDK to my project. Some optimizations would be much appreciated.

rsinger86 avatar Feb 16 '25 18:02 rsinger86

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 avatar Feb 18 '25 20:02 chris-mui

@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?

silviogutierrez avatar Feb 18 '25 20:02 silviogutierrez

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).

chris-mui avatar Feb 18 '25 21:02 chris-mui

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 avatar Mar 13 '25 12:03 ludo-idemenager

@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()

silviogutierrez avatar Mar 13 '25 15:03 silviogutierrez

Thanks, that's what I ended up doing too!

@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()

ludo-idemenager avatar Mar 13 '25 16:03 ludo-idemenager

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

rushilsrivastava avatar Apr 04 '25 21:04 rushilsrivastava

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?

bjornhol avatar Jun 12 '25 19:06 bjornhol

I can add that on AWS Lambda with python3.10 I'm sometimes seeing 3 second imports 😱

fortzi avatar Jul 02 '25 11:07 fortzi

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.

Dev63 avatar Jul 27 '25 19:07 Dev63

Maybe somone at Stripe can spend some time to investigate the performance problems, @ramya-stripe?

bjornhol avatar Jul 28 '25 20:07 bjornhol

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

rushilsrivastava avatar Jul 28 '25 20:07 rushilsrivastava

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:

  1. HTTP-related plumbing (error handling, retries, etc) (15% of SDK)
  2. 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!

xavdid-stripe avatar Aug 14 '25 01:08 xavdid-stripe

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).

Dev63 avatar Aug 14 '25 01:08 Dev63

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.

xavdid-stripe avatar Aug 14 '25 01:08 xavdid-stripe

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 :-)

Dev63 avatar Aug 14 '25 02:08 Dev63

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.

xavdid-stripe avatar Aug 14 '25 16:08 xavdid-stripe

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!

ek247 avatar Oct 16 '25 00:10 ek247

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.

xavdid-stripe avatar Oct 16 '25 01:10 xavdid-stripe

Ok, the PR is ready! initial testing is very positive. If anyone besides @ek247 wants to kick the tires on it, feel free to!

xavdid-stripe avatar Oct 16 '25 06:10 xavdid-stripe

I tested on my fairly large company Django monolith, and I'm seeing somewhere around 500ms faster startup times running stripe from that PR!

stianjensen avatar Oct 16 '25 11:10 stianjensen

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!

xavdid-stripe avatar Oct 30 '25 18:10 xavdid-stripe