# How Macro Manages Database Migrations Across Its SQLx Database Clients

> Learn how Macro Inc manages database migrations for its SQLx clients. Discover the centralized macro_db_client crate and shared macro_db_migrator library for efficient schema changes.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: how-to-guide
- Published: 2026-08-16

---

**Macro centralizes all PostgreSQL schema changes in a single `macro_db_client` crate, exposing migrations through a shared `macro_db_migrator` library that every downstream service consumes.**

The Macro codebase follows a **crate-based migration strategy** to keep database schema changes consistent across multiple microservices. Rather than duplicating migration logic, the engineering team maintains one authoritative source in `crates/macro_db_client` and distributes changes via Cargo dependencies and the `sqlx::migrate!` macro.

## Centralized Migration Storage

All **`.sql` migration files** live in `crates/macro_db_client/migrations/`. The naming convention follows chronological ordering:

- [`0001_baseline.sql`](https://github.com/macro-inc/macro/blob/main/0001_baseline.sql) — initial schema
- [`20260715154137_add_entity_access_source_type_entity_plain_index.sql`](https://github.com/macro-inc/macro/blob/main/20260715154137_add_entity_access_source_type_entity_plain_index.sql) — incremental changes

This directory serves as the **single source of truth** for the entire Macro platform. Any schema modification made here automatically propagates to every service that depends on `macro_db_client`.

## The Migration Distribution Architecture

Macro separates migration *storage* from migration *execution* across two crates:

| Component | Purpose | Key File |
|-----------|---------|----------|
| `macro_db_client` | Stores `.sql` files; shared dependency for all services | `crates/macro_db_client/migrations/` |
| `macro_db_migrator` | Exposes `sqlx::migrate!` macro to run migrations | [`crates/macro_db_migrator/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_migrator/src/lib.rs) |

In [`crates/macro_db_migrator/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_migrator/src/lib.rs), the migrations are exposed using SQLx's compile-time checked macro:

```rust
// Re-exports migrations from the canonical location
pub fn run(pool: &sqlx::PgPool) -> Result<()> {
    sqlx::migrate!("../macro_db_client/migrations")
        .run(pool)
        .await
}

```

This indirection allows services to execute migrations without hardcoding paths to the migration directory.

## How Services Consume Shared Migrations

Every service that touches PostgreSQL adds `macro_db_client` to its [`Cargo.toml`](https://github.com/macro-inc/macro/blob/main/Cargo.toml):

```toml
[dependencies]
macro_db_client = { path = "../../crates/macro_db_client" }

```

To apply migrations, services invoke the migrator at startup or during CI:

```rust
use macro_db_migrator::run;

async fn initialize_database(pool: &sqlx::PgPool) -> anyhow::Result<()> {
    run(pool).await?;
    Ok(())
}

```

### Test Integration with SQLx

Integration tests use the `#[sqlx::test]` attribute with an explicit migrations path. This ensures each test runs against a fresh database with the latest schema:

```rust
// Example from crates/stream/src/outbound/redis_pg/queries/test.rs
#[sqlx::test(migrations = "../macro_db_client/migrations")]
async fn test_document_queries(pool: PgPool) -> sqlx::Result<()> {
    // Test logic executes against migrated schema
}

```

## Local Development and CI Workflows

The `xtask` tooling standardizes migration commands across environments. In `crates/macro_db_client/justfile`:

```just

# Create a fresh database for local development

create_db:
    # Database creation logic

# Run pending SQLx migrations

migrate_db:
    sqlx migrate run --source migrations/

```

These commands are invoked through the workspace-level `justfile`:

```sh
just crates/macro_db_client/create_db   # fresh database

just crates/macro_db_client/migrate_db  # apply migrations

```

### Production and CI Safety

The same `just` commands wrap additional safety checks. In [`tooling/xtask/crates/xtask_workflows/src/workflows/preview_fly.rs`](https://github.com/macro-inc/macro/blob/main/tooling/xtask/crates/xtask_workflows/src/workflows/preview_fly.rs), migrations are referenced when previewing Fly.io deployments, with prompts that warn before applying changes to production databases.

## Key Implementation Files

Understanding these files clarifies how database migrations are managed across Macro's SQLx clients:

- **`crates/macro_db_client/migrations/`** — Canonical `.sql` migration series
- **`crates/macro_db_client/justfile`** — Local development commands (`create_db`, `migrate_db`)
- **[`crates/macro_db_migrator/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_migrator/src/lib.rs)** — `sqlx::migrate!` wrapper exposing the migration set
- **`services/*/Cargo.toml`** — Demonstrates `macro_db_client` dependency pattern
- **[`crates/stream/src/outbound/redis_pg/queries/test.rs`](https://github.com/macro-inc/macro/blob/main/crates/stream/src/outbound/redis_pg/queries/test.rs)** — `#[sqlx::test(migrations = "...")]` usage
- **[`tooling/xtask/crates/xtask_local/src/local/db.rs`](https://github.com/macro-inc/macro/blob/main/tooling/xtask/crates/xtask_local/src/local/db.rs)** — Local snapshot migration runner
- **[`tooling/xtask/crates/xtask_workflows/src/workflows/preview_fly.rs`](https://github.com/macro-inc/macro/blob/main/tooling/xtask/crates/xtask_workflows/src/workflows/preview_fly.rs)** — Production deployment migration handling

## Summary

- **Single crate ownership**: `macro_db_client` houses all `.sql` migrations, preventing drift across services
- **Macro-based distribution**: `sqlx::migrate!("../macro_db_client/migrations")` in [`macro_db_migrator/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/macro_db_migrator/src/lib.rs) compiles migrations into consumable code
- **Dependency sharing**: Services import `macro_db_client` via path dependencies in [`Cargo.toml`](https://github.com/macro-inc/macro/blob/main/Cargo.toml)
- **Test synchronization**: `#[sqlx::test(migrations = "...")]` guarantees consistent schema in integration tests
- **Unified tooling**: `just` commands in `crates/macro_db_client/justfile` standardize local and CI workflows

## Frequently Asked Questions

### How do Macro's microservices stay synchronized with database schema changes?

Services depend on `macro_db_client` via Cargo path dependencies. When the migration files in `crates/macro_db_client/migrations/` change, developers update their lockfiles and receive the new schema on next build. The shared `macro_db_migrator::run()` function ensures all services apply identical migrations.

### Can individual services override or skip migrations?

No. The architecture intentionally prevents this. Services call `macro_db_migrator::run()` which hardcodes the path to `../macro_db_client/migrations`. There is no mechanism to inject alternative migration sources without forking the migrator crate.

### How are migrations tested before production deployment?

Integration tests use `#[sqlx::test(migrations = "../macro_db_client/migrations")]` to spin up isolated PostgreSQL instances with the full schema. CI pipelines run these tests across all dependent crates. The `xtask_workflows` crate additionally validates migrations during Fly.io preview deployments.

### What prevents accidental production database modifications?

The `xtask` tooling layers interactive prompts onto migration commands for production environments. While Local development uses `just crates/macro_db_client/migrate_db` directly, production workflows in `tooling/xtask/crates/xtask_workflows/` require explicit confirmation before executing schema changes against live databases.