diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c47fe8..4eda62f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Adds a new `useStaticKey` param to `updateSessionInfo_Transaction` - This enables smooth switching between `useDynamicAccessTokenSigningKey` settings by allowing refresh calls to change the signing key type of a session +- Fixes performance issue with user pagination ### Migration @@ -24,6 +25,8 @@ Make sure the core is already upgraded to version 8.0.0 before migrating ALTER TABLE totp_user_devices ADD COLUMN created_at BIGINT UNSIGNED default 0; ALTER TABLE totp_user_devices ALTER COLUMN created_at DROP DEFAULT; +DROP INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users; +DROP INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users; ``` ## [6.0.0] - 2024-03-05 diff --git a/src/main/java/io/supertokens/storage/mysql/queries/GeneralQueries.java b/src/main/java/io/supertokens/storage/mysql/queries/GeneralQueries.java index 8e16b12..66461f3 100644 --- a/src/main/java/io/supertokens/storage/mysql/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/storage/mysql/queries/GeneralQueries.java @@ -98,21 +98,15 @@ static String getQueryToCreateUserPaginationIndex1(Start start) { + "(app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC);"; } - static String getQueryToCreateUserPaginationIndex2(Start start) { - return "CREATE INDEX all_auth_recipe_users_pagination_index2 ON " + Config.getConfig(start).getUsersTable() - + "(app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC);"; - } + // We have deliberately removed index2 and index4 because mysql 5.7 does not support index in such a way that one + // column is ASC and another column is DESC in the same index. Instead, we have updated the user pagination query to + // work with sorting in same ordering for both columns. static String getQueryToCreateUserPaginationIndex3(Start start) { return "CREATE INDEX all_auth_recipe_users_pagination_index3 ON " + Config.getConfig(start).getUsersTable() + "(recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC);"; } - static String getQueryToCreateUserPaginationIndex4(Start start) { - return "CREATE INDEX all_auth_recipe_users_pagination_index4 ON " + Config.getConfig(start).getUsersTable() - + "(recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC);"; - } - static String getQueryToCreatePrimaryUserId(Start start) { /* * Used in: @@ -233,9 +227,7 @@ public static void createTablesIfNotExists(Start start) throws SQLException, Sto // index update(start, getQueryToCreateUserPaginationIndex1(start), NO_OP_SETTER); - update(start, getQueryToCreateUserPaginationIndex2(start), NO_OP_SETTER); update(start, getQueryToCreateUserPaginationIndex3(start), NO_OP_SETTER); - update(start, getQueryToCreateUserPaginationIndex4(start), NO_OP_SETTER); update(start, getQueryToCreatePrimaryUserId(start), NO_OP_SETTER); update(start, getQueryToCreateRecipeIdIndex(start), NO_OP_SETTER); } @@ -796,7 +788,7 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant } else { String finalQuery = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM ( " + USER_SEARCH_TAG_CONDITION.toString() + " )" - + " AS finalResultTable ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder + ", primary_or_recipe_user_id DESC "; + + " AS finalResultTable ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder + ", primary_or_recipe_user_id " + timeJoinedOrder; usersFromQuery = execute(start, finalQuery, pst -> { for (int i = 1; i <= queryList.size(); i++) { pst.setString(i, queryList.get(i - 1)); @@ -832,11 +824,17 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant recipeIdCondition = recipeIdCondition + " AND"; } String timeJoinedOrderSymbol = timeJoinedOrder.equals("ASC") ? ">" : "<"; - String QUERY = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM " + Config.getConfig(start).getUsersTable() + " WHERE " + + // This query is slightly different from one in postgres because we want to use same ordering for + // primary_or_recipe_user_time_joined and primary_or_recipe_user_id because mysql 5.7 does not support + // different ordering for different columns using an index + String QUERY = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM " + + Config.getConfig(start).getUsersTable() + " WHERE " + recipeIdCondition + " (primary_or_recipe_user_time_joined " + timeJoinedOrderSymbol - + " ? OR (primary_or_recipe_user_time_joined = ? AND primary_or_recipe_user_id <= ?)) AND app_id = ? AND tenant_id = ?" + + " ? OR (primary_or_recipe_user_time_joined = ? AND primary_or_recipe_user_id " + + (timeJoinedOrderSymbol + "=") + " ?)) AND app_id = ? AND tenant_id = ?" + " ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder - + ", primary_or_recipe_user_id DESC LIMIT ?"; + + ", primary_or_recipe_user_id " + timeJoinedOrder + " LIMIT ?"; usersFromQuery = execute(start, QUERY, pst -> { if (includeRecipeIds != null) { for (int i = 0; i < includeRecipeIds.length; i++) { @@ -860,12 +858,16 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant }); } else { String recipeIdCondition = RECIPE_ID_CONDITION.toString(); - String QUERY = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM " + Config.getConfig(start).getUsersTable() + " WHERE "; + // This query is slightly different from one in postgres because we want to use same ordering for + // primary_or_recipe_user_time_joined and primary_or_recipe_user_id because mysql 5.7 does not support + // different ordering for different columns using an index + String QUERY = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM " + + Config.getConfig(start).getUsersTable() + " WHERE "; if (!recipeIdCondition.equals("")) { QUERY += recipeIdCondition + " AND"; } QUERY += " app_id = ? AND tenant_id = ? ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder - + ", primary_or_recipe_user_id DESC LIMIT ?"; + + ", primary_or_recipe_user_id " + timeJoinedOrder + " LIMIT ?"; usersFromQuery = execute(start, QUERY, pst -> { if (includeRecipeIds != null) { for (int i = 0; i < includeRecipeIds.length; i++) {