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

feat(migrations): add database indexes for performance optimization #18

Merged
merged 1 commit into from
Feb 26, 2025
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
2 changes: 2 additions & 0 deletions torii-storage-postgres/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod session;
use async_trait::async_trait;
use chrono::DateTime;
use chrono::Utc;
use migrations::CreateIndexes;
use migrations::CreateOAuthAccountsTable;
use migrations::CreatePasskeyChallengesTable;
use migrations::CreatePasskeysTable;
Expand Down Expand Up @@ -45,6 +46,7 @@ impl PostgresStorage {
Box::new(CreateOAuthAccountsTable),
Box::new(CreatePasskeysTable),
Box::new(CreatePasskeyChallengesTable),
Box::new(CreateIndexes),
];
manager.up(&migrations).await.map_err(|e| {
tracing::error!(error = %e, "Failed to run migrations");
Expand Down
104 changes: 102 additions & 2 deletions torii-storage-postgres/src/migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,104 @@ impl Migration<Postgres> for CreatePasskeyChallengesTable {
}
}

pub struct CreateIndexes;

#[async_trait]
impl Migration<Postgres> for CreateIndexes {
fn version(&self) -> i64 {
6
}

fn name(&self) -> &str {
"CreateIndexes"
}

async fn up<'a>(
&'a self,
conn: &'a mut <Postgres as Database>::Connection,
) -> Result<(), MigrationError> {
// Index for email searches
sqlx::query("CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)")
.execute(&mut *conn)
.await?;

// Index for sessions user_id foreign key
sqlx::query("CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id)")
.execute(&mut *conn)
.await?;

// Index for sessions expiration
sqlx::query("CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at)")
.execute(&mut *conn)
.await?;

// Indexes for oauth_accounts lookups
sqlx::query(
"CREATE INDEX IF NOT EXISTS idx_oauth_accounts_user_id ON oauth_accounts(user_id)",
)
.execute(&mut *conn)
.await?;

sqlx::query(
"CREATE INDEX IF NOT EXISTS idx_oauth_accounts_provider_subject ON oauth_accounts(provider, subject)",
)
.execute(&mut *conn)
.await?;

// Index for passkeys user_id
sqlx::query("CREATE INDEX IF NOT EXISTS idx_passkeys_user_id ON passkeys(user_id)")
.execute(&mut *conn)
.await?;

// Index for passkey credential lookup
sqlx::query(
"CREATE INDEX IF NOT EXISTS idx_passkeys_credential_id ON passkeys(credential_id)",
)
.execute(&mut *conn)
.await?;

// Index for passkey challenges expiration
sqlx::query(
"CREATE INDEX IF NOT EXISTS idx_passkey_challenges_expires_at ON passkey_challenges(expires_at)",
)
.execute(&mut *conn)
.await?;

Ok(())
}

async fn down<'a>(
&'a self,
conn: &'a mut <Postgres as Database>::Connection,
) -> Result<(), MigrationError> {
sqlx::query("DROP INDEX IF EXISTS idx_users_email")
.execute(&mut *conn)
.await?;
sqlx::query("DROP INDEX IF EXISTS idx_sessions_user_id")
.execute(&mut *conn)
.await?;
sqlx::query("DROP INDEX IF EXISTS idx_sessions_expires_at")
.execute(&mut *conn)
.await?;
sqlx::query("DROP INDEX IF EXISTS idx_oauth_accounts_user_id")
.execute(&mut *conn)
.await?;
sqlx::query("DROP INDEX IF EXISTS idx_oauth_accounts_provider_subject")
.execute(&mut *conn)
.await?;
sqlx::query("DROP INDEX IF EXISTS idx_passkeys_user_id")
.execute(&mut *conn)
.await?;
sqlx::query("DROP INDEX IF EXISTS idx_passkeys_credential_id")
.execute(&mut *conn)
.await?;
sqlx::query("DROP INDEX IF EXISTS idx_passkey_challenges_expires_at")
.execute(&mut *conn)
.await?;
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -415,18 +513,19 @@ mod tests {
Box::new(CreateOAuthAccountsTable),
Box::new(CreatePasskeysTable),
Box::new(CreatePasskeyChallengesTable),
Box::new(CreateIndexes),
];
manager.up(&migrations).await?;

// Verify migration was applied
let applied = manager.is_applied(3).await?;
let applied = manager.is_applied(6).await?;
assert!(applied, "Migration should be applied");

// Test down migrations
manager.down(&migrations).await?;

// Verify migration was rolled back
let applied = manager.is_applied(3).await?;
let applied = manager.is_applied(6).await?;
assert!(!applied, "Migration should be rolled back");

Ok(())
Expand All @@ -443,6 +542,7 @@ mod tests {
Box::new(CreateOAuthAccountsTable),
Box::new(CreatePasskeysTable),
Box::new(CreatePasskeyChallengesTable),
Box::new(CreateIndexes),
];
manager.up(&migrations).await?;

Expand Down
2 changes: 2 additions & 0 deletions torii-storage-sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod session;
use async_trait::async_trait;
use chrono::DateTime;
use chrono::Utc;
use migrations::CreateIndexes;
use migrations::{
CreateOAuthAccountsTable, CreatePasskeyChallengesTable, CreatePasskeysTable,
CreateSessionsTable, CreateUsersTable, SqliteMigrationManager,
Expand Down Expand Up @@ -51,6 +52,7 @@ impl SqliteStorage {
Box::new(CreateOAuthAccountsTable),
Box::new(CreatePasskeysTable),
Box::new(CreatePasskeyChallengesTable),
Box::new(CreateIndexes),
];
manager.up(&migrations).await.map_err(|e| {
tracing::error!(error = %e, "Failed to run migrations");
Expand Down
78 changes: 75 additions & 3 deletions torii-storage-sqlite/src/migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,76 @@ impl Migration<Sqlite> for CreatePasskeyChallengesTable {
}
}

pub struct CreateIndexes;

#[async_trait]
impl Migration<Sqlite> for CreateIndexes {
fn version(&self) -> i64 {
6
}

fn name(&self) -> &str {
"CreateIndexes"
}

async fn up<'a>(
&'a self,
conn: &'a mut <Sqlite as Database>::Connection,
) -> Result<(), MigrationError> {
// Create all indexes
sqlx::query(
r#"
-- Users table indexes
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);

-- Sessions table indexes
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);

-- OAuth accounts table indexes
CREATE INDEX IF NOT EXISTS idx_oauth_accounts_user_id ON oauth_accounts(user_id);
CREATE INDEX IF NOT EXISTS idx_oauth_accounts_provider_subject ON oauth_accounts(provider, subject);

-- OAuth state table indexes
CREATE INDEX IF NOT EXISTS idx_oauth_state_expires_at ON oauth_state(expires_at);

-- Passkeys table indexes
CREATE INDEX IF NOT EXISTS idx_passkeys_user_id ON passkeys(user_id);

-- Passkey challenges table indexes
CREATE INDEX IF NOT EXISTS idx_passkey_challenges_expires_at ON passkey_challenges(expires_at);
"#,
)
.execute(conn)
.await?;

Ok(())
}

async fn down<'a>(
&'a self,
conn: &'a mut <Sqlite as Database>::Connection,
) -> Result<(), MigrationError> {
// Drop all indexes in a single query
sqlx::query(
r#"
DROP INDEX IF EXISTS idx_users_email;
DROP INDEX IF EXISTS idx_sessions_user_id;
DROP INDEX IF EXISTS idx_sessions_expires_at;
DROP INDEX IF EXISTS idx_oauth_accounts_user_id;
DROP INDEX IF EXISTS idx_oauth_accounts_provider_subject;
DROP INDEX IF EXISTS idx_oauth_state_expires_at;
DROP INDEX IF EXISTS idx_passkeys_user_id;
DROP INDEX IF EXISTS idx_passkey_challenges_expires_at;
"#,
)
.execute(conn)
.await?;

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -392,18 +462,19 @@ mod tests {
Box::new(CreateOAuthAccountsTable),
Box::new(CreatePasskeysTable),
Box::new(CreatePasskeyChallengesTable),
Box::new(CreateIndexes),
];
manager.up(&migrations).await?;

// Verify migration was applied
let applied = manager.is_applied(5).await?;
let applied = manager.is_applied(6).await?;
assert!(applied, "Migration should be applied");

// Test down migrations
manager.down(&migrations).await?;

// Verify migration was rolled back
let applied = manager.is_applied(5).await?;
let applied = manager.is_applied(6).await?;
assert!(!applied, "Migration should be rolled back");

Ok(())
Expand All @@ -428,6 +499,7 @@ mod tests {
Box::new(CreateOAuthAccountsTable),
Box::new(CreatePasskeysTable),
Box::new(CreatePasskeyChallengesTable),
Box::new(CreateIndexes),
];
manager.up(&migrations).await?;

Expand All @@ -438,7 +510,7 @@ mod tests {
manager.up(&migrations).await?;

// Verify migration was applied
let applied = manager.is_applied(5).await?;
let applied = manager.is_applied(6).await?;
assert!(applied, "Migration should be applied");

Ok(())
Expand Down