Comments (7)
Instead of a pool, one could instead implement a rate-limiting approach where every request implicitly creates a new worker. While poolboy makes pooling really easy and nice, with a pool one still needs to decide what to do when the pool is full (retry after a timeout? fail? ..) and tune pool sizes. With rate-limiting you can just spin up as many workers as currently needed, and forget about the complexities.
Payment gateways to do rate-limiting on their end, and return HTTP 429 status codes when hitting the max. This can be handled easily enough with an exponential back-off in the processing worker that tops out at some point with a failure.
(For in-bound rate limiting: cashier can simply require the user to provide that, preventing the need for assumptions in cashier about the user's design and use cases.)
If really needed in future, job queueing and back-pressure mechanics could be added to the internals to handle load spikes, API limits, etc. I doubt many people will run into real-world situations that could justify that complexity, but at least one can see a path forward there if it is ever needed.
from cashier.
That's a great point and thinking about it a much simpler approach.
There would need to be some form of state somewhere to be shared with the workers when created to prevent unnecessary authentication requests for tokens.
Would you see there being a dedicated supervisor module for each type of gateway where this kind of state can be stored and passed along?
from cashier.
I have been having a think about this and from what I have read GenStage looks like a good fit for this feature. I have yet to develop anything with GenStage so I need to have a play with it to fully wrap my head around how it works.
Alternatively if there are any other suggestions or thoughts it would be good to get them down here so all options can be looked at.
from cashier.
There would need to be some form of state somewhere to be shared with the workers
An ets table entry should be enough for this, with a named process to fetch on lookup failure. Sth like:
- on init, start a supervised process that is used to fetch shared state into the ets table
- provide a function that returns the shared state, attempting first to fetch from ets
- if it does not exist in ets, then call (blocking) the fetcher proc to get the shared state and put it in ets. This becomes the point of synchronisation., preventing more than one request for populating the shared state from being made
The result should be really fast lookups in the common case, with state that can be invalidated whenever, and data populated through a single ( purposeful bottleneck) process.
GenStage looks like a good fit for this feature
It would certainly provide an easy path to transparent pooling with back-pressure back off. If you do go for a pooling mechanism, rather than a "spawn a process per request" approach, GenStage should be a rather nice fit. Would make plugging things like logging/stat generators post-transaction really easy as well if desired in future.
The only concern I can see is that if the transaction rate is limited by the time it takes the payment processor to do its job (particularly true if the user is expected to perform any interactions with the payment service), then any pooling system will get back logged easily due to that.
I suppose with a GenStage approach each consumer in the pipeline could handle multiple async requests to alleviate that ... but that kind of complexity usually just moves the problem elsewhere (e.g. bookkeeping overhead).
This would really suck for apps with peak times for purchases, as the precessing could easily bog down right when you don't want it.
IME BEAM processes are really cheap (and.very simple) while providing a great abstraction for "the state of a job". If rate limiting is needed, the gateway service will inform the app with a rate limit error, and then processes can take appropriate action at that point.
So I would expect the whole thing to come down to: where does latency occur most acutely in the system (the app, cashier, the payment gateway service?), and where are bottlenecks most appropriate to ensure the lowest (realistic) latency for payment processing (in the app, in cashier, inthe gateway service?) I don't have enough hands-on experience with payment gateways at higher volumes to know those answers.. :/
from cashier.
I have been experimenting with GenStage as a proof of concept (see the refactor branch - this is a bit broken at the moment) and so far I feel this approach could work out well (but I'm still open minded). I will try and address some of the key points above the best I can in my half asleep state!
An ets table entry should be enough for this, with a named process to fetch on lookup failure
This is also the conclusion that I have come to now and makes perfect sense.
If you do go for a pooling mechanism, rather than a "spawn a process per request" approach, GenStage should be a rather nice fit.
GenStage can give us spawn per request when using a ConsumerSupervisor
which I have been using per gateway.
This would really suck for apps with peak times for purchases, as the precessing could easily bog down right when you don't want it.
This one is a big problem and unfortunately (or fortunately I guess) one that I haven't ever had to deal with in my day job yet. However this blog post from discord is an interesting read and addresses the same problem.
So I would expect the whole thing to come down to: where does latency occur most acutely in the system (the app, cashier, the payment gateway service?), and where are bottlenecks most appropriate to ensure the lowest (realistic) latency for payment processing (in the app, in cashier, inthe gateway service?) I don't have enough hands-on experience with payment gateways at higher volumes to know those answers.. :/
I am in the same boat as you here and I will be looking to load test which ever solution we end up with to gauge the limitations of cashier. Coming up with the acceptable benchmarks will be a case of researching other payment libraries and reaching out to those that have implemented this kind of thing in large load environments.
from cashier.
That blog entry from Discord was really cool; read it when it was published (via .. Reddit? hmm..)
Anyways, it is all a bit of an implementation detail .. and given that as you point out, people are pushing huge volumes through GenStage based code as it is, worst case scenario is a refactor down the road when someone actually has 1000s of simultaneous purchases happening .. at which point I'm sure they'd be able to contribute something back ;)
So, ... after some further pondering today, it became clear to me that I would 100% support you in a GenStage direction, should you decide that route ... cheers!
from cashier.
That's great, thanks!
I should have the initial refactor to GenStage done by the end of the week which will hopefully clarify whether it's the correct approach or not. One cool side effect that I hadn't considered is the ability to start up a custom gateway that cashier isn't aware of and have it easily hook into the existing pipeline - currently using this for testing.
But anyway, time will tell if a further rethink is needed or not.
from cashier.
Related Issues (17)
- Documentation
- Gateway error handling
- Stripe gateway HOT 6
- Store card information with service HOT 3
- Common response format HOT 2
- Currency based routing
- Gateway failover
- Example projects
- PayPal external_customer_id field for stored cards
- Gateway process doesn't stop when :stop returned
- Support for subscriptions HOT 3
- Gateway feature support metadata HOT 3
- Add async functions to the cashier module
- Braintree support HOT 6
- Fix gateway tests
- Bypass race condition
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cashier.