Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: index for MAU #224

Merged
merged 2 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [7.1.3] - 2024-09-04

- Adds index on `last_active_time` for `user_last_active` table to improve the performance of MAU computation.

### Migration

```sql
CREATE INDEX IF NOT EXISTS user_last_active_last_active_time_index ON user_last_active (last_active_time DESC, app_id DESC);
```

## [7.1.2] - 2024-09-02

- Optimizes users count query
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'java-library'
}

version = "7.1.2"
version = "7.1.3"

repositories {
mavenCentral()
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/io/supertokens/storage/postgresql/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,15 @@ public void updateLastActive(AppIdentifier appIdentifier, String userId) throws
}
}

@TestOnly
public void updateLastActive(AppIdentifier appIdentifier, String userId, long timestamp) throws StorageQueryException {
try {
ActiveUsersQueries.updateUserLastActive(this, appIdentifier, userId, timestamp);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countUsersActiveSince(AppIdentifier appIdentifier, long time) throws StorageQueryException {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import java.sql.Connection;
import java.sql.SQLException;

import org.jetbrains.annotations.TestOnly;

import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute;
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.update;
import static io.supertokens.storage.postgresql.config.Config.getConfig;
Expand All @@ -34,6 +36,11 @@ static String getQueryToCreateAppIdIndexForUserLastActiveTable(Start start) {
+ Config.getConfig(start).getUserLastActiveTable() + "(app_id);";
}

public static String getQueryToCreateLastActiveTimeIndexForUserLastActiveTable(Start start) {
return "CREATE INDEX IF NOT EXISTS user_last_active_last_active_time_index ON "
+ Config.getConfig(start).getUserLastActiveTable() + "(last_active_time DESC, app_id DESC);";
}

public static int countUsersActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime)
throws SQLException, StorageQueryException {
String QUERY = "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getUserLastActiveTable()
Expand Down Expand Up @@ -90,6 +97,22 @@ public static int updateUserLastActive(Start start, AppIdentifier appIdentifier,
});
}

@TestOnly
public static int updateUserLastActive(Start start, AppIdentifier appIdentifier, String userId, long timestamp)
throws SQLException, StorageQueryException {
String QUERY = "INSERT INTO " + Config.getConfig(start).getUserLastActiveTable()
+
"(app_id, user_id, last_active_time) VALUES(?, ?, ?) ON CONFLICT(app_id, user_id) DO UPDATE SET " +
"last_active_time = ?";

return update(start, QUERY, pst -> {
pst.setString(1, appIdentifier.getAppId());
pst.setString(2, userId);
pst.setLong(3, timestamp);
pst.setLong(4, timestamp);
});
}

public static Long getLastActiveByUserId(Start start, AppIdentifier appIdentifier, String userId)
throws StorageQueryException {
String QUERY = "SELECT last_active_time FROM " + Config.getConfig(start).getUserLastActiveTable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ public static void createTablesIfNotExists(Start start, Connection con) throws S
// Index
update(con, ActiveUsersQueries.getQueryToCreateAppIdIndexForUserLastActiveTable(start),
NO_OP_SETTER);
update(con, ActiveUsersQueries.getQueryToCreateLastActiveTimeIndexForUserLastActiveTable(start),
NO_OP_SETTER);
}

if (!doesTableExists(start, con, Config.getConfig(start).getAccessTokenSigningKeysTable())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
import io.supertokens.emailpassword.EmailPassword;
import io.supertokens.emailpassword.ParsedFirebaseSCryptResponse;
import io.supertokens.featureflag.EE_FEATURES;
import io.supertokens.featureflag.FeatureFlag;
import io.supertokens.featureflag.FeatureFlagTestContent;
import io.supertokens.passwordless.Passwordless;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
Expand All @@ -39,6 +42,7 @@
import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage;
import io.supertokens.session.Session;
import io.supertokens.session.info.SessionInformationHolder;
import io.supertokens.storage.postgresql.Start;
import io.supertokens.storage.postgresql.test.httpRequest.HttpRequestForTesting;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.thirdparty.ThirdParty;
Expand Down Expand Up @@ -386,6 +390,30 @@ private void createSessions(Main main) throws Exception {
es.awaitTermination(10, TimeUnit.MINUTES);
}

private void createActiveUserEntries(Main main) throws Exception {
System.out.println("Creating active user entries...");

ExecutorService es = Executors.newFixedThreadPool(NUM_THREADS);

for (String userId : allPrimaryUserIds) {
String finalUserId = userId;
es.execute(() -> {
try {
Storage storage = StorageLayer.getBaseStorage(main);
Start start = (Start) storage;

start.updateLastActive(new AppIdentifier(null, null), finalUserId, System.currentTimeMillis() - new Random().nextInt(1000 * 3600 * 24 * 60));

} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

es.shutdown();
es.awaitTermination(10, TimeUnit.MINUTES);
}

@Test
public void testCreatingOneMillionUsers() throws Exception {
if (System.getenv("ONE_MILLION_USERS_TEST") == null) {
Expand All @@ -400,7 +428,7 @@ public void testCreatingOneMillionUsers() throws Exception {

FeatureFlagTestContent.getInstance(process.getProcess())
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA, EE_FEATURES.DASHBOARD_LOGIN});
process.startProcess();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));

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

{
long st = System.currentTimeMillis();
createActiveUserEntries(process.getProcess());
long en = System.currentTimeMillis();
System.out.println("Time taken to create active user entries: " + ((en - st) / 1000) + " sec");
}

sanityCheckAPIs(process.getProcess());
allUserIds.clear();
allPrimaryUserIds.clear();
Expand All @@ -466,7 +501,7 @@ public void testCreatingOneMillionUsers() throws Exception {

FeatureFlagTestContent.getInstance(process.getProcess())
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA, EE_FEATURES.DASHBOARD_LOGIN});
process.startProcess();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));

Expand Down Expand Up @@ -888,6 +923,36 @@ private void measureOperations(Main main) throws Exception {
return null;
});
System.out.println("Update user metadata " + time);
assert time < 3000;
}

{ // measure user counting
long time = measureTime(() -> {
try {
AuthRecipe.getUsersCount(main, null);
AuthRecipe.getUsersCount(main, new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD});
AuthRecipe.getUsersCount(main, new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY});
} catch (Exception e) {
errorCount.incrementAndGet();
throw new RuntimeException(e);
}
return null;
});
System.out.println("User counting: " + time);
assert time < 3000;
}
{ // measure telemetry
long time = measureTime(() -> {
try {
FeatureFlag.getInstance(main).getPaidFeatureStats();
} catch (Exception e) {
errorCount.incrementAndGet();
throw new RuntimeException(e);
}
return null;
});
System.out.println("Telemetry: " + time);
assert time < 3000;
}

assertEquals(0, errorCount.get());
Expand Down
Loading