-
Notifications
You must be signed in to change notification settings - Fork 181
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
Sample pull request with very basic support for acquiring locks on multiple resources in one call #49
Conversation
@jsbattig thanks for your interest in the library! As a starting point, I'd like to better understand your use-case; what kind of pattern requires taking multiple locks in one pass? How many locks are being taken? What is the observed performance difference? Do you foresee this being a need for multiple consumers? It would be great to create a github issue describing some of the above and linking this PR; we can iterate there. Also, I'll note that I'm currently working on the 2.0 release of the library; you can see the latest code on the release-2.0 branch. |
Hey, thanks for your reply!
Our use case is essentially for soft-locking database entities during a long backend process where we do things in batches for better throughout.
Essentially we pick up let’s say 500 entities and all their dependencies at a time, do some number crunching on these entries and produce output back to the database related to these entities.
These processes live together with 1:1 real-time processing in other parts of the solution that will attempt to acquire a soft-lock on the entity before they touch it. If they can’t acquire a lock in a reasonable period of time they will error out back too the UI so the user knows that whatever it was trying to do he/she will have to retry later.
In my experiments the performance gain going batch on the locks is in the range of 50x.
This is the test I used to test it out:
[Test]
public void TestHighVolumeLocks()
{
using var tran = conn.BeginTransaction();
var lockNames = new List<string>();
for (var i = 0; i < 500; i++)
lockNames.Add($"LOCK{i}");
var locks = new SqlDistributedLocks(lockNames, tran);
using var handle = locks.Acquire();
}
It really depends on your network conditions, overall load in SQL, etc… but the gains are usually significant when batching up with SQL. Some of the folks I work with have been doing this “batching up” of operations against SQL and before that in the prior company we worked together with Oracle for years.
With SQL in particular when you try to batch up operations on wide tables you hit diminishing returns very quickly (the overhead of the # of fields kills any benefit you gain by reducing roundtrips and general operation overhead). But when working with skinny tables or these locks that are super light, you have good gains as compared to going back and forth on the network and all the overhead of initiating a new operation.
In our particular use case, for now she’s just acquiring the locks in a loop, processing all the ones that Acquire Lock succeeded and quarantining the ones she could not acquire in order to be retried later.
In this particular use case, as-is right now going batch would not help us a lot really because the expense of these process is so high that saving 1 second of processing every 500 entities that take 30 minutes to process doesn’t add up to much. But I can easily see scenarios where these 500 objects can be processed in a few seconds and saving 1 second there and the roundtrips is significant.
Hope this description helps.
Seba
…On Sep 14, 2020, 9:21 PM -0300, madelson ***@***.***>, wrote:
@jsbattig thanks for your interest in the library!
As a starting point, I'd like to better understand your use-case; what kind of pattern requires taking multiple locks in one pass? How many locks are being taken? What is the observed performance difference? Do you foresee this being a need for multiple consumers?
It would be great to create a github issue describing some of the above and linking this PR; we can iterate there.
Also, I'll note that I'm currently working on the 2.0 release of the library; you can see the latest code on the release-2.0 branch.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
Thanks for the detail, @jsbattig , this is really helpful. Before we go further down this road, how are you constructing your distributed lock instances today when you are acquiring 50 locks in a loop? Are you sharing a single transaction or connection between them (by passing a transaction/connection to the constructor)? If not, can you try sharing a connection/transaction and see how that changes performance? Obviously batched is going to be faster than 50 round trips, but if the library is opening 50 concurrent SQL connections under the hood then that could be very slow as well. |
We tested this using a single connection, and also a single transaction. In either case the gains going batch is 50x. If you use a connection string forget it… it takes “forever" to get so many locks.
The developer working on this is currently using Connection bound locks. She tried with an open transaction (with the benefit we can unlock at the end by committing or rolling back) but she commented that for some unknown reason the connection pool got “locked”. So she’s binding the locks to a connection and then at the end she goes through the cycle of disposing all acquired locks.
…On Sep 15, 2020, 9:48 AM -0300, madelson ***@***.***>, wrote:
Thanks for the detail, @jsbattig , this is really helpful.
Before we go further down this road, how are you constructing your distributed lock instances today when you are acquiring 50 locks in a loop? Are you sharing a single transaction or connection between them (by passing a transaction/connection to the constructor)? If not, can you try sharing a connection/transaction and see how that changes performance?
Obviously batched is going to be faster than 50 round trips, but if the library is opening 50 concurrent SQL connections under the hood then that could be very slow as well.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
What happens when an object in the collection is already locked by some other resource? Would you fail the entire batch or just that lock? Seems like you'd open up code to race conditions and potentially deadlocks. I've not fully read through the code, just my first instinct. |
@samirbanjanovic see the proposal issue for details. |
Closing since this is tracked under #50 |
This pull requests allows the user to acquire locks on multiple resources on one call.
The code ain't pretty. I tried not to modify the existing code but rather there's a new set of files that operate with an Enumerable for the lockNames and there's a new set of interfaces and classes to handle this.
I think it could be generalized to keep the code more compact.
I didn't want to delve into Semaphores either as the code is significantly more complicated on the T-SQL side.
And finally, the code has no support for the async methods.
This PR is not intended to be merged as-is but rather to take the idea if desired and implement in the core library and likely extend to other classes.