Merge pull request #2457 from mposolda/master
KEYCLOAK-2610 Performance improve - add more indexes to database
This commit is contained in:
commit
e506e385f1
5 changed files with 209 additions and 5 deletions
|
@ -24,5 +24,39 @@
|
|||
<column name="EMAIL" type="VARCHAR(255)"/>
|
||||
</createIndex>
|
||||
|
||||
<!-- Indexes for foreign keys are available by default on some RDBMS (for example MySQL) but not on some others (for example PostgreSQL), so explicitly creating them here -->
|
||||
<createIndex indexName="IDX_USER_ROLE_MAPPING" tableName="USER_ROLE_MAPPING">
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_USER_GROUP_MAPPING" tableName="USER_GROUP_MEMBERSHIP">
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_USER_CONSENT" tableName="USER_CONSENT">
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_CONSENT_PROTMAPPER" tableName="USER_CONSENT_PROT_MAPPER">
|
||||
<column name="USER_CONSENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_CONSENT_ROLE" tableName="USER_CONSENT_ROLE">
|
||||
<column name="USER_CONSENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
|
||||
<createIndex indexName="IDX_USER_ATTRIBUTE" tableName="USER_ATTRIBUTE">
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_USER_CREDENTIAL" tableName="CREDENTIAL">
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_USER_REQACTIONS" tableName="USER_REQUIRED_ACTION">
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
|
||||
<createIndex indexName="IDX_FEDIDENTITY_USER" tableName="FEDERATED_IDENTITY">
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_FEDIDENTITY_FEDUSER" tableName="FEDERATED_IDENTITY">
|
||||
<column name="FEDERATED_USER_ID" type="VARCHAR(255)"/>
|
||||
</createIndex>
|
||||
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -84,7 +84,8 @@
|
|||
"password": "${keycloak.connectionsJpa.password:}",
|
||||
"databaseSchema": "${keycloak.connectionsJpa.databaseSchema:update}",
|
||||
"showSql": "${keycloak.connectionsJpa.showSql:false}",
|
||||
"formatSql": "${keycloak.connectionsJpa.formatSql:true}"
|
||||
"formatSql": "${keycloak.connectionsJpa.formatSql:true}",
|
||||
"globalStatsInterval": "${keycloak.connectionsJpa.globalStatsInterval:-1}"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ Optional parameters:
|
|||
* `many.users.batch` - Measurement batch size. Default: *1000*.
|
||||
* `many.users.reimport` - Switch for phases 2 and 3. Default: *false*.
|
||||
* `many.users.minTokenValidity` - Minimum validity of admin-client's access token. Default: *10000*. (ms)
|
||||
* `many.users.read.after.create` - If true, then additional request to read user is send always after the user is created. Default: *false*
|
||||
* `many.users.create.objects` - If true, then some additional objects will be added to each user (2 attributes, password credential, 2 group mappings, 1 required action) Default: *false*
|
||||
* `many.users.create.social.links` - If true, then one social (identityProvider) link will be added to each user and then later read. Default: *false*
|
||||
* `keycloak.connectionsJpa.globalStatsInterval` - Interval in seconds to log Hibernate statistics into log. Default: *-1* (which means statistics are disabled)
|
||||
|
||||
|
||||
### With MySQL
|
||||
|
|
|
@ -4,12 +4,29 @@ import java.io.BufferedOutputStream;
|
|||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.authentication.requiredactions.UpdatePassword;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.Timer;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
@ -25,6 +42,16 @@ public class ManyUsersTest extends AbstractUserTest {
|
|||
|
||||
private static final int COUNT = Integer.parseInt(System.getProperty("many.users.count", "10000"));
|
||||
private static final int BATCH = Integer.parseInt(System.getProperty("many.users.batch", "1000"));
|
||||
|
||||
// When true, then it will always send another request to GET user after he is created (this trigger some DB queries and cache user on Keycloak side)
|
||||
private static final boolean READ_USER_AFTER_CREATE = Boolean.parseBoolean(System.getProperty("many.users.read.after.create", "false"));
|
||||
|
||||
// When true, then each user will be updated with password, 2 additional attributes, 2 default groups and some required action
|
||||
private static final boolean CREATE_OBJECTS = Boolean.parseBoolean(System.getProperty("many.users.create.objects", "false"));
|
||||
|
||||
// When true, then each user will be updated with 2 federated identity links
|
||||
private static final boolean CREATE_SOCIAL_LINKS = Boolean.parseBoolean(System.getProperty("many.users.create.social.links", "false"));
|
||||
|
||||
private static final boolean REIMPORT = Boolean.parseBoolean(System.getProperty("many.users.reimport", "false"));
|
||||
|
||||
private static final String REALM = "realm_with_many_users";
|
||||
|
@ -60,25 +87,90 @@ public class ManyUsersTest extends AbstractUserTest {
|
|||
|
||||
@Before
|
||||
public void before() {
|
||||
log.infof("Reading users after create is %s", READ_USER_AFTER_CREATE ? "ENABLED" : "DISABLED");
|
||||
|
||||
users = new LinkedList<>();
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
users.add(createUserRep("user" + i));
|
||||
}
|
||||
|
||||
realmTimer.reset("create realm before test");
|
||||
RealmRepresentation realm = new RealmRepresentation();
|
||||
realm.setRealm(REALM);
|
||||
realmsResouce().create(realm);
|
||||
createRealm(REALM);
|
||||
|
||||
if (CREATE_OBJECTS) {
|
||||
|
||||
// Assuming default groups and required action already created
|
||||
if (realmResource().getDefaultGroups().size() == 0) {
|
||||
log.infof("Creating default groups 'group1' and 'group2'.");
|
||||
setDefaultGroup("group1");
|
||||
setDefaultGroup("group2");
|
||||
|
||||
RequiredActionProviderRepresentation updatePassword = realmResource().flows().getRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
|
||||
updatePassword.setDefaultAction(true);
|
||||
realmResource().flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), updatePassword);
|
||||
}
|
||||
}
|
||||
|
||||
refreshToken();
|
||||
}
|
||||
|
||||
private void setDefaultGroup(String groupName) {
|
||||
GroupRepresentation group = new GroupRepresentation();
|
||||
group.setName(groupName);
|
||||
Response resp = realmResource().groups().add(group);
|
||||
String groupId = ApiUtil.getCreatedId(resp);
|
||||
resp.close();
|
||||
realmResource().addDefaultGroup(groupId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
realmTimer.clearStats(true, true, false);
|
||||
usersTimer.clearStats();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UserRepresentation createUser(UsersResource users, UserRepresentation user) {
|
||||
// Add some additional attributes to user
|
||||
if (CREATE_OBJECTS) {
|
||||
Map<String, Object> attrs = new HashMap<>();
|
||||
attrs.put("attr1", Collections.singletonList("val1"));
|
||||
attrs.put("attr2", Collections.singletonList("val2"));
|
||||
user.setAttributes(attrs);
|
||||
}
|
||||
|
||||
UserRepresentation userRep = super.createUser(users, user);
|
||||
|
||||
// Add password
|
||||
if (CREATE_OBJECTS) {
|
||||
CredentialRepresentation password = new CredentialRepresentation();
|
||||
password.setType(CredentialRepresentation.PASSWORD);
|
||||
password.setValue("password");
|
||||
password.setTemporary(false);
|
||||
users.get(userRep.getId()).resetPassword(password);
|
||||
}
|
||||
|
||||
// Add social link
|
||||
if (CREATE_SOCIAL_LINKS) {
|
||||
createSocialLink("facebook", users, userRep.getId());
|
||||
}
|
||||
|
||||
return userRep;
|
||||
}
|
||||
|
||||
private void createSocialLink(String provider, UsersResource users, String userId) {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
|
||||
FederatedIdentityRepresentation link = new FederatedIdentityRepresentation();
|
||||
link.setIdentityProvider(provider);
|
||||
link.setUserId(uuid);
|
||||
link.setUserName(uuid);
|
||||
users.get(userId).addFederatedIdentity(provider, link);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void manyUsers() throws IOException {
|
||||
RealmRepresentation realm = realmResource().toRepresentation();
|
||||
|
@ -90,7 +182,19 @@ public class ManyUsersTest extends AbstractUserTest {
|
|||
int i = 0;
|
||||
for (UserRepresentation user : users) {
|
||||
refreshTokenIfMinValidityExpired();
|
||||
createUser(realmResource().users(), user);
|
||||
UserRepresentation createdUser = createUser(realmResource().users(), user);
|
||||
|
||||
// Send additional request to read every user after he is created
|
||||
if (READ_USER_AFTER_CREATE) {
|
||||
UserRepresentation returned = realmResource().users().get(createdUser.getId()).toRepresentation();
|
||||
Assert.assertEquals(returned.getId(), createdUser.getId());
|
||||
}
|
||||
|
||||
// Send additional request to read social links of user
|
||||
if (CREATE_SOCIAL_LINKS) {
|
||||
List<FederatedIdentityRepresentation> fedIdentities = realmResource().users().get(createdUser.getId()).getFederatedIdentity();
|
||||
}
|
||||
|
||||
if (++i % BATCH == 0) {
|
||||
usersTimer.reset();
|
||||
log.info("Created users: " + i + " / " + users.size());
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
#
|
||||
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
log4j.rootLogger=info
|
||||
|
||||
log4j.appender.keycloak=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.keycloak.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.keycloak.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
|
||||
|
||||
log4j.appender.testsuite=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.testsuite.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.testsuite.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %m%n
|
||||
|
||||
log4j.logger.org.keycloak=off, keycloak
|
||||
|
||||
log4j.logger.org.keycloak.testsuite=debug, testsuite
|
||||
log4j.additivity.org.keycloak.testsuite=false
|
||||
|
||||
# Enable to view events
|
||||
# log4j.logger.org.keycloak.events=debug
|
||||
|
||||
# Enable to view loaded SPI and Providers
|
||||
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
|
||||
# log4j.logger.org.keycloak.provider.ProviderManager=debug
|
||||
# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
|
||||
|
||||
# Liquibase updates logged with "info" by default. Logging level can be changed by system property "keycloak.liquibase.logging.level"
|
||||
keycloak.liquibase.logging.level=info
|
||||
log4j.logger.org.keycloak.connections.jpa.updater.liquibase=${keycloak.liquibase.logging.level}
|
||||
log4j.logger.org.keycloak.connections.jpa=debug
|
||||
|
||||
# Enable to view database updates
|
||||
# log4j.logger.org.keycloak.connections.mongo.updater.DefaultMongoUpdaterProvider=debug
|
||||
# log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=debug
|
||||
# log4j.logger.org.keycloak.migration.MigrationModelManager=debug
|
||||
|
||||
# Enable to view kerberos/spnego logging
|
||||
# log4j.logger.org.keycloak.broker.kerberos=trace
|
||||
|
||||
# Enable to view detailed AS REQ and TGS REQ requests to embedded Kerberos server
|
||||
# log4j.logger.org.apache.directory.server.kerberos=debug
|
||||
|
||||
log4j.logger.org.xnio=off
|
||||
log4j.logger.org.hibernate=off
|
||||
log4j.logger.org.jboss.resteasy=warn
|
||||
log4j.logger.org.apache.directory.api=warn
|
||||
log4j.logger.org.apache.directory.server.core=warn
|
Loading…
Reference in a new issue