mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Disable peristent user session batching
Closes #41662 Signed-off-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com> Signed-off-by: Alexander Schwartz <aschwart@redhat.com> Co-authored-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com> Co-authored-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
af96183788
commit
935caa97ea
@ -64,6 +64,11 @@ When using the `+/realms/{realm-name}/broker/{provider_alias}/token+` endpoint f
|
||||
|
||||
When using GitHub as an IDP, you can now enable JSON responses to leverage the token refresh for this endpoint.
|
||||
|
||||
=== Persistent User Session Batching Disabled
|
||||
|
||||
The batching of persistent user session updates has been turned off by default because it negatively impacts performance with some database vendors, which offset the benefits with other database vendors.
|
||||
It can be enabled using the CLI option `--spi-user-sessions--infinispan--use-batches=true`, but users are encouraged to load test their environment to verify performance improvements.
|
||||
|
||||
== Required field in User Session note mapper
|
||||
|
||||
The name of the session note is now shown as a required field in the Admin UI.
|
||||
|
||||
@ -35,7 +35,6 @@ import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
@ -59,10 +58,13 @@ import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.provider.ProviderEvent;
|
||||
import org.keycloak.provider.ProviderEventListener;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||
|
||||
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory<UserSessionProvider>, ServerInfoAwareProviderFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
|
||||
@ -77,20 +79,18 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
public static final String CONFIG_USE_CACHES = "useCaches";
|
||||
private static final boolean DEFAULT_USE_CACHES = true;
|
||||
public static final String CONFIG_USE_BATCHES = "useBatches";
|
||||
private static final boolean DEFAULT_USE_BATCHES = true;
|
||||
private static final boolean DEFAULT_USE_BATCHES = false;
|
||||
|
||||
private long offlineSessionCacheEntryLifespanOverride;
|
||||
|
||||
private long offlineClientSessionCacheEntryLifespanOverride;
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
private PersisterLastSessionRefreshStore persisterLastSessionRefreshStore;
|
||||
private InfinispanKeyGenerator keyGenerator;
|
||||
SerializeExecutionsByKey<String> serializerSession = new SerializeExecutionsByKey<>();
|
||||
SerializeExecutionsByKey<String> serializerOfflineSession = new SerializeExecutionsByKey<>();
|
||||
SerializeExecutionsByKey<UUID> serializerClientSession = new SerializeExecutionsByKey<>();
|
||||
SerializeExecutionsByKey<UUID> serializerOfflineClientSession = new SerializeExecutionsByKey<>();
|
||||
final SerializeExecutionsByKey<String> serializerSession = new SerializeExecutionsByKey<>();
|
||||
final SerializeExecutionsByKey<String> serializerOfflineSession = new SerializeExecutionsByKey<>();
|
||||
final SerializeExecutionsByKey<UUID> serializerClientSession = new SerializeExecutionsByKey<>();
|
||||
final SerializeExecutionsByKey<UUID> serializerOfflineClientSession = new SerializeExecutionsByKey<>();
|
||||
ArrayBlockingQueue<PersistentUpdate> asyncQueuePersistentUpdate;
|
||||
private PersistentSessionsWorker persistentSessionsWorker;
|
||||
private int maxBatchSize;
|
||||
@ -106,10 +106,10 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
|
||||
if (useCaches) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
cache = connections.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
|
||||
offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME);
|
||||
clientSessionCache = connections.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME);
|
||||
offlineClientSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME);
|
||||
cache = connections.getCache(USER_SESSION_CACHE_NAME);
|
||||
offlineSessionsCache = connections.getCache(OFFLINE_USER_SESSION_CACHE_NAME);
|
||||
clientSessionCache = connections.getCache(CLIENT_SESSION_CACHE_NAME);
|
||||
offlineClientSessionsCache = connections.getCache(OFFLINE_CLIENT_SESSION_CACHE_NAME);
|
||||
}
|
||||
|
||||
if (MultiSiteUtils.isPersistentSessionsEnabled()) {
|
||||
@ -146,7 +146,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
offlineSessionCacheEntryLifespanOverride = config.getInt(CONFIG_OFFLINE_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, -1);
|
||||
if (offlineSessionCacheEntryLifespanOverride != -1) {
|
||||
// to be removed in KC 27
|
||||
@ -168,11 +167,8 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
|
||||
@Override
|
||||
public void postInit(final KeycloakSessionFactory factory) {
|
||||
factory.register(new ProviderEventListener() {
|
||||
|
||||
@Override
|
||||
public void onEvent(ProviderEvent event) {
|
||||
if (event instanceof PostMigrationEvent) {
|
||||
factory.register(event -> {
|
||||
if (event instanceof PostMigrationEvent) {
|
||||
if (!useCaches) {
|
||||
keyGenerator = new InfinispanKeyGenerator() {
|
||||
@Override
|
||||
@ -191,22 +187,20 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
});
|
||||
}
|
||||
|
||||
} else if (event instanceof UserModel.UserRemovedEvent) {
|
||||
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
|
||||
} else if (event instanceof UserModel.UserRemovedEvent userRemovedEvent) {
|
||||
|
||||
UserSessionProvider provider1 = userRemovedEvent.getKeycloakSession().getProvider(UserSessionProvider.class, getId());
|
||||
if (provider1 instanceof InfinispanUserSessionProvider) {
|
||||
((InfinispanUserSessionProvider) provider1).onUserRemoved(userRemovedEvent.getRealm(), userRemovedEvent.getUser());
|
||||
} else if (provider1 instanceof PersistentUserSessionProvider) {
|
||||
((PersistentUserSessionProvider) provider1).onUserRemoved(userRemovedEvent.getRealm(), userRemovedEvent.getUser());
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown provider type: " + provider1.getClass());
|
||||
}
|
||||
UserSessionProvider provider1 = userRemovedEvent.getKeycloakSession().getProvider(UserSessionProvider.class, getId());
|
||||
if (provider1 instanceof InfinispanUserSessionProvider) {
|
||||
((InfinispanUserSessionProvider) provider1).onUserRemoved(userRemovedEvent.getRealm(), userRemovedEvent.getUser());
|
||||
} else if (provider1 instanceof PersistentUserSessionProvider) {
|
||||
((PersistentUserSessionProvider) provider1).onUserRemoved(userRemovedEvent.getRealm(), userRemovedEvent.getUser());
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown provider type: " + provider1.getClass());
|
||||
}
|
||||
|
||||
} else if (event instanceof ResetTimeOffsetEvent) {
|
||||
if (persisterLastSessionRefreshStore != null) {
|
||||
persisterLastSessionRefreshStore.reset();
|
||||
}
|
||||
} else if (event instanceof ResetTimeOffsetEvent) {
|
||||
if (persisterLastSessionRefreshStore != null) {
|
||||
persisterLastSessionRefreshStore.reset();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -219,13 +213,9 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
}
|
||||
|
||||
public void initializePersisterLastSessionRefreshStore(final KeycloakSessionFactory sessionFactory) {
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
// Initialize persister for periodically doing bulk DB updates of lastSessionRefresh timestamps of refreshed sessions
|
||||
persisterLastSessionRefreshStore = new PersisterLastSessionRefreshStoreFactory().createAndInit(session, true);
|
||||
}
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> {
|
||||
// Initialize persister for periodically doing bulk DB updates of lastSessionRefresh timestamps of refreshed sessions
|
||||
persisterLastSessionRefreshStore = new PersisterLastSessionRefreshStoreFactory().createAndInit(session, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -966,6 +966,8 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||
userSessionsPerformer.applyChangesSynchronously(s);
|
||||
clientSessionPerformer.applyChangesSynchronously(s);
|
||||
});
|
||||
userSessionsPerformer.clear();
|
||||
clientSessionPerformer.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -113,17 +113,20 @@ public class JpaChangesPerformer<K, V extends SessionEntity> implements SessionC
|
||||
exceptions.forEach(ex::addSuppressed);
|
||||
throw ex;
|
||||
}
|
||||
changes.clear();
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void applyChangesSynchronously(KeycloakSession session) {
|
||||
if (!changes.isEmpty()) {
|
||||
changes.forEach(persistentUpdate -> persistentUpdate.perform(session));
|
||||
changes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
changes.clear();
|
||||
}
|
||||
|
||||
private void processClientSessionUpdate(KeycloakSession innerSession, Map.Entry<K, SessionUpdatesList<V>> entry, MergedUpdate<V> merged) {
|
||||
SessionUpdatesList<V> sessionUpdates = entry.getValue();
|
||||
SessionEntityWrapper<V> sessionWrapper = sessionUpdates.getEntityWrapper();
|
||||
@ -348,7 +351,7 @@ public class JpaChangesPerformer<K, V extends SessionEntity> implements SessionC
|
||||
@Override
|
||||
public void setNotes(Map<String, String> notes) {
|
||||
clientSessionModel.getNotes().keySet().forEach(clientSessionModel::removeNote);
|
||||
notes.forEach((k, v) -> clientSessionModel.setNote(k, v));
|
||||
notes.forEach(clientSessionModel::setNote);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -647,7 +650,7 @@ public class JpaChangesPerformer<K, V extends SessionEntity> implements SessionC
|
||||
@Override
|
||||
public void setNotes(Map<String, String> notes) {
|
||||
userSessionModel.getNotes().keySet().forEach(userSessionModel::removeNote);
|
||||
notes.forEach((k, v) -> userSessionModel.setNote(k, v));
|
||||
notes.forEach(userSessionModel::setNote);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -20,8 +20,10 @@ package org.keycloak.models.sessions.infinispan.changes;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.commons.util.concurrent.CompletionStages;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.models.AbstractKeycloakTransaction;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.infinispan.SessionFunction;
|
||||
@ -145,8 +147,18 @@ abstract public class PersistentSessionsChangelogBasedTransaction<K, V extends S
|
||||
changesPerformers.add(new JpaChangesPerformer<>(cacheName, null) {
|
||||
@Override
|
||||
public void applyChanges() {
|
||||
KeycloakModelUtils.runJobInTransaction(kcSession.getKeycloakSessionFactory(),
|
||||
super::applyChangesSynchronously);
|
||||
Retry.executeWithBackoff(
|
||||
iteration -> KeycloakModelUtils.runJobInTransaction(kcSession.getKeycloakSessionFactory(), super::applyChangesSynchronously),
|
||||
(iteration, t) -> {
|
||||
if (t instanceof ModelDuplicateException ex) {
|
||||
// duplicate exceptions are unlikely to succeed on a retry,
|
||||
throw ex;
|
||||
} else if (iteration > 20) {
|
||||
// never retry more than 20 times
|
||||
throw new RuntimeException("Maximum number of retries reached", t);
|
||||
}
|
||||
}, PersistentSessionsWorker.UPDATE_TIMEOUT, PersistentSessionsWorker.UPDATE_BASE_INTERVAL_MILLIS);
|
||||
clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -43,6 +43,8 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public class PersistentSessionsWorker {
|
||||
private static final Logger LOG = Logger.getLogger(PersistentSessionsWorker.class);
|
||||
public static final Duration UPDATE_TIMEOUT = Duration.of(10, ChronoUnit.SECONDS);
|
||||
public static final int UPDATE_BASE_INTERVAL_MILLIS = 0;
|
||||
|
||||
private final KeycloakSessionFactory factory;
|
||||
private final ArrayBlockingQueue<PersistentUpdate> asyncQueuePersistentUpdate;
|
||||
@ -150,7 +152,7 @@ public class PersistentSessionsWorker {
|
||||
}
|
||||
}
|
||||
},
|
||||
Duration.of(10, ChronoUnit.SECONDS), 0);
|
||||
UPDATE_TIMEOUT, UPDATE_BASE_INTERVAL_MILLIS);
|
||||
} catch (RuntimeException ex) {
|
||||
tracing.error(ex);
|
||||
batch.forEach(o -> o.fail(ex));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user