keycloak-uncached

Details

diff --git a/testsuite/performance/README.datasets.md b/testsuite/performance/README.datasets.md
new file mode 100644
index 0000000..2338be1
--- /dev/null
+++ b/testsuite/performance/README.datasets.md
@@ -0,0 +1,46 @@
+# Keycloak Performance Testsuite - Generating datasets
+
+
+## Generating a set of datasets for multiple realms
+
+The first dataset is small and is created quickly. Building of each subsequent dataset continues on top
+of the previous dataset.
+
+Datasets are created with a specific released server version (rather than a snapshot) in order to be
+usable with later releases - newer server version should be able to migrate schema from any previous release.
+
+We use 10 concurrent threads, which is enough to saturate a 
+dual core machine. For quad-core you can try to double the number of workers.
+
+```
+cd testsuite/performance
+
+mvn clean install -Dserver.version=4.0.0.Beta1
+
+mvn verify -Pteardown
+mvn verify -Pprovision
+mvn verify -Pgenerate-data -Ddataset=10r100u1c -DnumOfWorkers=10
+mvn verify -Pexport-dump -Ddataset=10r100u1c
+
+mvn verify -Pgenerate-data -Ddataset=20r100u1c -DstartAtRealmIdx=10 -DnumOfWorkers=10
+mvn verify -Pexport-dump -Ddataset=20r100u1c
+
+mvn verify -Pgenerate-data -Ddataset=50r100u1c -DstartAtRealmIdx=20 -DnumOfWorkers=10
+mvn verify -Pexport-dump -Ddataset=50r100u1c
+
+mvn verify -Pgenerate-data -Ddataset=200r100u1c -DstartAtRealmIdx=50 -DnumOfWorkers=10
+mvn verify -Pexport-dump -Ddataset=200r100u1c
+
+mvn verify -Pgenerate-data -Ddataset=500r100u1c -DstartAtRealmIdx=200 -DnumOfWorkers=10
+mvn verify -Pexport-dump -Ddataset=500r100u1c
+```
+
+If the dataset dump file is not available locally but it's known that the dataset for specific version exists on the server
+it can be retrieved by specifying a proper server version again. For example:
+```
+mvn verify -Pteardown
+mvn clean install
+mvn verify -Pprovision
+mvn verify -Pimport-dump -Ddataset=20r100u1c -Dserver.version=4.0.0.Beta1
+
+```
diff --git a/testsuite/performance/tests/docker-compose.sh b/testsuite/performance/tests/docker-compose.sh
index af8cd5e..d9b11b1 100755
--- a/testsuite/performance/tests/docker-compose.sh
+++ b/testsuite/performance/tests/docker-compose.sh
@@ -399,7 +399,7 @@ case "$OPERATION" in
                 fi
                 echo "Importing $DATASET.sql.gz"
                 set -o pipefail
-                if ! zcat $DATASET.sql.gz | docker exec -i $DB_CONTAINER /usr/bin/mysql -u root --password=root keycloak ; then
+                if ! gunzip -c $DATASET.sql.gz | docker exec -i $DB_CONTAINER /usr/bin/mysql -u root --password=root keycloak ; then
                     echo Import failed.
                     exit 1
                 fi
diff --git a/testsuite/performance/tests/parameters/datasets/10r100u1c.properties b/testsuite/performance/tests/parameters/datasets/10r100u1c.properties
new file mode 100644
index 0000000..68a3ae2
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/10r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=10
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/parameters/datasets/200r100u1c.properties b/testsuite/performance/tests/parameters/datasets/200r100u1c.properties
new file mode 100644
index 0000000..4b8cf98
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/200r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=200
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/parameters/datasets/20r100u1c.properties b/testsuite/performance/tests/parameters/datasets/20r100u1c.properties
new file mode 100644
index 0000000..4296e87
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/20r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=20
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/parameters/datasets/500r100u1c.properties b/testsuite/performance/tests/parameters/datasets/500r100u1c.properties
new file mode 100644
index 0000000..2f4c164
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/500r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=500
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/parameters/datasets/50r100u1c.properties b/testsuite/performance/tests/parameters/datasets/50r100u1c.properties
new file mode 100644
index 0000000..b5c3475
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/50r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=50
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/pom.xml b/testsuite/performance/tests/pom.xml
index 2fc997b..5d9a0e1 100644
--- a/testsuite/performance/tests/pom.xml
+++ b/testsuite/performance/tests/pom.xml
@@ -185,6 +185,7 @@
                             <goal>read-project-properties</goal>
                         </goals>
                         <configuration>
+                            <quiet>true</quiet>
                             <files>
                                 <file>${provisioning.properties.file}</file>
                                 <file>${dataset.properties.file}</file>
@@ -456,6 +457,12 @@
         
         <profile>
             <id>generate-data</id>
+            <properties>
+                <startAtRealmIdx>0</startAtRealmIdx>
+                <ignoreConflicts>false</ignoreConflicts>
+                <skipRealmRoles>false</skipRealmRoles>
+                <skipClientRoles>false</skipClientRoles>
+            </properties>
             <build>
                 <plugins>
                     <plugin>
@@ -504,6 +511,10 @@
                                         <argument>-DauthUser=${keycloak.admin.user}</argument>
                                         <argument>-DauthPassword=${keycloak.admin.password}</argument>
                                         <argument>-DnumOfWorkers=${numOfWorkers}</argument>
+                                        <argument>-DstartAtRealmIdx=${startAtRealmIdx}</argument>
+                                        <argument>-DignoreConflicts=${ignoreConflicts}</argument>
+                                        <argument>-DskipRealmRoles=${skipRealmRoles}</argument>
+                                        <argument>-DskipClientRoles=${skipClientRoles}</argument>
                                         <argument>org.keycloak.performance.RealmsConfigurationLoader</argument>
                                         <argument>benchmark-realms.json</argument>
                                     </arguments>
diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationLoader.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationLoader.java
index 52d1fde..2597899 100644
--- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationLoader.java
+++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationLoader.java
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.core.JsonToken;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.jboss.logging.Logger;
 import org.keycloak.admin.client.Keycloak;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -12,6 +13,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 
+import javax.ws.rs.ClientErrorException;
 import javax.ws.rs.core.Response;
 import java.io.File;
 import java.io.IOException;
@@ -28,24 +30,37 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.LinkedBlockingQueue;
 import static org.keycloak.performance.RealmsConfigurationBuilder.EXPORT_FILENAME;
 
+import static org.keycloak.performance.TestConfig.ignoreConflicts;
 import static org.keycloak.performance.TestConfig.numOfWorkers;
+import static org.keycloak.performance.TestConfig.skipClientRoles;
+import static org.keycloak.performance.TestConfig.skipRealmRoles;
+import static org.keycloak.performance.TestConfig.startAtRealmIdx;
+import static org.keycloak.performance.TestConfig.startAtUserIdx;
 
 /**
  * # build
- * mvn -f testsuite/integration-arquillian/tests/performance/gatling-perf clean install
+ * mvn -f testsuite/performance/tests clean install
  *
  * # generate benchmark-realms.json file with generated test data
- * mvn -f testsuite/integration-arquillian/tests/performance/gatling-perf exec:java -Dexec.mainClass=org.keycloak.performance.RealmsConfigurationBuilder -DnumOfRealms=2 -DusersPerRealm=2 -DclientsPerRealm=2 -DrealmRoles=2 -DrealmRolesPerUser=2 -DclientRolesPerUser=2 -DclientRolesPerClient=2
+ * mvn -f testsuite/performance/tests exec:java -Dexec.mainClass=org.keycloak.performance.RealmsConfigurationBuilder -DnumOfRealms=2 -DusersPerRealm=2 -DclientsPerRealm=2 -DrealmRoles=2 -DrealmRolesPerUser=2 -DclientRolesPerUser=2 -DclientRolesPerClient=2
  *
  * # use benchmark-realms.json to load the data up to Keycloak Server listening on localhost:8080
- * mvn -f testsuite/integration-arquillian/tests/performance/gatling-perf exec:java -Dexec.mainClass=org.keycloak.performance.RealmsConfigurationLoader -DnumOfWorkers=5 -Dexec.args=benchmark-realms.json > perf-output.txt
+ * mvn -f testsuite/performance/tests exec:java -Dexec.mainClass=org.keycloak.performance.RealmsConfigurationLoader -DnumOfWorkers=5 -Dexec.args=benchmark-realms.json > perf-output.txt
  *
  * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
  */
 public class RealmsConfigurationLoader {
 
+    static Logger log = Logger.getLogger(RealmsConfigurationLoader.class.getName());
+
     static final int ERROR_CHECK_INTERVAL = 10;
 
+    static int currentRealm = 0;
+    static int currentUser = 0;
+    static int currentClient = 0;
+
+    static boolean started;
+
     // multi-thread mechanics
     static final BlockingQueue<AdminJob> queue = new LinkedBlockingQueue<>(numOfWorkers);
     static final ArrayList<Worker> workers = new ArrayList<>();
@@ -58,24 +73,28 @@ public class RealmsConfigurationLoader {
     static boolean realmCreated;
 
     public static void main(String [] args) throws IOException {
-        System.out.println("Keycloak servers: "+TestConfig.serverUrisList);
+        println("Keycloak servers: "+TestConfig.serverUrisList);
 
         if (args.length == 0) {
             args = new String[] {EXPORT_FILENAME};
         }
 
         if (args.length != 1) {
-            System.out.println("Usage: java " + RealmsConfigurationLoader.class.getName() + " <FILE>");
+            println("Usage: java " + RealmsConfigurationLoader.class.getName() + " <FILE>");
             return;
         }
 
         String file = args[0];
-        System.out.println("Using file: " + new File(args[0]).getAbsolutePath());
-        System.out.println("Number of workers (numOfWorkers): " + numOfWorkers);
+        println("Using file: " + new File(args[0]).getAbsolutePath());
+        println("Number of workers (numOfWorkers): " + numOfWorkers);
+        println("Parameters: ");
+        println("    startAtRealmIdx: " + startAtRealmIdx);
+//        println("    startAtUserIdx: " + startAtUserIdx);
 
         JsonParser p = initParser(file);
 
         initWorkers();
+        initProgress();
 
         try {
 
@@ -88,6 +107,28 @@ public class RealmsConfigurationLoader {
         }
     }
 
+    private static void initProgress() {
+        Thread t = new Thread(() -> {
+
+            for (;;) {
+                try {
+                    Thread.sleep(60000);
+                    println("At realm: " + currentRealm + ", Clients: " + currentClient + ", Users: " + currentUser);
+                } catch (InterruptedException e) {
+                    return;
+                }
+
+            }
+
+        },"Progress Logger");
+        t.setDaemon(true);
+        t.start();
+    }
+
+    private static void println(String s) {
+        System.out.println(s);
+    }
+
     private static void completeWorkers() {
 
         try {
@@ -101,7 +142,7 @@ public class RealmsConfigurationLoader {
                 try {
                     w.join(5000);
                     if (w.isAlive()) {
-                        System.out.println("Worker thread failed to stop: ");
+                        println("Worker thread failed to stop: ");
                         dumpThread(w);
                     }
                 } catch (InterruptedException e) {
@@ -117,6 +158,7 @@ public class RealmsConfigurationLoader {
         while (t != JsonToken.END_OBJECT && t != JsonToken.END_ARRAY) {
             if (t != JsonToken.START_ARRAY) {
                 readRealm(p);
+                currentRealm += 1;
             }
             t = p.nextToken();
         }
@@ -150,7 +192,7 @@ public class RealmsConfigurationLoader {
         for (StackTraceElement e: w.getStackTrace()) {
             b.append(e.toString()).append("\n");
         }
-        System.out.print(b);
+        println(b.toString());
     }
 
     private static void readRealm(JsonParser p) throws IOException {
@@ -158,57 +200,112 @@ public class RealmsConfigurationLoader {
         // as soon as we encounter users, roles, clients we create a CreateRealmJob
         // TODO: if after that point in a realm we encounter realm attribute, we report a warning but continue
 
-        RealmRepresentation r = new RealmRepresentation();
-        JsonToken t = p.nextToken();
-        while (t != JsonToken.END_OBJECT) {
+        boolean skip = false;
+        try {
+            RealmRepresentation r = new RealmRepresentation();
+            JsonToken t = p.nextToken();
+            outer:
+            while (t != JsonToken.END_OBJECT && !skip) {
+
+                //System.out.println(t + ", name: " + p.getCurrentName() + ", text: '" + p.getText() + "', value: " + p.getValueAsString());
+
+                switch (p.getCurrentName()) {
+                    case "realm":
+                        r.setRealm(getStringValue(p));
+                        skip = !started && realmSkipped(r.getRealm()) ;
+                        if (skip) {
+                            break outer;
+                        }
+                        break;
+                    case "enabled":
+                        r.setEnabled(getBooleanValue(p));
+                        break;
+                    case "accessTokenLifespan":
+                        r.setAccessCodeLifespan(getIntegerValue(p));
+                        break;
+                    case "registrationAllowed":
+                        r.setRegistrationAllowed(getBooleanValue(p));
+                        break;
+                    case "passwordPolicy":
+                        r.setPasswordPolicy(getStringValue(p));
+                        break;
+                    case "sslRequired":
+                        r.setSslRequired(getStringValue(p));
+                        break;
+                    case "users":
+                        ensureRealm(r);
+                        if (seekToStart()) {
+                            enqueueFetchRealmRoles(r);
+                            completePending();
+                        }
+                        readUsers(r, p);
+                        break;
+                    case "roles":
+                        ensureRealm(r);
+                        readRoles(r, p);
+                        break;
+                    case "clients":
+                        ensureRealm(r);
+                        readClients(r, p);
+                        completePending();
+                        if (seekToStart()) {
+                            enqueueFetchMissingClients(r);
+                            completePending();
+                        }
+                        break;
+                    default: {
+                        // if we don't understand the field we ignore it - but report that
+                        log.warn("Realm attribute ignored: " + p.getCurrentName());
+                        consumeAttribute(p);
+                        continue; // skip p.nextToken() at end of loop - consumeAttribute() already did it
+                    }
+                }
 
-            //System.out.println(t + ", name: " + p.getCurrentName() + ", text: '" + p.getText() + "', value: " + p.getValueAsString());
+                t = p.nextToken();
+            }
 
-            switch (p.getCurrentName()) {
-                case "realm":
-                    r.setRealm(getStringValue(p));
-                    break;
-                case "enabled":
-                    r.setEnabled(getBooleanValue(p));
-                    break;
-                case "accessTokenLifespan":
-                    r.setAccessCodeLifespan(getIntegerValue(p));
-                    break;
-                case "registrationAllowed":
-                    r.setRegistrationAllowed(getBooleanValue(p));
-                    break;
-                case "passwordPolicy":
-                    r.setPasswordPolicy(getStringValue(p));
-                    break;
-                case "users":
-                    ensureRealm(r);
-                    readUsers(r, p);
-                    break;
-                case "roles":
-                    ensureRealm(r);
-                    readRoles(r, p);
-                    break;
-                case "clients":
-                    ensureRealm(r);
-                    readClients(r, p);
-                    break;
-                default: {
-                    // if we don't understand the field we ignore it - but report that
-                    System.out.println("Realm attribute ignored: " + p.getCurrentName());
-                    consumeAttribute(p);
-                }
+            if (skip) {
+                log.info("Realm skipped: " + r.getRealm());
+                consumeParent(p);
             }
-            t = p.nextToken();
+
+        } finally {
+            // we wait for realm to complete
+            completePending();
+
+            // reset realm specific cache
+            realmCreated = false;
+            clientIdMap.clear();
+            realmRoleIdMap.clear();
+            clientRoleIdMap.clear();
         }
+    }
 
-        // we wait for realm to complete
-        completePending();
+    private static void consumeParent(JsonParser p) throws IOException {
+        while (p.currentToken() != JsonToken.END_OBJECT) {
+            consumeAttribute(p);
+        }
+    }
+
+    private static boolean seekToStart() {
+        return startAtRealmIdx > 0 || startAtUserIdx > 0;
+    }
 
-        // reset realm specific cache
-        realmCreated = false;
-        clientIdMap.clear();
-        realmRoleIdMap.clear();
-        clientRoleIdMap.clear();
+    private static boolean seeking() {
+        return currentRealm < startAtRealmIdx || currentUser < startAtUserIdx;
+    }
+
+    private static boolean realmSkipped(String realm) {
+        int pos = realm.lastIndexOf("_");
+        int idx = Integer.parseInt(realm.substring(pos+1));
+        return idx < startAtRealmIdx;
+    }
+
+    private static boolean userSkipped(String username) {
+        int pos = username.indexOf("_");
+        int end = username.indexOf("_", pos+1);
+        int idx = Integer.parseInt(username.substring(pos+1, end));
+        return idx < startAtUserIdx;
     }
 
     private static void ensureRealm(RealmRepresentation r) {
@@ -220,34 +317,18 @@ public class RealmsConfigurationLoader {
 
     private static void createRealm(RealmRepresentation r) {
         try {
+            started = true;
             queue.put(new CreateRealmJob(r));
         } catch (InterruptedException e) {
             throw new RuntimeException("Interrupted", e);
         }
 
-        // now wait for job to appear
-        PendingResult next = pendingResult.poll();
-        while (next == null) {
-            waitForAwhile();
-            next = pendingResult.poll();
-        }
-
-        // then wait for the job to complete
-        while (!next.isDone()) {
-            waitForAwhile();
-        }
-
-        try {
-            next.get();
-        } catch (InterruptedException e) {
-            throw new RuntimeException("Interrupted", e);
-        } catch (ExecutionException e) {
-            throw new RuntimeException("Execution failed", e.getCause());
-        }
+        completePending();
     }
 
     private static void enqueueCreateUser(RealmRepresentation r, UserRepresentation u) {
         try {
+            started = true;
             queue.put(new CreateUserJob(r, u));
         } catch (InterruptedException e) {
             throw new RuntimeException("Interrupted", e);
@@ -256,6 +337,7 @@ public class RealmsConfigurationLoader {
 
     private static void enqueueCreateRealmRole(RealmRepresentation r, RoleRepresentation role) {
         try {
+            started = true;
             queue.put(new CreateRealmRoleJob(r, role));
         } catch (InterruptedException e) {
             throw new RuntimeException("Interrupted", e);
@@ -264,6 +346,7 @@ public class RealmsConfigurationLoader {
 
     private static void enqueueCreateClientRole(RealmRepresentation r, RoleRepresentation role, String client) {
         try {
+            started = true;
             queue.put(new CreateClientRoleJob(r, role, client));
         } catch (InterruptedException e) {
             throw new RuntimeException("Interrupted", e);
@@ -272,12 +355,31 @@ public class RealmsConfigurationLoader {
 
     private static void enqueueCreateClient(RealmRepresentation r, ClientRepresentation client) {
         try {
+            started = true;
             queue.put(new CreateClientJob(r, client));
         } catch (InterruptedException e) {
             throw new RuntimeException("Interrupted", e);
         }
     }
 
+    private static void enqueueFetchMissingClients(RealmRepresentation r) {
+        try {
+            started = true;
+            queue.put(new FetchMissingClientsJob(r));
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted", e);
+        }
+    }
+
+    private static void enqueueFetchRealmRoles(RealmRepresentation r) {
+        try {
+            started = true;
+            queue.put(new FetchRealmRolesJob(r));
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted", e);
+        }
+    }
+
     private static void waitForAwhile() {
         waitForAwhile(100, "Interrupted");
     }
@@ -299,18 +401,22 @@ public class RealmsConfigurationLoader {
         if (t != JsonToken.START_ARRAY) {
             throw new RuntimeException("Error reading field 'users'. Expected array of users [" + t + "]");
         }
-        int count = 0;
+
         t = p.nextToken();
         while (t == JsonToken.START_OBJECT) {
             UserRepresentation u = p.readValueAs(UserRepresentation.class);
-            enqueueCreateUser(r, u);
+            if (!started && userSkipped(u.getUsername())) {
+                log.info("User skipped: " + u.getUsername());
+            } else {
+                enqueueCreateUser(r, u);
+            }
             t = p.nextToken();
-            count += 1;
+            currentUser += 1;
 
             // every some users check to see pending errors
             // in order to short-circuit if any errors have occurred
-            if (count % ERROR_CHECK_INTERVAL == 0) {
-                checkPendingErrors();
+            if (currentUser % ERROR_CHECK_INTERVAL == 0) {
+                checkPendingErrors(u.getUsername());
             }
         }
     }
@@ -367,14 +473,16 @@ public class RealmsConfigurationLoader {
             t = p.nextToken();
             while (t != JsonToken.END_ARRAY) {
                 RoleRepresentation u = p.readValueAs(RoleRepresentation.class);
-                enqueueCreateClientRole(r, u, client);
+                if (!seeking() || !skipClientRoles) {
+                    enqueueCreateClientRole(r, u, client);
+                }
                 t = p.nextToken();
                 count += 1;
 
                 // every some roles check to see pending errors
                 // in order to short-circuit if any errors have occurred
                 if (count % ERROR_CHECK_INTERVAL == 0) {
-                    checkPendingErrors();
+                    checkPendingErrors(u.getName());
                 }
             }
             t = p.nextToken();
@@ -393,14 +501,16 @@ public class RealmsConfigurationLoader {
         int count = 0;
         while (t == JsonToken.START_OBJECT) {
             RoleRepresentation u = p.readValueAs(RoleRepresentation.class);
-            enqueueCreateRealmRole(r, u);
+            if (!seeking() || !skipRealmRoles) {
+                enqueueCreateRealmRole(r, u);
+            }
             t = p.nextToken();
             count += 1;
 
             // every some roles check to see pending errors
             // in order to short-circuit if any errors have occurred
             if (count % ERROR_CHECK_INTERVAL == 0) {
-                checkPendingErrors();
+                checkPendingErrors(u.getName());
             }
         }
     }
@@ -410,25 +520,25 @@ public class RealmsConfigurationLoader {
         if (t != JsonToken.START_ARRAY) {
             throw new RuntimeException("Error reading field 'clients'. Expected array of clients [" + t + "]");
         }
-        int count = 0;
+
         t = p.nextToken();
         while (t == JsonToken.START_OBJECT) {
             ClientRepresentation u = p.readValueAs(ClientRepresentation.class);
             enqueueCreateClient(r, u);
             t = p.nextToken();
-            count += 1;
+            currentClient += 1;
 
             // every some users check to see pending errors
-            if (count % ERROR_CHECK_INTERVAL == 0) {
-                checkPendingErrors();
+            if (currentClient % ERROR_CHECK_INTERVAL == 0) {
+                checkPendingErrors(u.getClientId());
             }
         }
     }
 
-    private static void checkPendingErrors() {
+    private static void checkPendingErrors(String label) {
         // now wait for job to appear
         PendingResult next = pendingResult.peek();
-        while (next == null) {
+        while (next == null && queue.size() > 0) {
             waitForAwhile();
             next = pendingResult.peek();
         }
@@ -445,7 +555,7 @@ public class RealmsConfigurationLoader {
                 } catch (InterruptedException e) {
                     throw new RuntimeException("Interrupted");
                 } catch (ExecutionException e) {
-                    throw new RuntimeException("Execution failed", e.getCause());
+                    throw new RuntimeException("Execution failed in the vicinity of " + label + ": ", e.getCause());
                 }
             }
         }
@@ -479,9 +589,15 @@ public class RealmsConfigurationLoader {
     }
 
     private static void consumeAttribute(JsonParser p) throws IOException {
-        JsonToken t = p.nextToken();
+        JsonToken t = p.currentToken();
         if (t == JsonToken.START_OBJECT || t == JsonToken.START_ARRAY) {
             p.skipChildren();
+            p.nextToken();
+        } else if (t == JsonToken.FIELD_NAME) {
+            p.nextToken();
+            consumeAttribute(p);
+        } else {
+            p.nextToken();
         }
     }
 
@@ -528,6 +644,40 @@ public class RealmsConfigurationLoader {
         }
     }
 
+    static class FetchMissingClientsJob extends AdminJob {
+
+        private RealmRepresentation realm;
+
+        FetchMissingClientsJob(RealmRepresentation r) {
+            realm = r;
+        }
+
+        @Override
+        public void run() {
+            List<ClientRepresentation> clients = admin().realms().realm(realm.getRealm()).clients().findAll();
+            for (ClientRepresentation c: clients) {
+                clientIdMap.put(c.getClientId(), c.getId());
+            }
+        }
+    }
+
+    static class FetchRealmRolesJob extends AdminJob {
+
+        private RealmRepresentation realm;
+
+        FetchRealmRolesJob(RealmRepresentation r) {
+            realm = r;
+        }
+
+        @Override
+        public void run() {
+            List<RoleRepresentation> roles = admin().realms().realm(realm.getRealm()).roles().list();
+            for (RoleRepresentation r: roles) {
+                realmRoleIdMap.put(r.getName(), r.getId());
+            }
+        }
+    }
+
     static class CreateRealmJob extends AdminJob {
 
         private RealmRepresentation realm;
@@ -538,7 +688,15 @@ public class RealmsConfigurationLoader {
 
         @Override
         public void run() {
-            admin().realms().create(realm);
+            try {
+                admin().realms().create(realm);
+            } catch (ClientErrorException e) {
+                if (e.getMessage().endsWith("409 Conflict") && ignoreConflicts) {
+                    log.warn("Ignoring conflict when creating a realm: " + realm.getRealm());
+                    return;
+                }
+                throw e;
+            }
         }
     }
 
@@ -556,11 +714,17 @@ public class RealmsConfigurationLoader {
         public void run() {
             Response response = admin().realms().realm(realm.getRealm()).users().create(user);
             response.close();
-            if (response.getStatus() != 201) {
-                throw new RuntimeException("Failed to create user with status: " + response.getStatusInfo().getReasonPhrase());
+
+            if (response.getStatus() == 409 && ignoreConflicts) {
+                log.warn("Ignoring conflict when creating a user: " + user.getUsername());
+                user.setId(admin().realms().realm(realm.getRealm()).users().search(user.getUsername()).get(0).getId());
+            } else if (response.getStatus() == 201) {
+                user.setId(extractIdFromResponse(response));
+            } else {
+                throw new RuntimeException("Failed to create user with status: " + response.getStatusInfo());
             }
 
-            String userId = extractIdFromResponse(response);
+            String userId = user.getId();
 
             List<CredentialRepresentation> creds = user.getCredentials();
             for (CredentialRepresentation cred: creds) {
@@ -641,8 +805,16 @@ public class RealmsConfigurationLoader {
 
         @Override
         public void run() {
-            admin().realms().realm(realm.getRealm()).roles().create(role);
-
+            try {
+                admin().realms().realm(realm.getRealm()).roles().create(role);
+            } catch (ClientErrorException e) {
+                if (e.getMessage().endsWith("409 Conflict") && ignoreConflicts) {
+                    log.warn("Ignoring conflict when creating a realm role: " + role.getName());
+                    role = admin().realms().realm(realm.getRealm()).roles().get(role.getName()).toRepresentation();
+                } else {
+                    throw e;
+                }
+            }
             // we need the id but it's not returned by REST API - we have to perform a get on the created role and save the returned id
             RoleRepresentation rr = admin().realms().realm(realm.getRealm()).roles().get(role.getName()).toRepresentation();
             realmRoleIdMap.put(rr.getName(), rr.getId());
@@ -668,7 +840,18 @@ public class RealmsConfigurationLoader {
             if (id == null) {
                 throw new RuntimeException("No client created for clientId: " + clientId);
             }
-            admin().realms().realm(realm.getRealm()).clients().get(id).roles().create(role);
+
+            try {
+                admin().realms().realm(realm.getRealm()).clients().get(id).roles().create(role);
+
+            } catch (ClientErrorException e) {
+                if (e.getMessage().endsWith("409 Conflict") && ignoreConflicts) {
+                    log.warn("Ignoring conflict when creating a client role: " + role.getName());
+                    role = admin().realms().realm(realm.getRealm()).clients().get(id).roles().get(role.getName()).toRepresentation();
+                } else {
+                    throw e;
+                }
+            }
 
             // we need the id but it's not returned by REST API - we have to perform a get on the created role and save the returned id
             RoleRepresentation rr = admin().realms().realm(realm.getRealm()).clients().get(id).roles().get(role.getName()).toRepresentation();
@@ -680,6 +863,7 @@ public class RealmsConfigurationLoader {
 
             roleIdMap.put(rr.getName(), rr.getId());
         }
+
     }
 
     static class CreateClientJob extends AdminJob {
@@ -697,11 +881,16 @@ public class RealmsConfigurationLoader {
         public void run() {
             Response response = admin().realms().realm(realm.getRealm()).clients().create(client);
             response.close();
-            if (response.getStatus() != 201) {
+
+            if (response.getStatus() == 409 && ignoreConflicts) {
+                log.warn("Ignoring conflict when creating a client: " + client.getClientId());
+                client = admin().realms().realm(realm.getRealm()).clients().findByClientId(client.getClientId()).get(0);
+            } else if (response.getStatus() == 201) {
+                client.setId(extractIdFromResponse(response));
+            } else {
                 throw new RuntimeException("Failed to create client with status: " + response.getStatusInfo().getReasonPhrase());
             }
-            String id = extractIdFromResponse(response);
-            clientIdMap.put(client.getClientId(), id);
+            clientIdMap.put(client.getClientId(), client.getId());
         }
     }
 
diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java
index 5b6a11a..94824d7 100644
--- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java
+++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java
@@ -34,6 +34,11 @@ public class TestConfig {
     // Settings used by RealmsConfigurationLoader only - when loading data into Keycloak
     //
     public static final int numOfWorkers = Integer.getInteger("numOfWorkers", 1);
+    public static final int startAtRealmIdx = Integer.getInteger("startAtRealmIdx", 0);
+    public static final int startAtUserIdx = 0; // doesn't work properly, will be removed later //Integer.getInteger("startAtUserIdx", 0);
+    public static final boolean ignoreConflicts = "true".equals(System.getProperty("ignoreConflicts", "false"));
+    public static final boolean skipRealmRoles = "true".equals(System.getProperty("skipRealmRoles", "false"));
+    public static final boolean skipClientRoles = "true".equals(System.getProperty("skipClientRoles", "false"));
 
     //
     // Settings used by RealmConfigurationLoader to connect to Admin REST API
diff --git a/testsuite/performance/tests/src/main/resources/logback.xml b/testsuite/performance/tests/src/main/resources/logback.xml
new file mode 100644
index 0000000..153511f
--- /dev/null
+++ b/testsuite/performance/tests/src/main/resources/logback.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
+        <resetJUL>true</resetJUL>
+    </contextListener>
+
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx</pattern>
+        </encoder>
+        <immediateFlush>false</immediateFlush>
+    </appender>
+
+    <!-- Uncomment for logging ALL HTTP request and responses -->
+    <!-- 	<logger name="io.gatling.http" level="TRACE" /> -->
+    <!-- Uncomment for logging ONLY FAILED HTTP request and responses -->
+    <!-- 	<logger name="io.gatling.http" level="DEBUG" /> -->
+
+    <root level="WARN">
+        <appender-ref ref="CONSOLE" />
+    </root>
+
+</configuration>
\ No newline at end of file