Merge pull request #2457 from mposolda/master

KEYCLOAK-2610 Performance improve - add more indexes to database
This commit is contained in:
Marek Posolda 2016-03-31 13:36:07 +02:00
commit e506e385f1
5 changed files with 209 additions and 5 deletions

View file

@ -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>

View file

@ -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}"
}
},

View file

@ -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

View file

@ -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());

View file

@ -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