diff --git a/docs/documentation/upgrading/topics/changes/changes-25_0_2.adoc b/docs/documentation/upgrading/topics/changes/changes-25_0_2.adoc new file mode 100644 index 0000000000..83a1fe03c2 --- /dev/null +++ b/docs/documentation/upgrading/topics/changes/changes-25_0_2.adoc @@ -0,0 +1,5 @@ += Improving performance for deletion of user consents + +When a client scope or the full realm are deleted the associated user consents should also be removed. A new index over the table `USER_CONSENT_CLIENT_SCOPE` has been added to increase the performance. + +Note that, if the table contains more than 300.000 entries, by default {project_name} skips the creation of the indexes during the automatic schema migration and logs the SQL statements to the console instead. The statements must be run manually in the DB after {project_name}'s startup. Check the link:{upgradingguide_link}[{upgradingguide_name}] for details on how to configure a different limit. diff --git a/docs/documentation/upgrading/topics/changes/changes.adoc b/docs/documentation/upgrading/topics/changes/changes.adoc index 943b4a7a2a..71f768ae1c 100644 --- a/docs/documentation/upgrading/topics/changes/changes.adoc +++ b/docs/documentation/upgrading/topics/changes/changes.adoc @@ -5,6 +5,10 @@ include::changes-26_0_0.adoc[leveloffset=3] +=== Migrating to 25.0.2 + +include::changes-25_0_2.adoc[leveloffset=3] + === Migrating to 25.0.0 include::changes-25_0_0.adoc[leveloffset=3] diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java index 48b81eb9ae..06246414b8 100644 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java @@ -182,18 +182,15 @@ public class JpaUtils { */ public static Properties loadSpecificNamedQueries(String databaseType) { URL specificUrl = JpaUtils.class.getClassLoader().getResource("META-INF/queries-" + databaseType + ".properties"); - URL defaultUrl = JpaUtils.class.getClassLoader().getResource("META-INF/queries-default.properties"); - - if (defaultUrl == null) { - throw new IllegalStateException("META-INF/queries-default.properties was not found in the classpath"); - } Properties specificQueries = loadSqlProperties(specificUrl); - Properties defaultQueries = loadSqlProperties(defaultUrl); Properties queries = new Properties(); + if (specificQueries == null) { + return queries; + } - for (String queryNameFull : defaultQueries.stringPropertyNames()) { - String querySql = defaultQueries.getProperty(queryNameFull); + for (String queryNameFull : specificQueries.stringPropertyNames()) { + String querySql = specificQueries.getProperty(queryNameFull); String queryName = getQueryShortName(queryNameFull); String specificQueryNameFull = getQueryFromProperties(queryName, specificQueries); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java index 9bb1ee75f0..265aa0391c 100644 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java @@ -32,19 +32,14 @@ import java.io.Serializable; * @author Marek Posolda */ @NamedQueries({ - // sub-query with deletion performs very slow in MySQL/MariaDB databases - // It is removed from here and added manually in JpaUtils to give a native implementation if needed - // @NamedQuery(name="deleteClientSessionsByRealm", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId = :realmId)"), + @NamedQuery(name="deleteClientSessionsByRealm", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId = :realmId)"), @NamedQuery(name="deleteClientSessionsByClient", query="delete from PersistentClientSessionEntity sess where sess.clientId = :clientId"), @NamedQuery(name="deleteClientSessionsByExternalClient", query="delete from PersistentClientSessionEntity sess where sess.clientStorageProvider = :clientStorageProvider and sess.externalClientId = :externalClientId"), @NamedQuery(name="deleteClientSessionsByClientStorageProvider", query="delete from PersistentClientSessionEntity sess where sess.clientStorageProvider = :clientStorageProvider"), - // sub-query with deletion performs very slow in MySQL/MariaDB databases - // It is removed from here and added manually in JpaUtils to give a native implementation if needed - // @NamedQuery(name="deleteClientSessionsByUser", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.userId = :userId)"), + @NamedQuery(name="deleteClientSessionsByUser", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.userId = :userId)"), @NamedQuery(name="deleteClientSessionsByUserSession", query="delete from PersistentClientSessionEntity sess where sess.userSessionId = :userSessionId and sess.offline = :offline"), - // KEYCLOAK-18842: The deleteExpiredClientSessions performs very slow in MySQL/MariaDB databases - // It is removed from here and added manually in JpaUtils to give a native implementation if needed - //@NamedQuery(name="deleteExpiredClientSessions", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId = :realmId AND u.offline = :offline AND u.lastSessionRefresh < :lastSessionRefresh)"), + @NamedQuery(name="deleteExpiredClientSessions", query="delete from PersistentClientSessionEntity sess where sess.offline = :offline AND sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId = :realmId AND u.offline = :offline AND u.lastSessionRefresh < :lastSessionRefresh)"), + @NamedQuery(name="deleteClientSessionsByRealmSessionType", query="delete from PersistentClientSessionEntity sess where sess.offline = :offline AND sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId = :realmId and u.offline = :offline)"), @NamedQuery(name="findClientSessionsByUserSession", query="select sess from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and sess.offline = :offline"), @NamedQuery(name="findClientSessionsOrderedByIdInterval", query="select sess from PersistentClientSessionEntity sess where sess.offline = :offline and sess.userSessionId >= :fromSessionId and sess.userSessionId <= :toSessionId order by sess.userSessionId"), @NamedQuery(name="findClientSessionsOrderedByIdExact", query="select sess from PersistentClientSessionEntity sess where sess.offline = :offline and sess.userSessionId IN (:userSessionIds)"), diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-18.0.15.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-18.0.15.xml new file mode 100644 index 0000000000..aabf2554af --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-18.0.15.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml index 8f804e0c9b..e4fb81bc45 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml @@ -73,6 +73,7 @@ + diff --git a/model/jpa/src/main/resources/META-INF/queries-default.properties b/model/jpa/src/main/resources/META-INF/queries-default.properties deleted file mode 100644 index 749eb034cb..0000000000 --- a/model/jpa/src/main/resources/META-INF/queries-default.properties +++ /dev/null @@ -1,22 +0,0 @@ -# properties file to define all default queries that are loaded separately -# in a properties file. These queries can be overloaded with a -# specific file for each database type. Queries are defined in the form: -# name[type]=sql -# type can be native (for native queries) or jpql (jpql syntax) -# if no type is defined jpql is the default - -deleteExpiredClientSessions=delete from PersistentClientSessionEntity sess where sess.offline = :offline AND sess.userSessionId IN (\ - select u.userSessionId from PersistentUserSessionEntity u \ - where u.realmId = :realmId AND u.offline = :offline AND u.lastSessionRefresh < :lastSessionRefresh) - -deleteClientSessionsByRealm=delete from PersistentClientSessionEntity sess where sess.userSessionId IN (\ - select u.userSessionId from PersistentUserSessionEntity u \ - where u.realmId = :realmId) - -deleteClientSessionsByRealmSessionType=delete from PersistentClientSessionEntity sess where sess.offline = :offline AND sess.userSessionId IN (\ - select u.userSessionId from PersistentUserSessionEntity u \ - where u.realmId = :realmId and u.offline = :offline) - -deleteClientSessionsByUser=delete from PersistentClientSessionEntity sess where sess.userSessionId IN (\ - select u.userSessionId from PersistentUserSessionEntity u \ - where u.userId = :userId) diff --git a/model/jpa/src/main/resources/META-INF/queries-mariadb.properties b/model/jpa/src/main/resources/META-INF/queries-mariadb.properties index b2422319da..cf8e05c460 100644 --- a/model/jpa/src/main/resources/META-INF/queries-mariadb.properties +++ b/model/jpa/src/main/resources/META-INF/queries-mariadb.properties @@ -16,3 +16,11 @@ deleteClientSessionsByRealmSessionType[native]=delete c from OFFLINE_CLIENT_SESS deleteClientSessionsByUser[native]=delete c from OFFLINE_CLIENT_SESSION c join OFFLINE_USER_SESSION u \ where c.USER_SESSION_ID = u.USER_SESSION_ID and u.USER_ID = :userId + +deleteUserConsentClientScopesByRealm[native]=delete cc from USER_CONSENT_CLIENT_SCOPE cc join USER_CONSENT uc join USER_ENTITY u \ + where cc.USER_CONSENT_ID = uc.ID and uc.USER_ID = u.ID and u.REALM_ID=:realmId + +deleteUserConsentsByRealm[native]=delete uc from USER_CONSENT uc join USER_ENTITY u where uc.USER_ID = u.ID and u.REALM_ID = :realmId + +deleteUserConsentClientScopesByClient[native]=delete cc from USER_CONSENT_CLIENT_SCOPE cc join USER_CONSENT uc \ + where cc.USER_CONSENT_ID = uc.ID and uc.CLIENT_ID = :clientId diff --git a/model/jpa/src/main/resources/META-INF/queries-mysql.properties b/model/jpa/src/main/resources/META-INF/queries-mysql.properties index b2422319da..cf8e05c460 100644 --- a/model/jpa/src/main/resources/META-INF/queries-mysql.properties +++ b/model/jpa/src/main/resources/META-INF/queries-mysql.properties @@ -16,3 +16,11 @@ deleteClientSessionsByRealmSessionType[native]=delete c from OFFLINE_CLIENT_SESS deleteClientSessionsByUser[native]=delete c from OFFLINE_CLIENT_SESSION c join OFFLINE_USER_SESSION u \ where c.USER_SESSION_ID = u.USER_SESSION_ID and u.USER_ID = :userId + +deleteUserConsentClientScopesByRealm[native]=delete cc from USER_CONSENT_CLIENT_SCOPE cc join USER_CONSENT uc join USER_ENTITY u \ + where cc.USER_CONSENT_ID = uc.ID and uc.USER_ID = u.ID and u.REALM_ID=:realmId + +deleteUserConsentsByRealm[native]=delete uc from USER_CONSENT uc join USER_ENTITY u where uc.USER_ID = u.ID and u.REALM_ID = :realmId + +deleteUserConsentClientScopesByClient[native]=delete cc from USER_CONSENT_CLIENT_SCOPE cc join USER_CONSENT uc \ + where cc.USER_CONSENT_ID = uc.ID and uc.CLIENT_ID = :clientId