Skip to content

Commit c9436ff

Browse files
authored
fix: index for MAU (#224)
* fix: index for MAU * fix: feature flags
1 parent b933a54 commit c9436ff

File tree

6 files changed

+112
-3
lines changed

6 files changed

+112
-3
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
99

10+
## [7.1.3] - 2024-09-04
11+
12+
- Adds index on `last_active_time` for `user_last_active` table to improve the performance of MAU computation.
13+
14+
### Migration
15+
16+
```sql
17+
CREATE INDEX IF NOT EXISTS user_last_active_last_active_time_index ON user_last_active (last_active_time DESC, app_id DESC);
18+
```
19+
1020
## [7.1.2] - 2024-09-02
1121

1222
- Optimizes users count query

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ plugins {
22
id 'java-library'
33
}
44

5-
version = "7.1.2"
5+
version = "7.1.3"
66

77
repositories {
88
mavenCentral()

src/main/java/io/supertokens/storage/postgresql/Start.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,6 +1332,15 @@ public void updateLastActive(AppIdentifier appIdentifier, String userId) throws
13321332
}
13331333
}
13341334

1335+
@TestOnly
1336+
public void updateLastActive(AppIdentifier appIdentifier, String userId, long timestamp) throws StorageQueryException {
1337+
try {
1338+
ActiveUsersQueries.updateUserLastActive(this, appIdentifier, userId, timestamp);
1339+
} catch (SQLException e) {
1340+
throw new StorageQueryException(e);
1341+
}
1342+
}
1343+
13351344
@Override
13361345
public int countUsersActiveSince(AppIdentifier appIdentifier, long time) throws StorageQueryException {
13371346
try {

src/main/java/io/supertokens/storage/postgresql/queries/ActiveUsersQueries.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.sql.Connection;
1010
import java.sql.SQLException;
1111

12+
import org.jetbrains.annotations.TestOnly;
13+
1214
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute;
1315
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.update;
1416
import static io.supertokens.storage.postgresql.config.Config.getConfig;
@@ -34,6 +36,11 @@ static String getQueryToCreateAppIdIndexForUserLastActiveTable(Start start) {
3436
+ Config.getConfig(start).getUserLastActiveTable() + "(app_id);";
3537
}
3638

39+
public static String getQueryToCreateLastActiveTimeIndexForUserLastActiveTable(Start start) {
40+
return "CREATE INDEX IF NOT EXISTS user_last_active_last_active_time_index ON "
41+
+ Config.getConfig(start).getUserLastActiveTable() + "(last_active_time DESC, app_id DESC);";
42+
}
43+
3744
public static int countUsersActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime)
3845
throws SQLException, StorageQueryException {
3946
String QUERY = "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getUserLastActiveTable()
@@ -90,6 +97,22 @@ public static int updateUserLastActive(Start start, AppIdentifier appIdentifier,
9097
});
9198
}
9299

100+
@TestOnly
101+
public static int updateUserLastActive(Start start, AppIdentifier appIdentifier, String userId, long timestamp)
102+
throws SQLException, StorageQueryException {
103+
String QUERY = "INSERT INTO " + Config.getConfig(start).getUserLastActiveTable()
104+
+
105+
"(app_id, user_id, last_active_time) VALUES(?, ?, ?) ON CONFLICT(app_id, user_id) DO UPDATE SET " +
106+
"last_active_time = ?";
107+
108+
return update(start, QUERY, pst -> {
109+
pst.setString(1, appIdentifier.getAppId());
110+
pst.setString(2, userId);
111+
pst.setLong(3, timestamp);
112+
pst.setLong(4, timestamp);
113+
});
114+
}
115+
93116
public static Long getLastActiveByUserId(Start start, AppIdentifier appIdentifier, String userId)
94117
throws StorageQueryException {
95118
String QUERY = "SELECT last_active_time FROM " + Config.getConfig(start).getUserLastActiveTable()

src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ public static void createTablesIfNotExists(Start start, Connection con) throws S
303303
// Index
304304
update(con, ActiveUsersQueries.getQueryToCreateAppIdIndexForUserLastActiveTable(start),
305305
NO_OP_SETTER);
306+
update(con, ActiveUsersQueries.getQueryToCreateLastActiveTimeIndexForUserLastActiveTable(start),
307+
NO_OP_SETTER);
306308
}
307309

308310
if (!doesTableExists(start, con, Config.getConfig(start).getAccessTokenSigningKeysTable())) {

src/test/java/io/supertokens/storage/postgresql/test/OneMillionUsersTest.java

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@
2727
import io.supertokens.emailpassword.EmailPassword;
2828
import io.supertokens.emailpassword.ParsedFirebaseSCryptResponse;
2929
import io.supertokens.featureflag.EE_FEATURES;
30+
import io.supertokens.featureflag.FeatureFlag;
3031
import io.supertokens.featureflag.FeatureFlagTestContent;
3132
import io.supertokens.passwordless.Passwordless;
33+
import io.supertokens.pluginInterface.RECIPE_ID;
34+
import io.supertokens.pluginInterface.Storage;
3235
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
3336
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
3437
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
@@ -39,6 +42,7 @@
3942
import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage;
4043
import io.supertokens.session.Session;
4144
import io.supertokens.session.info.SessionInformationHolder;
45+
import io.supertokens.storage.postgresql.Start;
4246
import io.supertokens.storage.postgresql.test.httpRequest.HttpRequestForTesting;
4347
import io.supertokens.storageLayer.StorageLayer;
4448
import io.supertokens.thirdparty.ThirdParty;
@@ -386,6 +390,30 @@ private void createSessions(Main main) throws Exception {
386390
es.awaitTermination(10, TimeUnit.MINUTES);
387391
}
388392

393+
private void createActiveUserEntries(Main main) throws Exception {
394+
System.out.println("Creating active user entries...");
395+
396+
ExecutorService es = Executors.newFixedThreadPool(NUM_THREADS);
397+
398+
for (String userId : allPrimaryUserIds) {
399+
String finalUserId = userId;
400+
es.execute(() -> {
401+
try {
402+
Storage storage = StorageLayer.getBaseStorage(main);
403+
Start start = (Start) storage;
404+
405+
start.updateLastActive(new AppIdentifier(null, null), finalUserId, System.currentTimeMillis() - new Random().nextInt(1000 * 3600 * 24 * 60));
406+
407+
} catch (Exception e) {
408+
throw new RuntimeException(e);
409+
}
410+
});
411+
}
412+
413+
es.shutdown();
414+
es.awaitTermination(10, TimeUnit.MINUTES);
415+
}
416+
389417
@Test
390418
public void testCreatingOneMillionUsers() throws Exception {
391419
if (System.getenv("ONE_MILLION_USERS_TEST") == null) {
@@ -400,7 +428,7 @@ public void testCreatingOneMillionUsers() throws Exception {
400428

401429
FeatureFlagTestContent.getInstance(process.getProcess())
402430
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
403-
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
431+
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA, EE_FEATURES.DASHBOARD_LOGIN});
404432
process.startProcess();
405433
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
406434

@@ -445,6 +473,13 @@ public void testCreatingOneMillionUsers() throws Exception {
445473
System.out.println("Time taken to create sessions: " + ((en - st) / 1000) + " sec");
446474
}
447475

476+
{
477+
long st = System.currentTimeMillis();
478+
createActiveUserEntries(process.getProcess());
479+
long en = System.currentTimeMillis();
480+
System.out.println("Time taken to create active user entries: " + ((en - st) / 1000) + " sec");
481+
}
482+
448483
sanityCheckAPIs(process.getProcess());
449484
allUserIds.clear();
450485
allPrimaryUserIds.clear();
@@ -466,7 +501,7 @@ public void testCreatingOneMillionUsers() throws Exception {
466501

467502
FeatureFlagTestContent.getInstance(process.getProcess())
468503
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
469-
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
504+
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA, EE_FEATURES.DASHBOARD_LOGIN});
470505
process.startProcess();
471506
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
472507

@@ -888,6 +923,36 @@ private void measureOperations(Main main) throws Exception {
888923
return null;
889924
});
890925
System.out.println("Update user metadata " + time);
926+
assert time < 3000;
927+
}
928+
929+
{ // measure user counting
930+
long time = measureTime(() -> {
931+
try {
932+
AuthRecipe.getUsersCount(main, null);
933+
AuthRecipe.getUsersCount(main, new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD});
934+
AuthRecipe.getUsersCount(main, new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY});
935+
} catch (Exception e) {
936+
errorCount.incrementAndGet();
937+
throw new RuntimeException(e);
938+
}
939+
return null;
940+
});
941+
System.out.println("User counting: " + time);
942+
assert time < 3000;
943+
}
944+
{ // measure telemetry
945+
long time = measureTime(() -> {
946+
try {
947+
FeatureFlag.getInstance(main).getPaidFeatureStats();
948+
} catch (Exception e) {
949+
errorCount.incrementAndGet();
950+
throw new RuntimeException(e);
951+
}
952+
return null;
953+
});
954+
System.out.println("Telemetry: " + time);
955+
assert time < 3000;
891956
}
892957

893958
assertEquals(0, errorCount.get());

0 commit comments

Comments
 (0)