Skip to content
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: 1 addition & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ tokio = { version = "1.43", features = ["full"] }

# Web framework
axum = { version = "0.8", features = ["macros"] }
tower-http = { version = "0.6", features = ["cors", "trace"] }
tower-http = { version = "0.6", features = ["cors", "trace", "timeout"] }

# Database
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", "migrate", "json", "bigdecimal", "chrono"] }
Expand Down
13 changes: 10 additions & 3 deletions backend/crates/atlas-api/src/handlers/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,21 @@ pub async fn list_blocks(
.await?;
let total_count = total.0.unwrap_or(0);

// Convert page-based navigation to a keyset cursor using block numbers.
// Blocks are sequential so: cursor = max_block - (page - 1) * limit
// WHERE number <= cursor is O(log N) via primary key; OFFSET was O(N).
let limit = pagination.limit();
let cursor = (total_count - 1) - (pagination.page.saturating_sub(1) as i64) * limit;

let blocks: Vec<Block> = sqlx::query_as(
"SELECT number, hash, parent_hash, timestamp, gas_used, gas_limit, transaction_count, indexed_at
FROM blocks
WHERE number <= $2
ORDER BY number DESC
LIMIT $1 OFFSET $2"
LIMIT $1"
)
.bind(pagination.limit())
.bind(pagination.offset())
.bind(limit)
.bind(cursor)
.fetch_all(&state.pool)
.await?;

Expand Down
8 changes: 7 additions & 1 deletion backend/crates/atlas-api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use axum::{
};
use sqlx::PgPool;
use std::sync::Arc;
use std::time::Duration;
use tower_http::cors::{Any, CorsLayer};
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

Expand Down Expand Up @@ -49,7 +51,7 @@ async fn main() -> Result<()> {

// Run migrations
tracing::info!("Running database migrations");
atlas_common::db::run_migrations(&pool).await?;
atlas_common::db::run_migrations(&database_url).await?;

let state = Arc::new(AppState {
pool,
Expand Down Expand Up @@ -209,6 +211,10 @@ async fn main() -> Result<()> {
.route("/api/status", get(handlers::status::get_status))
// Health
.route("/health", get(|| async { "OK" }))
.layer(TimeoutLayer::with_status_code(
axum::http::StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(10),
))
.layer(
CorsLayer::new()
.allow_origin(Any)
Expand Down
27 changes: 22 additions & 5 deletions backend/crates/atlas-common/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use sqlx::{Executor, PgPool};

/// Create a database connection pool
/// Create a database connection pool.
/// Sets statement_timeout = 10s on every connection to prevent slow queries
/// from exhausting the pool.
pub async fn create_pool(database_url: &str, max_connections: u32) -> Result<PgPool, sqlx::Error> {
PgPoolOptions::new()
.max_connections(max_connections)
.after_connect(|conn, _meta| {
Box::pin(async move {
conn.execute("SET statement_timeout = '10s'").await?;
Ok(())
})
})
.connect(database_url)
.await
}

/// Run database migrations
pub async fn run_migrations(pool: &PgPool) -> Result<(), sqlx::migrate::MigrateError> {
sqlx::migrate!("../../migrations").run(pool).await
/// Run database migrations using a dedicated connection without statement_timeout,
/// since migrations (index builds, bulk inserts) can legitimately exceed 10s.
pub async fn run_migrations(database_url: &str) -> Result<(), sqlx::Error> {
let pool = PgPoolOptions::new()
.max_connections(1)
.connect(database_url)
.await?;
sqlx::migrate!("../../migrations")
.run(&pool)
.await
.map_err(|e| sqlx::Error::Migrate(Box::new(e)))?;
Ok(())
}
2 changes: 1 addition & 1 deletion backend/crates/atlas-indexer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async fn main() -> Result<()> {

// Run migrations
tracing::info!("Running database migrations");
atlas_common::db::run_migrations(&pool).await?;
atlas_common::db::run_migrations(&config.database_url).await?;

// Start indexer
let indexer = indexer::Indexer::new(pool.clone(), config.clone());
Expand Down