-
-
Notifications
You must be signed in to change notification settings - Fork 192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhance db-scheduler to support asynchronous task execution #304
base: async
Are you sure you want to change the base?
Conversation
Enhance db-scheduler to support asynchronous task execution
Thank you! I am currently on vacation and will not be able to look at it for a couple of weeks. And it is also a critical change so I need some time to consider it |
I see some significant performance improvements as well. @amit-handda can you post some benchmarks as well? |
Hello @kagkarlsson, hope you had nice vacation. Wondering if you are back and had the time to look at the PR ? Let me know if you have any query. TY ! |
Thank you! Yes I am back, but I struggle to get the bandwidth to review this PR unfortunately. I have scanned through the code and noted a couple of things:
|
Hello @kagkarlsson , thanks for the feedback. ques |
Well, that is the problem, I do not know currently. What options do we have 🤔 |
|
||
return completableFuture.whenCompleteAsync((completion, ex) -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not 100% sure what happens here. What will the significance of whenCompleteAsync
be here?
Will it happen in parallell with executor.removeCurrentlyProcessing
here?
executePickedExecution(pickedExecution).whenComplete((c, ex) -> executor.removeCurrentlyProcessing(executionId));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using whenCompleteAsync
to associate the action
to the db-scheduler executor explicitly (otherwise, it might use the common forkjoinpool).
I dont think it ll execute in parallel to removeCurrentlyProcessing
since *Async
API only enables to specify the executor service for the action.
ref link: https://www.linkedin.com/pulse/asynchronous-programming-java-completablefuture-aliaksandr-liakh/
please let me know in case I am off.
I need to understand why a future-based execution will be that much faster. Feels like we are skipping av step.. |
Hi, It is due to our task defn. Task implementation does nothing other than sleeping for a second. After our changes, the sleeping tasks dont block the scheduler threadpool (since task implementation returns futures). Before our changes, same task implementation would block the scheduler threadpool (hence, leading to inferior performance), let me know if you have any issues. |
IMO, returning a |
I think what @amit-handda is mentioning is more specific for reactive solutions (In this case Kotlin coroutines which are non-blocking on threads). I think having this in place will unlock bunch of reactive usecase (including Spring framework users) to write their workers in more scalable way. |
The results you are referring to, where future-based and non-future based tasks are compared, what polling-strategy are they using, and what limits? In my mind it should not be pulling in more work before completing a significant part of the last batch. But that will be affected by polling-strategy changes. |
So the reactive systems that I am referring to usually use stuff like e-poll, kqueue etc. For example if you are using Reactor + Netty there are various system level event driven libraries it can build on, you can read details here. Combined with project reactor, it can get you amazing async DB libraries like R2DBC which means you can talk to DB in async manner now.
If the work it's doing is CPU intensive for sure, even regular threads will be blocked. But if the work is more IO intensive then in traditional model you are just spinning up threads and wasting cycles waiting for it to complete. That's where these So goin details a little bit more let's assume as simple job scheduler that sends out emails. All the job does is pull an email from worker job and then makes an HTTP call to say SendGrid or some external service to send email. Now in traditional thread model you won't be able to spin up more than couple of hundred thread, so you can't do processing of more than couple of hundred parallel emails at a time, however the async/reactor models unblock you from that. Since each job turns into a future promise based model, none of the threads have to block and wait until the worker lets go of thread. That's what event loops have done and unlocked at large scale. This PR IMO will exactly do that. |
I appreciate the explanation, but I was also concretely asking about what settings where used in the test :)
was referring to my mental model of how the scheduler works |
I think the thing is that if the thread-pool is not the bottleneck in terms of how many executions we may have running in parallel, we need to consider how many is safe to allow? After they finish there will be a backlog of executions to complete.. |
Hi @kagkarlsson, we used lock_and_fetch poll strategy with 0.5 (lower_limit) and 4.0 (upper_limit). please let me know if you need more info. Many thanks for your careful review feedback. Have a good day.
perf test config
|
Any updates on if this can be merged? This will really unblock a lot of use-cases. |
It is taking some time because it is not a priority feature (though I am starting to see the merits) and there are just still things to consider that I feel are unanswered. My thoughts atm
Also concerned about unknown unknowns. If it goes in, I think it needs to do so as an experimental feature. |
Would it at all be possible to enable async-mode by config-setting? |
|
I feel conflicted about this change. It will enable higher throughput using fewer threads (for non-blocking io use-cases), which is great. On the other hand, I worry about the things it might break or make more complicated. I was considering if |
Maybe it is possible to let the async version live and be released from a custom branch 🤔 |
Thanks @kagkarlsson I agree with this, we can let it breathe through custom branch please. |
If we can do that and release it a some sort of beta package. I can write some reactor benchmarks as well. |
@kagkarlsson happy friday, checking in. May I help getting it released from custom branch ? thanks |
Shall we recreate the PR against the |
Trying to merge into the |
I reviewed the past comment about this issue and curious also. we can do like below code to impl non blokc task(like http request). and on the other hand task is async or not it depends on use-case
|
Sorry for not having the bandwidth for pursuing this issue. What about using virtual threads to solve this? |
@kagkarlsson virtual thread are in EAP and even after rollout just like record classes it's gonna take years for people to move over. All of existing library and software stack still relies on |
I am experimenting some with this feature in #369. Copied the ideas from this PR, but wanted a way to experiment without affecting existing code and tasks, so making it a slightly more hack-ish add-on for now. See example AsyncOneTimeTaskMain.java |
// Since execution is executed in an async way, we need to wait for a while to let the execution finish before asserting | ||
Thread.sleep(1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sleeping in tests is an anti-pattern I am trying to avoid as much as possible :)
@kagkarlsson Virtual threads would probably be much much simpler to support for users who can use it (starting from Java 19 with Scheduler.create(...)
// ...
.executorService(Executors.newVirtualThreadPerTaskExecutor())
.build(); If I get a chance to experiment with this I will report back |
I'm also interested on knowing your results with virtual threads. |
Thank you for maintaining db-scheduler.
What
This PR enhances db-scheduler to support task execution asynchronously. It addresses this issue
Why
db-scheduler has an executorservice of threadpool. Threadpools are used to schedule db-scheduler tasks.
Tasks were executed in a synchronous way. If tasks do something (http call eg) asynchronously, the threadpool will remain blocked.
This might not be an issue where tasks are tiny or the task throughput is tiny. But, it can become an issue for some scenarios.
Current status