From 428319dd0a310804edea9898f73dae223d63e5a1 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:04:06 +0100 Subject: [PATCH 1/9] fix --- 8.0.0.md | 13 +++++++++++++ src/Controllers/DatabaseController.js | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/8.0.0.md b/8.0.0.md index 3d7dd9d6e2..efb0264b0d 100644 --- a/8.0.0.md +++ b/8.0.0.md @@ -5,6 +5,7 @@ This document only highlights specific changes that require a longer explanation --- - [Email Verification](#email-verification) +- [Database Indexes](#database-indexes) --- @@ -25,3 +26,15 @@ The request to re-send a verification email changed to sending a `POST` request Related pull requests: - https://github.com/parse-community/parse-server/pull/8488 + +## Database Indexes + +As part of the email verification and password reset improvements in Parse Server 8, the queries used for these operations have changed to use tokens instead of username/email fields. To ensure optimal query performance, Parse Server now automatically creates indexes on the following fields during server initialization: + +- `_User._email_verify_token`: used for email verification queries +- `_User._perishable_token`: used for password reset queries + +These indexes are created automatically when Parse Server starts, similar to how indexes for `username` and `email` fields are created. No manual intervention is required. + +> [!WARNING] +> If you have a large existing user base, the index creation may take some time during the first server startup after upgrading to Parse Server 8. The server logs will indicate when index creation is complete or if any errors occur. If you have any concerns regarding a potential database performance impact during index creation, you could create these indexes manually in a controlled procedure before upgrading Parse Server. diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 15207a7295..e3b5cc210a 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1764,6 +1764,20 @@ class DatabaseController { throw error; }); + await this.adapter + .ensureIndex('_User', requiredUserFields, ['_email_verify_token'], '_email_verify_token', false) + .catch(error => { + logger.warn('Unable to create index for email verification token: ', error); + throw error; + }); + + await this.adapter + .ensureIndex('_User', requiredUserFields, ['_perishable_token'], '_perishable_token', false) + .catch(error => { + logger.warn('Unable to create index for password reset token: ', error); + throw error; + }); + await this.adapter.ensureUniqueness('_Role', requiredRoleFields, ['name']).catch(error => { logger.warn('Unable to ensure uniqueness for role name: ', error); throw error; From 09988c6c10093dd43f24d4e9d37d338e4720ee45 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:16:32 +0100 Subject: [PATCH 2/9] tests --- spec/DatabaseController.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/DatabaseController.spec.js b/spec/DatabaseController.spec.js index d8ce516131..f0d229f033 100644 --- a/spec/DatabaseController.spec.js +++ b/spec/DatabaseController.spec.js @@ -413,6 +413,8 @@ describe('DatabaseController', function () { case_insensitive_username: { username: 1 }, case_insensitive_email: { email: 1 }, email_1: { email: 1 }, + _email_verify_token: { _email_verify_token: 1 }, + _perishable_token: { _perishable_token: 1 }, }); } ); @@ -437,6 +439,8 @@ describe('DatabaseController', function () { _id_: { _id: 1 }, username_1: { username: 1 }, email_1: { email: 1 }, + _email_verify_token: { _email_verify_token: 1 }, + _perishable_token: { _perishable_token: 1 }, }); } ); From f5cddbf9b01e04560e0284b51e93439492fce7b0 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 1 Nov 2025 00:03:24 +0100 Subject: [PATCH 3/9] compound index --- 8.0.0.md | 10 +++++----- spec/DatabaseController.spec.js | 16 ++++++++++++---- src/Controllers/DatabaseController.js | 16 ++++++++++++++-- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/8.0.0.md b/8.0.0.md index efb0264b0d..81caee0da0 100644 --- a/8.0.0.md +++ b/8.0.0.md @@ -29,12 +29,12 @@ Related pull requests: ## Database Indexes -As part of the email verification and password reset improvements in Parse Server 8, the queries used for these operations have changed to use tokens instead of username/email fields. To ensure optimal query performance, Parse Server now automatically creates indexes on the following fields during server initialization: +As part of the email verification and password reset improvements in Parse Server 8, the queries used for these operations have changed to use tokens instead of username/email fields. To ensure optimal query performance, Parse Server now automatically creates compound indexes on the following fields during server initialization: -- `_User._email_verify_token`: used for email verification queries -- `_User._perishable_token`: used for password reset queries +- `(_email_verify_token, emailVerified, _email_verify_token_expires_at)` - Used for email verification queries +- `(_perishable_token, _perishable_token_expires_at)` - Used for password reset queries -These indexes are created automatically when Parse Server starts, similar to how indexes for `username` and `email` fields are created. No manual intervention is required. +These sparse, compound indexes are created automatically when Parse Server starts. No manual intervention is required. > [!WARNING] -> If you have a large existing user base, the index creation may take some time during the first server startup after upgrading to Parse Server 8. The server logs will indicate when index creation is complete or if any errors occur. If you have any concerns regarding a potential database performance impact during index creation, you could create these indexes manually in a controlled procedure before upgrading Parse Server. +> If you have a large existing user base, the index creation may take some time during the first server startup after upgrading to Parse Server 8. If you have any concerns regarding a potential database performance impact during index creation, you may create these indexes manually in a controlled manner before upgrading Parse Server. diff --git a/spec/DatabaseController.spec.js b/spec/DatabaseController.spec.js index f0d229f033..6e7d6181c5 100644 --- a/spec/DatabaseController.spec.js +++ b/spec/DatabaseController.spec.js @@ -413,8 +413,12 @@ describe('DatabaseController', function () { case_insensitive_username: { username: 1 }, case_insensitive_email: { email: 1 }, email_1: { email: 1 }, - _email_verify_token: { _email_verify_token: 1 }, - _perishable_token: { _perishable_token: 1 }, + _email_verify_token: { + _email_verify_token: 1, + emailVerified: 1, + _email_verify_token_expires_at: 1, + }, + _perishable_token: { _perishable_token: 1, _perishable_token_expires_at: 1 }, }); } ); @@ -439,8 +443,12 @@ describe('DatabaseController', function () { _id_: { _id: 1 }, username_1: { username: 1 }, email_1: { email: 1 }, - _email_verify_token: { _email_verify_token: 1 }, - _perishable_token: { _perishable_token: 1 }, + _email_verify_token: { + _email_verify_token: 1, + emailVerified: 1, + _email_verify_token_expires_at: 1, + }, + _perishable_token: { _perishable_token: 1, _perishable_token_expires_at: 1 }, }); } ); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index e3b5cc210a..2cdb11c866 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1765,14 +1765,26 @@ class DatabaseController { }); await this.adapter - .ensureIndex('_User', requiredUserFields, ['_email_verify_token'], '_email_verify_token', false) + .ensureIndex( + '_User', + requiredUserFields, + ['_email_verify_token', 'emailVerified', '_email_verify_token_expires_at'], + '_email_verify_token', + false + ) .catch(error => { logger.warn('Unable to create index for email verification token: ', error); throw error; }); await this.adapter - .ensureIndex('_User', requiredUserFields, ['_perishable_token'], '_perishable_token', false) + .ensureIndex( + '_User', + requiredUserFields, + ['_perishable_token', '_perishable_token_expires_at'], + '_perishable_token', + false + ) .catch(error => { logger.warn('Unable to create index for password reset token: ', error); throw error; From d9865fc82b69b94f4503185465e52440cf85e838 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 1 Nov 2025 00:25:50 +0100 Subject: [PATCH 4/9] Revert "compound index" This reverts commit f5cddbf9b01e04560e0284b51e93439492fce7b0. --- 8.0.0.md | 10 +++++----- spec/DatabaseController.spec.js | 16 ++++------------ src/Controllers/DatabaseController.js | 16 ++-------------- 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/8.0.0.md b/8.0.0.md index 81caee0da0..efb0264b0d 100644 --- a/8.0.0.md +++ b/8.0.0.md @@ -29,12 +29,12 @@ Related pull requests: ## Database Indexes -As part of the email verification and password reset improvements in Parse Server 8, the queries used for these operations have changed to use tokens instead of username/email fields. To ensure optimal query performance, Parse Server now automatically creates compound indexes on the following fields during server initialization: +As part of the email verification and password reset improvements in Parse Server 8, the queries used for these operations have changed to use tokens instead of username/email fields. To ensure optimal query performance, Parse Server now automatically creates indexes on the following fields during server initialization: -- `(_email_verify_token, emailVerified, _email_verify_token_expires_at)` - Used for email verification queries -- `(_perishable_token, _perishable_token_expires_at)` - Used for password reset queries +- `_User._email_verify_token`: used for email verification queries +- `_User._perishable_token`: used for password reset queries -These sparse, compound indexes are created automatically when Parse Server starts. No manual intervention is required. +These indexes are created automatically when Parse Server starts, similar to how indexes for `username` and `email` fields are created. No manual intervention is required. > [!WARNING] -> If you have a large existing user base, the index creation may take some time during the first server startup after upgrading to Parse Server 8. If you have any concerns regarding a potential database performance impact during index creation, you may create these indexes manually in a controlled manner before upgrading Parse Server. +> If you have a large existing user base, the index creation may take some time during the first server startup after upgrading to Parse Server 8. The server logs will indicate when index creation is complete or if any errors occur. If you have any concerns regarding a potential database performance impact during index creation, you could create these indexes manually in a controlled procedure before upgrading Parse Server. diff --git a/spec/DatabaseController.spec.js b/spec/DatabaseController.spec.js index 6e7d6181c5..f0d229f033 100644 --- a/spec/DatabaseController.spec.js +++ b/spec/DatabaseController.spec.js @@ -413,12 +413,8 @@ describe('DatabaseController', function () { case_insensitive_username: { username: 1 }, case_insensitive_email: { email: 1 }, email_1: { email: 1 }, - _email_verify_token: { - _email_verify_token: 1, - emailVerified: 1, - _email_verify_token_expires_at: 1, - }, - _perishable_token: { _perishable_token: 1, _perishable_token_expires_at: 1 }, + _email_verify_token: { _email_verify_token: 1 }, + _perishable_token: { _perishable_token: 1 }, }); } ); @@ -443,12 +439,8 @@ describe('DatabaseController', function () { _id_: { _id: 1 }, username_1: { username: 1 }, email_1: { email: 1 }, - _email_verify_token: { - _email_verify_token: 1, - emailVerified: 1, - _email_verify_token_expires_at: 1, - }, - _perishable_token: { _perishable_token: 1, _perishable_token_expires_at: 1 }, + _email_verify_token: { _email_verify_token: 1 }, + _perishable_token: { _perishable_token: 1 }, }); } ); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 2cdb11c866..e3b5cc210a 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1765,26 +1765,14 @@ class DatabaseController { }); await this.adapter - .ensureIndex( - '_User', - requiredUserFields, - ['_email_verify_token', 'emailVerified', '_email_verify_token_expires_at'], - '_email_verify_token', - false - ) + .ensureIndex('_User', requiredUserFields, ['_email_verify_token'], '_email_verify_token', false) .catch(error => { logger.warn('Unable to create index for email verification token: ', error); throw error; }); await this.adapter - .ensureIndex( - '_User', - requiredUserFields, - ['_perishable_token', '_perishable_token_expires_at'], - '_perishable_token', - false - ) + .ensureIndex('_User', requiredUserFields, ['_perishable_token'], '_perishable_token', false) .catch(error => { logger.warn('Unable to create index for password reset token: ', error); throw error; From 1fd069c245d878c093be3e3c084e1d27003710f1 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 1 Nov 2025 00:26:03 +0100 Subject: [PATCH 5/9] add fit_only_db --- spec/eslint.config.js | 1 + spec/helper.js | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/spec/eslint.config.js b/spec/eslint.config.js index 6d280d08e3..4d23d3b649 100644 --- a/spec/eslint.config.js +++ b/spec/eslint.config.js @@ -25,6 +25,7 @@ module.exports = [ it_id: "readonly", fit_id: "readonly", it_only_db: "readonly", + fit_only_db: "readonly", it_only_mongodb_version: "readonly", it_only_postgres_version: "readonly", it_only_node_version: "readonly", diff --git a/spec/helper.js b/spec/helper.js index d2f26e584c..43b5ceeb81 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -515,6 +515,17 @@ global.it_only_db = db => { } }; +global.fit_only_db = db => { + if ( + process.env.PARSE_SERVER_TEST_DB === db || + (!process.env.PARSE_SERVER_TEST_DB && db == 'mongo') + ) { + return fit; + } else { + return xit; + } +}; + global.it_only_mongodb_version = version => { if (!semver.validRange(version)) { throw new Error('Invalid version range'); From af5f9512066ddf7bfba3b8ae506bff95f450f33f Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:09:48 +0100 Subject: [PATCH 6/9] non sparse --- src/Adapters/Storage/Mongo/MongoStorageAdapter.js | 2 ++ src/Controllers/DatabaseController.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 481d5257d9..ad5a69ea70 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -687,6 +687,7 @@ export class MongoStorageAdapter implements StorageAdapter { const defaultOptions: Object = { background: true, sparse: true }; const indexNameOptions: Object = indexName ? { name: indexName } : {}; const ttlOptions: Object = options.ttl !== undefined ? { expireAfterSeconds: options.ttl } : {}; + const sparseOptions: Object = options.sparse !== undefined ? { sparse: options.sparse } : {}; const caseInsensitiveOptions: Object = caseInsensitive ? { collation: MongoCollection.caseInsensitiveCollation() } : {}; @@ -695,6 +696,7 @@ export class MongoStorageAdapter implements StorageAdapter { ...caseInsensitiveOptions, ...indexNameOptions, ...ttlOptions, + ...sparseOptions, }; return this._adaptiveCollection(className) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index e3b5cc210a..a8ea65a97b 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1765,14 +1765,14 @@ class DatabaseController { }); await this.adapter - .ensureIndex('_User', requiredUserFields, ['_email_verify_token'], '_email_verify_token', false) + .ensureIndex('_User', requiredUserFields, ['_email_verify_token'], '_email_verify_token', false, { sparse: false }) .catch(error => { logger.warn('Unable to create index for email verification token: ', error); throw error; }); await this.adapter - .ensureIndex('_User', requiredUserFields, ['_perishable_token'], '_perishable_token', false) + .ensureIndex('_User', requiredUserFields, ['_perishable_token'], '_perishable_token', false, { sparse: false }) .catch(error => { logger.warn('Unable to create index for password reset token: ', error); throw error; From 3b25f665a114724ee96e826fbab8741195ec189c Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:21:20 +0100 Subject: [PATCH 7/9] add e2e tests --- spec/DatabaseController.spec.js | 142 ++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/spec/DatabaseController.spec.js b/spec/DatabaseController.spec.js index f0d229f033..b1ccc0d586 100644 --- a/spec/DatabaseController.spec.js +++ b/spec/DatabaseController.spec.js @@ -444,6 +444,148 @@ describe('DatabaseController', function () { }); } ); + + it_only_db('mongo')( + 'should use _email_verify_token index in email verification', + async () => { + const TestUtils = require('../lib/TestUtils'); + let emailVerificationLink; + const emailSentPromise = TestUtils.resolvingPromise(); + const emailAdapter = { + sendVerificationEmail: options => { + emailVerificationLink = options.link; + emailSentPromise.resolve(); + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + await reconfigureServer({ + databaseURI: 'mongodb://localhost:27017/testEmailVerifyTokenIndexStats', + databaseAdapter: undefined, + appName: 'test', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }); + + // Create a user to trigger email verification + const user = new Parse.User(); + user.setUsername('statsuser'); + user.setPassword('password'); + user.set('email', 'stats@example.com'); + await user.signUp(); + await emailSentPromise; + + // Get index stats before the query + const config = Config.get(Parse.applicationId); + const collection = await config.database.adapter._adaptiveCollection('_User'); + const statsBefore = await collection._mongoCollection.aggregate([ + { $indexStats: {} }, + ]).toArray(); + const emailVerifyIndexBefore = statsBefore.find( + stat => stat.name === '_email_verify_token' + ); + const accessesBefore = emailVerifyIndexBefore?.accesses?.ops || 0; + + // Perform email verification (this should use the index) + const request = require('../lib/request'); + await request({ + url: emailVerificationLink, + followRedirects: false, + }); + + // Get index stats after the query + const statsAfter = await collection._mongoCollection.aggregate([ + { $indexStats: {} }, + ]).toArray(); + const emailVerifyIndexAfter = statsAfter.find( + stat => stat.name === '_email_verify_token' + ); + const accessesAfter = emailVerifyIndexAfter?.accesses?.ops || 0; + + // Verify the index was actually used + expect(accessesAfter).toBeGreaterThan(accessesBefore); + expect(emailVerifyIndexAfter).toBeDefined(); + + // Verify email verification succeeded + await user.fetch(); + expect(user.get('emailVerified')).toBe(true); + } + ); + + it_only_db('mongo')( + 'should use _perishable_token index in password reset', + async () => { + const TestUtils = require('../lib/TestUtils'); + let passwordResetLink; + const emailSentPromise = TestUtils.resolvingPromise(); + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: options => { + passwordResetLink = options.link; + emailSentPromise.resolve(); + }, + sendMail: () => {}, + }; + await reconfigureServer({ + databaseURI: 'mongodb://localhost:27017/testPerishableTokenIndexStats', + databaseAdapter: undefined, + appName: 'test', + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }); + + // Create a user + const user = new Parse.User(); + user.setUsername('statsuser2'); + user.setPassword('oldpassword'); + user.set('email', 'stats2@example.com'); + await user.signUp(); + + // Request password reset + await Parse.User.requestPasswordReset('stats2@example.com'); + await emailSentPromise; + + const url = new URL(passwordResetLink); + const token = url.searchParams.get('token'); + + // Get index stats before the query + const config = Config.get(Parse.applicationId); + const collection = await config.database.adapter._adaptiveCollection('_User'); + const statsBefore = await collection._mongoCollection.aggregate([ + { $indexStats: {} }, + ]).toArray(); + const perishableTokenIndexBefore = statsBefore.find( + stat => stat.name === '_perishable_token' + ); + const accessesBefore = perishableTokenIndexBefore?.accesses?.ops || 0; + + // Perform password reset (this should use the index) + const request = require('../lib/request'); + await request({ + method: 'POST', + url: 'http://localhost:8378/1/apps/test/request_password_reset', + body: { new_password: 'newpassword', token, username: 'statsuser2' }, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + followRedirects: false, + }); + + // Get index stats after the query + const statsAfter = await collection._mongoCollection.aggregate([ + { $indexStats: {} }, + ]).toArray(); + const perishableTokenIndexAfter = statsAfter.find( + stat => stat.name === '_perishable_token' + ); + const accessesAfter = perishableTokenIndexAfter?.accesses?.ops || 0; + + // Verify the index was actually used + expect(accessesAfter).toBeGreaterThan(accessesBefore); + expect(perishableTokenIndexAfter).toBeDefined(); + } + ); }); describe('convertEmailToLowercase', () => { From 62b4fd6e0162c2fd420804b2c447452c6465f09b Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:56:32 +0100 Subject: [PATCH 8/9] add sparse --- src/Controllers/DatabaseController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index a8ea65a97b..e3b5cc210a 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -1765,14 +1765,14 @@ class DatabaseController { }); await this.adapter - .ensureIndex('_User', requiredUserFields, ['_email_verify_token'], '_email_verify_token', false, { sparse: false }) + .ensureIndex('_User', requiredUserFields, ['_email_verify_token'], '_email_verify_token', false) .catch(error => { logger.warn('Unable to create index for email verification token: ', error); throw error; }); await this.adapter - .ensureIndex('_User', requiredUserFields, ['_perishable_token'], '_perishable_token', false, { sparse: false }) + .ensureIndex('_User', requiredUserFields, ['_perishable_token'], '_perishable_token', false) .catch(error => { logger.warn('Unable to create index for password reset token: ', error); throw error; From 8109230137db9e0b1b3aeb97984b5c695f9fcff4 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 1 Nov 2025 12:04:57 +0100 Subject: [PATCH 9/9] Update 8.0.0.md --- 8.0.0.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/8.0.0.md b/8.0.0.md index efb0264b0d..ab41c0cf29 100644 --- a/8.0.0.md +++ b/8.0.0.md @@ -23,7 +23,7 @@ The request to re-send a verification email changed to sending a `POST` request > [!IMPORTANT] > Parse Server does not keep a history of verification tokens but only stores the most recently generated verification token in the database. Every time Parse Server generates a new verification token, the currently stored token is replaced. If a user opens a link with an expired token, and that token has already been replaced in the database, Parse Server cannot associate the expired token with any user. In this case, another way has to be offered to the user to re-send a verification email. To mitigate this issue, set the Parse Server option `emailVerifyTokenReuseIfValid: true` and set `emailVerifyTokenValidityDuration` to a longer duration, which ensures that the currently stored verification token is not replaced too soon. -Related pull requests: +Related pull request: - https://github.com/parse-community/parse-server/pull/8488 @@ -38,3 +38,7 @@ These indexes are created automatically when Parse Server starts, similar to how > [!WARNING] > If you have a large existing user base, the index creation may take some time during the first server startup after upgrading to Parse Server 8. The server logs will indicate when index creation is complete or if any errors occur. If you have any concerns regarding a potential database performance impact during index creation, you could create these indexes manually in a controlled procedure before upgrading Parse Server. + +Related pull request: + +- https://github.com/parse-community/parse-server/pull/9893