Repository navigation
Replies: 3 comments 3 replies
|
The current state of the GlobalLock API suggests using Based on the use case it may not be enough to synchronize just one block of code inside This raises a new set of questions to answer before we are able to continue providing lock implementations.
|
Yes, we need it. This is the purpose for Item 2 condition is invalidated by the statement above.
The argument that JPA
This can be rewritten as try (GlobalLock unlock = lock.acquire("lockName")) {
KeycloakModelUtils.runJobInTransaction(job);
}Note that both The usecase for short-lived This can be illustrated on a JPA example. (Please do not treat this as an ultimate solution.) |
|
We discussed this offline with @hmlnarik and we came up with the following: The purpose of GlobalLock API is to provide a way to synchronize some code execution across more Keycloak nodes/threads. The locks can be used in 3 situations: 1An execution with all its effects being visible to others can be enclosed into one block of code. In this situation, we don't need to take transactional processing into account as all effects of the execution are visible to everyone at the time the code block is finished. For this purpose, we recommend using the try (GlobalLock lock = lockProvider.acquire("DATABASE_UPDATE_LOCK")) {
// update database schema if necessary
}When this execution is finished the schema is updated and all other nodes can see the new schema. The lock is released. 2Effects of an execution that is synchronized are visible only when the current transaction commits. In this situation, we can not use An example of this situation is when we need to synchronize some execution that triggers a change in Keycloak storage. lockProvider.acquire("MY_LOCK_NAME")
RealmModel realm = session.getRealm("realmName");
if (!realm.getAttributes().contains("myAttrThatIsCreatedOnlyByOneInstance")) {
realm.addAttribute("myAttrThatIsCreatedOnlyByOneInstance", "I created it " + keycloakInstanceIdentifier());
}At the end of this block of code, the lock cannot be released because the changes done by 3The execution is done in more than one transaction and it needs to be acquired until all of them are finished. An example of this situation is when we want to run some migration from one store to another in more bulks. Each bulk is migrated in one of the transactions and the lock can be released only when all bulks were migrated. For this purpose we can use approach in point 1, as we can do something like this: try (GlobalLock lock = locksProvider.acquire("MIGRATON_LOCK")) {
for (Bulk b : bulks) {
runJobInTransaction() {
b.execute();
}
}
}Here is a Javadoc of GlobalLockProvider suggestion based on the above: https://gist.github.com/mhajas/ae24e0c19abe20d6430457a24fce26a3 |
Uh oh!
There was an error while loading. Please reload this page.
What we currently have
There is a
DBLockProviderinterface that allows us to acquire the following locks.New storage
The new storage split Keycloak data into independent areas, where each area can be initialized independently. For example, it is not needed to lock the whole database when initializing the authorization services area.
The current interface is not flexible enough to provide a way to simply introduce a new block of code that is synchronized across the whole cluster.
Suggested changes
We suggest adding a new
GlobalLockSPIthat allows us to request a newGlobalLockfor anyStringidentifier that should be replicated across the whole cluster.Here is a PR with the SPI introduction and
DBLockcompatible implementation for that SPI. After the PR is merged we will introduce implementation for HotRod and JPA map storages.Any thoughts or ideas for improvement?
All reactions