Skip to content

Commit dd56ae9

Browse files
authored
fix(indexworker): skip index creation on OrioleDB (#2481)
OrioleDB does not support `CREATE INDEX CONCURRENTLY`. Detect the table's storage engine via `pg_am` before acquiring the advisory lock and skip index creation when OrioleDB is detected.
1 parent 2d8f2b6 commit dd56ae9

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed

internal/api/apiworker/apiworker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func (o *Worker) maybeCreateIndexes(
217217
) {
218218
if cfg.IndexWorker.EnsureUserSearchIndexesExist || cfg.IndexWorker.MaxUsersThreshold > 0 {
219219
err := indexworker.CreateIndexes(ctx, cfg, le)
220-
if err != nil && !errors.Is(err, indexworker.ErrAdvisoryLockAlreadyAcquired) {
220+
if err != nil && !errors.Is(err, indexworker.ErrAdvisoryLockAlreadyAcquired) && !errors.Is(err, indexworker.ErrOrioleDBUnsupported) {
221221
le.WithError(err).Error("Failed to create indexes")
222222
}
223223
}

internal/indexworker/indexworker.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
// ErrAdvisoryLockAlreadyAcquired is returned when another process already holds the advisory lock
1818
var ErrAdvisoryLockAlreadyAcquired = errors.New("advisory lock already acquired by another process")
19-
var ErrExtensionNotFound = errors.New("extension not found")
19+
var ErrOrioleDBUnsupported = errors.New("index worker does not support OrioleDB tables")
2020

2121
type Outcome string
2222

@@ -64,6 +64,19 @@ func CreateIndexes(ctx context.Context, config *conf.GlobalConfiguration, le *lo
6464
}
6565
db = db.WithContext(ctx)
6666

67+
// Check if the users table uses OrioleDB, which does not support
68+
// CREATE INDEX CONCURRENTLY or DROP INDEX CONCURRENTLY.
69+
isOrioleDB, err := isOrioleDBTable(db, config.DB.Namespace, "users")
70+
if err != nil {
71+
le.WithError(err).Warn("Failed to check table access method, proceeding with index creation")
72+
} else if isOrioleDB {
73+
le.WithFields(logrus.Fields{
74+
"outcome": OutcomeSkipped,
75+
"code": "orioledb_unsupported",
76+
}).Info("Skipping index creation because auth.users uses the OrioleDB storage engine, which does not support CONCURRENTLY operations")
77+
return ErrOrioleDBUnsupported
78+
}
79+
6780
// Try to obtain advisory lock to ensure only one index worker is creating indexes at a time
6881
lockName := "auth_index_worker"
6982
var lockAcquired bool
@@ -306,6 +319,24 @@ func getApproximateUserCount(db *pop.Connection, namespace string) (int64, error
306319
return userCount, nil
307320
}
308321

322+
// isOrioleDBTable checks if the specified table uses the OrioleDB storage engine
323+
// by examining the table's access method in pg_class.
324+
func isOrioleDBTable(db *pop.Connection, namespace, tableName string) (bool, error) {
325+
query := fmt.Sprintf(`
326+
SELECT am.amname
327+
FROM pg_class c
328+
JOIN pg_am am ON c.relam = am.oid
329+
JOIN pg_namespace n ON c.relnamespace = n.oid
330+
WHERE n.nspname = '%s' AND c.relname = '%s'
331+
`, namespace, tableName)
332+
333+
var amName string
334+
if err := db.RawQuery(query).First(&amName); err != nil {
335+
return false, fmt.Errorf("failed to check table access method: %w", err)
336+
}
337+
return amName == "orioledb", nil
338+
}
339+
309340
// dropInvalidIndexes drops any invalid indexes from previous interrupted attempts
310341
func dropInvalidIndexes(db *pop.Connection, le *logrus.Entry, namespace string, indexNames []string) {
311342
indexNamesList := make([]string, len(indexNames))

internal/indexworker/indexworker_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type IndexWorkerTestSuite struct {
2828
logger *logrus.Entry
2929
maxUsersThreshold int64
3030
ensureUserSearchIndexesExist bool
31+
isOrioleDB bool
3132
}
3233

3334
func (ts *IndexWorkerTestSuite) SetupSuite() {
@@ -59,6 +60,14 @@ func (ts *IndexWorkerTestSuite) SetupSuite() {
5960
require.NoError(ts.T(), popConn.Open())
6061
ts.popDB = popConn
6162

63+
// Detect if the users table uses OrioleDB
64+
isOrioleDB, err := isOrioleDBTable(popConn, config.DB.Namespace, "users")
65+
if err != nil {
66+
ts.T().Logf("Failed to detect OrioleDB, assuming standard PostgreSQL: %v", err)
67+
} else {
68+
ts.isOrioleDB = isOrioleDB
69+
}
70+
6271
// Ensure we have a clean state for testing
6372
ts.cleanupIndexes()
6473
}
@@ -90,6 +99,9 @@ func (ts *IndexWorkerTestSuite) cleanupIndexes() {
9099
}
91100

92101
func (ts *IndexWorkerTestSuite) TestCreateIndexesHappyPath() {
102+
if ts.isOrioleDB {
103+
ts.T().Skip("OrioleDB does not support CREATE INDEX CONCURRENTLY")
104+
}
93105
ctx := context.Background()
94106

95107
err := CreateIndexes(ctx, ts.config, ts.logger)
@@ -132,6 +144,9 @@ func (ts *IndexWorkerTestSuite) TestGetIndexStatuses() {
132144
}
133145

134146
func (ts *IndexWorkerTestSuite) TestIdempotency() {
147+
if ts.isOrioleDB {
148+
ts.T().Skip("OrioleDB does not support CREATE INDEX CONCURRENTLY")
149+
}
135150
ctx := context.Background()
136151

137152
// First run - create all indexes
@@ -188,6 +203,9 @@ func (ts *IndexWorkerTestSuite) TestIdempotency() {
188203

189204
// If an index is removed out of band, it will be created when the method is called
190205
func (ts *IndexWorkerTestSuite) TestOutOfBandIndexRemoval() {
206+
if ts.isOrioleDB {
207+
ts.T().Skip("OrioleDB does not support CREATE INDEX CONCURRENTLY")
208+
}
191209
ctx := context.Background()
192210

193211
// First, create all indexes
@@ -235,6 +253,9 @@ func (ts *IndexWorkerTestSuite) TestOutOfBandIndexRemoval() {
235253

236254
// Test concurrent access - workers coordinate via advisory lock
237255
func (ts *IndexWorkerTestSuite) TestConcurrentWorkers() {
256+
if ts.isOrioleDB {
257+
ts.T().Skip("OrioleDB does not support CREATE INDEX CONCURRENTLY")
258+
}
238259
ctx := context.Background()
239260

240261
numWorkers := 5
@@ -292,6 +313,9 @@ func getIndexNames(indexes []struct {
292313
// TestMaxUsersThresholdSkipsIndexCreation verifies when EnsureUserSearchIndexesExist=false and MaxUsersThreshold > 0,
293314
// index creation is skipped if user count exceeds the threshold.
294315
func (ts *IndexWorkerTestSuite) TestMaxUsersThresholdSkipsIndexCreation() {
316+
if ts.isOrioleDB {
317+
ts.T().Skip("OrioleDB does not support CREATE INDEX CONCURRENTLY")
318+
}
295319
ctx := context.Background()
296320

297321
// No explicit user opt-in - rely on threshold behavior
@@ -328,6 +352,9 @@ func (ts *IndexWorkerTestSuite) TestMaxUsersThresholdSkipsIndexCreation() {
328352
// TestUserOptInAlwaysCreatesIndexes verifies that when EnsureUserSearchIndexesExist=true,
329353
// indexes are always created regardless of user count or threshold setting.
330354
func (ts *IndexWorkerTestSuite) TestUserOptInAlwaysCreatesIndexes() {
355+
if ts.isOrioleDB {
356+
ts.T().Skip("OrioleDB does not support CREATE INDEX CONCURRENTLY")
357+
}
331358
ctx := context.Background()
332359

333360
// Insert test users so there's a non-zero user count
@@ -361,6 +388,9 @@ func (ts *IndexWorkerTestSuite) TestUserOptInAlwaysCreatesIndexes() {
361388
// TestUserOptInWithZeroThreshold verifies that EnsureUserSearchIndexesExist=true
362389
// with MaxUsersThreshold=0 (disabled) still creates indexes.
363390
func (ts *IndexWorkerTestSuite) TestUserOptInWithZeroThreshold() {
391+
if ts.isOrioleDB {
392+
ts.T().Skip("OrioleDB does not support CREATE INDEX CONCURRENTLY")
393+
}
364394
ctx := context.Background()
365395

366396
ts.config.IndexWorker.EnsureUserSearchIndexesExist = true
@@ -383,6 +413,9 @@ func (ts *IndexWorkerTestSuite) TestUserOptInWithZeroThreshold() {
383413
// This test simulates a scenario where indexes become invalid (e.g., from interrupted CONCURRENT creation)
384414
// and verifies that CreateIndexes properly handles them by dropping and recreating.
385415
func (ts *IndexWorkerTestSuite) TestCreateIndexesWithInvalidIndexes() {
416+
if ts.isOrioleDB {
417+
ts.T().Skip("OrioleDB does not support CREATE INDEX CONCURRENTLY")
418+
}
386419
ctx := context.Background()
387420

388421
// Step 1: Run CreateIndexes to create all indexes
@@ -477,6 +510,27 @@ func (ts *IndexWorkerTestSuite) TestCreateIndexesWithInvalidIndexes() {
477510
ts.logger.Infof("Successfully recovered from %d invalid indexes", len(indexesToInvalidate))
478511
}
479512

513+
func (ts *IndexWorkerTestSuite) TestIsOrioleDBTableNonExistentTable() {
514+
_, err := isOrioleDBTable(ts.popDB, ts.namespace, "nonexistent_table")
515+
assert.Error(ts.T(), err, "Should return error for non-existent table")
516+
}
517+
518+
func (ts *IndexWorkerTestSuite) TestCreateIndexesSkipsOnOrioleDB() {
519+
if !ts.isOrioleDB {
520+
ts.T().Skip("Test only runs on OrioleDB")
521+
}
522+
ctx := context.Background()
523+
524+
err := CreateIndexes(ctx, ts.config, ts.logger)
525+
require.ErrorIs(ts.T(), err, ErrOrioleDBUnsupported)
526+
527+
// Verify no indexes were created
528+
indexes := getUsersIndexes(ts.namespace)
529+
existingIndexes, err := getIndexStatuses(ts.popDB, ts.namespace, getIndexNames(indexes))
530+
require.NoError(ts.T(), err)
531+
assert.Empty(ts.T(), existingIndexes, "No indexes should be created when OrioleDB is detected")
532+
}
533+
480534
// Run the test suite
481535
func TestIndexWorker(t *testing.T) {
482536
suite.Run(t, new(IndexWorkerTestSuite))

0 commit comments

Comments
 (0)