오프라인 스키마 관리: Rust 애플리케이션 강화를 위한 sqlx-cli 및 diesel-cli 활용
Wenhao Wang
Dev Intern · Leapcell

소개
현대 소프트웨어 개발 세계에서 데이터베이스는 거의 모든 애플리케이션의 기반입니다. 애플리케이션이 발전함에 따라 데이터 구조도 진화합니다. 데이터베이스 마이그레이션으로 알려진 이러한 변경 사항을 관리하는 것은 애플리케이션 안정성, 유지 보수성 및 배포 가능성에 직접적인 영향을 미치는 중요한 작업입니다. Rust 생태계에서 강력한 타이핑과 컴파일 타임 보장이 높이 평가되는 곳에서는 애플리케이션의 데이터 모델이 데이터베이스 스키마와 동기화되도록 하는 것이 가장 중요합니다. 마이그레이션 관리를 위한 많은 도구가 있지만, 라이브 데이터베이스 연결 없이 오프라인 스키마 검사를 수행하고 마이그레이션을 관리할 수 있는 기능은 특히 CI/CD 파이프라인이나 엄격한 네트워크 액세스가 있는 로컬 개발 환경에서 상당한 이점을 제공합니다. 이 글에서는 Rust 개발자가 sqlx-cli
및 diesel-cli
와 같은 강력한 명령줄 도구를 활용하여 데이터베이스 마이그레이션 및 스키마를 효과적으로 관리하는 방법을 자세히 살펴보고, 특히 오프라인 기능에 중점을 둡니다.
환경 이해
sqlx-cli
및 diesel-cli
의 구체적인 내용을 다루기 전에 Rust에서 데이터베이스 스키마 관리의 기반이 되는 몇 가지 핵심 개념을 명확히 해 봅시다:
- 데이터베이스 마이그레이션: 데이터베이스 스키마에 대한 프로그래밍 방식 변경입니다. 일반적으로 데이터베이스를 한 상태에서 다른 상태로 발전시키는 방법(예: 새 테이블 추가, 열 수정, 인덱스 생성)을 정의하는 버전이 지정된 파일(예: SQL 스크립트 또는 Rust 코드)입니다.
- 스키마: 테이블, 열, 관계, 인덱스 및 기타 데이터베이스 요소에 대한 공식 설명입니다.
- ORM(객체 관계형 매퍼): 객체 지향 프로그래밍 언어를 사용하여 호환되지 않는 유형 시스템 간의 데이터를 변환하는 프로그래밍 기술입니다. Rust에서
Diesel
은 저명한 ORM입니다. - 쿼리 빌더: 개발자가 일반적으로 유형 안전성을 제공하고 원시 SQL 문자열을 피할 수 있도록 프로그래밍 방식으로 SQL 쿼리를 구성할 수 있는 라이브러리입니다.
SQLx
는 주로 쿼리 빌더이자 비동기 데이터베이스 드라이버입니다. - 오프라인 스키마 확인: 라이브 데이터베이스 연결 없이 애플리케이션의 데이터 모델(예: Rust 구조체)과 데이터베이스 스키마 간의 불일치를 확인하거나 마이그레이션 파일을 생성할 수 있는 기능입니다. 이를 통해 개발이 크게 빨라지고 CI/CD 효율성이 향상됩니다.
SQLx
와 Diesel
모두 Rust에서 데이터베이스와 상호 작용하기 위한 강력한 솔루션을 제공하며, 해당 CLI 도구는 이러한 기능을 스키마 관리로 확장합니다. Diesel
은 Rust 코드 내에서 유형 안전한 쿼리와 스키마 정의에 중점을 둔 ORM인 반면, SQLx
는 유형 안전한 원시 SQL 쿼리와 보다 간편한 스키마 정의 접근 방식을 강조합니다. 그러나 두 도구 모두 현대 개발에 필수적인 강력한 마이그레이션 유틸리티를 제공합니다.
sqlx-cli: 유형 안전한 SQL 및 오프라인 확인
sqlx-cli
는 SQLx
데이터베이스 크레이트의 명령줄 인터페이스입니다. SQLx
는 원시 SQL 쿼리의 컴파일 타임 확인으로 유명하며, 애플리케이션을 실행하기 전에 쿼리가 구문적으로 올바르고 데이터베이스 스키마와 일치하는지 확인합니다. 이것이 sqlx-cli
가 특히 오프라인 sqlx database diff
및 sqlx migrate add
기능을 통해 빛나는 부분입니다.
기본적으로 sqlx-cli
는 SQL 스크립트 파일을 통해 마이그레이션을 관리합니다. sqlx-cli
를 사용하는 일반적인 마이그레이션 워크플로우는 다음과 같습니다.
-
새로운 마이그레이션 생성:
sqlx migrate add create_users_table
이 명령은 지정된 마이그레이션 디렉터리(일반적으로
migrations/
)에 각각 마이그레이션을 적용하고 되돌리기 위한_up.sql
및_down.sql
접미사가 있는 새 마이그레이션 파일을 생성합니다. -
마이그레이션 SQL 작성: 그런 다음 이러한 파일에 SQL DDL 문을 채웁니다.
-- migrations/20231027100000_create_users_table.up.sql CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP );
-- migrations/20231027100000_create_users_table.down.sql DROP TABLE users;
-
마이그레이션 적용: 일반적으로 다음을 실행합니다.
sqlx migrate run
이것은 보류 중인 모든 마이그레이션을 데이터베이스에 적용합니다.
오프라인 확인 맥락에서 sqlx-cli
의 주요 기능은 애플리케이션의 쿼리를 마이그레이션 기록과 확인할 수 있다는 것입니다. SQLx
는 이전 데이터베이스 상호 작용 또는 sqlx prepare
에 의해 생성된 .sqlx
메타데이터 파일을 저장합니다. 이 파일은 예상 스키마 및 쿼리 유형을 캡처합니다. 새 쿼리가 있거나 스키마가 수정된 경우 sqlx prepare --check
를 실행하면 SQLx
DATABASE_URL
환경 변수가 실제 데이터베이스 대신 스키마 파일을 가리키는 경우 라이브 데이터베이스 연결 없이 데이터베이스 마이그레이션과 Rust 코드를 확인하고 불일치를 식별할 수 있습니다.
또는 sqlx-cli
자체는 DATABASE_URL
이 스키마 파일을 가리킬 필요 없이 강력한 오프라인 확인을 제공합니다. Rust 애플리케이션과 일련의 마이그레이션이 있는 프로젝트를 생각해 보세요. CI 실행마다 라이브 데이터베이스를 부팅하여 애플리케이션 쿼리가 예상 스키마와 여전히 유효한지 확인하는 대신, sqlx-cli
는 마이그레이션 파일에서 데이터베이스 스키마를 합성할 수 있습니다.
# 이 명령은 오프라인에서 실행하여 마이그레이션이 유효한 SQL이고 # 명백한 구문 오류가 없는지 확인할 수 있습니다. sqlx migrate info
sqlx-cli
는 주로 마이그레이션 실행에 중점을 두지만, sqlx database diff
명령은 Rust 구조체에서 직접 오프라인 스키마 생성 및 비교를 처리하거나 스키마 파일을 통해 표현되는 두 개의 다른 데이터베이스 상태를 비교하거나 라이브 데이터베이스를 비교하는 강력한 실험적 기능입니다. Rust 구조체에서 모든 오프라인 스키마 생성을 위한 완숙한 기능은 아니지만, 그 개발은 Rust 구조체에서 더 정교한 오프라인 스키마 관리를 향한 명확한 경로를 나타냅니다. 현재 sqlx
사용자를 위한 주요 "오프라인 확인"은 .sqlx
메타데이터 파일에 의해 제공되는 컴파일 타임 확인에서 비롯되며, 이는 SQLX_OFFLINE
이 true
로 설정되어 있거나(sqlx prepare --check
에 의해) 암시적으로 cargo check
에 의해 확인됩니다.
// src/main.rs #[macro_use] extern crate sqlx; #[tokio::main] async fn main() -> Result<(), sqlx::Error> { // 실제 애플리케이션에서는 DATABASE_URL이 환경 변수에서 로드됩니다. let database_url = std::env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); let pool = sqlx::PgPool::connect(&database_url).await?; let row: (i64,) = sqlx::query_as("SELECT $1 FROM users") // `users` 테이블이 없으면 여기서 오류 발생 .bind(150_i64) .fetch_one(&pool) .await?; println!("{}", row.0); Ok(()) }
SQLX_OFFLINE=true
가 설정되어 있으면 sqlx
는 캐시된 정보를 사용하여 쿼리를 유효성 검사하므로 cargo check
는 쿼리에 대한 오프라인 스키마 유효성 검사 도구로 효과적으로 작동합니다. 이는 CI/CD에 매우 강력한 기능입니다.
diesel-cli: ORM 기반 마이그레이션 및 스키마 생성
diesel-cli
는 Diesel
ORM의 명령줄 도구입니다. Diesel
은 매크로를 통해 Rust 코드 내에서 스키마를 직접 정의하는 데 중점을 두어 데이터베이스 상호 작용에 다른 접근 방식을 취합니다. 이를 통해 쿼리 및 조작에 대해 높은 수준의 유형 안전성을 제공할 수 있습니다.
diesel-cli
의 스키마 관리는 schema.rs
파일과 마이그레이션 시스템을 중심으로 이루어집니다.
-
Diesel 초기화:
diesel setup
이 명령은 필요한 폴더와
diesel.toml
구성 파일을 설정합니다. -
schema.rs
생성: 여기에서diesel-cli
가 오프라인 작업에 진정으로 빛을 발합니다.diesel-cli
는 라이브 데이터베이스 연결을 검사하여 데이터베이스 테이블 및 열의 Rust 표현을 포함하는src/schema.rs
파일을 생성할 수 있습니다.diesel print-schema > src/schema.rs
이
schema.rs
파일은 Rust 애플리케이션 내에서 데이터베이스 스키마의 단일 진실 공급원 역할을 합니다. 그러나 이 특정 명령은 라이브 데이터베이스 연결이 필요합니다.diesel-cli
의 오프라인 파워는 마이그레이션 시스템 과schema.rs
가 생성되면 애플리케이션이 라이브 데이터베이스가 아닌 해당 파일에 대해 유형 검사를 받는다는 사실에서 비롯됩니다.schema.rs
가 마이그레이션과 최신 상태를 유지하도록 수동으로 확인할 수 있습니다. -
새 마이그레이션 생성:
diesel migration generate create_posts_table
sqlx-cli
와 유사하게 마이그레이션을 위한up.sql
및down.sql
파일이 생성됩니다. -
마이그레이션 SQL 작성:
-- migrations/20231027100000_create_posts_table/up.sql CREATE TABLE posts ( id SERIAL PRIMARY KEY, title VARCHAR NOT NULL, body TEXT NOT NULL, published BOOLEAN NOT NULL DEFAULT FALSE );
-- migrations/20231027100000_create_posts_table/down.sql DROP TABLE posts;
-
마이그레이션 적용:
diesel migration run
이것은 마이그레이션을 데이터베이스에 적용합니다.
diesel-cli
를 사용한 오프라인 확인의 핵심 사용 사례는 라이브 데이터베이스에 연결하지 않고 src/schema.rs
가 마이그레이션 상태를 정확하게 반영하는지 확인해야 할 때 발생합니다. diesel print-schema
는 연결이 필요하지만, 선택한 백엔드(예: SQLite)에 대해 사용 가능한 경우 인메모리 데이터베이스 또는 CI에서 설정 및 해제되는 도커화된 테스트 데이터베이스와 통합할 수 있습니다. PostgreSQL 또는 MySQL의 경우 일반적으로 테스트 데이터베이스를 설정해야 합니다.
그러나 진정한 오프라인 파워는 schema.rs
덕분에 Rust 코드의 유형 안전성이 보장된다는 사실에서 비롯됩니다. schema.rs
를 한 번 생성할 수 있으며, 그런 다음 애플리케이션을 빌드하기만 하면 Rust 컴파일러가 schema.rs
가 Rust 구조체에 의해 표현되는 스키마와 더 이상 일치하지 않는 경우 발생하는 모든 불일치를 잡을 수 있습니다.
간단한 Diesel
애플리케이션을 생각해 보세요.
// src/schema.rs (diesel print-schema에 의해 생성되거나 마이그레이션에 따라 수동으로 유지 관리됨) diesel::table! { posts (id) { id -> Int4, title -> Varchar, body -> Text, published -> Bool, } } // src/models.rs use crate::schema::posts; use diesel::Queryable; #[derive(Queryable)] pub struct Post { pub id: i32, pub title: String, pub body: String, pub published: bool, } // src/main.rs use diesel::prelude::*; use diesel::pg::PgConnection; fn main() { let database_url = std::env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); let mut connection = PgConnection::establish(&database_url) .expect("Error connecting to database"); // 이 쿼리는 `src/schema.rs`에 대해 유형 검사를 받습니다. let results: Vec<crate::models::Post> = posts::table .filter(posts::published.eq(true)) .limit(5) .load(&mut connection) .expect("Error loading posts"); println!("Found {} published posts.", results.len()); }
posts
에 새 열을 추가하는 등 마이그레이션을 변경하고 src/schema.rs
를 업데이트하지 않으면, Rust 코드가 오래된 schema.rs
를 사용하는 경우 cargo build
또는 cargo check
가 실패할 가능성이 높습니다. 그런 다음 임시 데이터베이스에서 마이그레이션을 실행하고 src/schema.rs
를 다시 생성하여 스키마 변경을 다시 평가할 수 있습니다. 이 프로세스는 라이브 프로덕션 등급 데이터베이스 인스턴스를 항상 사용할 수 없을 때 일관성을 보장하는 데 도움이 됩니다.
CI/CD의 경우 diesel migration check
명령은 schema.rs
파일과 모든 마이그레이션이 실행될 때 생성될 수 있는 파일과 비교하여 불일치를 표시할 수 있습니다.
# 이 명령은 마이그레이션을 실행하기 위해 데이터베이스 연결이 필요하지만, # 인메모리 또는 임시 DB를 가리킬 수 있습니다. # 그리고 스키마를 비교합니다. diesel migration check
진정한 오프라인 확인은 schema.rs
가 원하는 스키마를 반영하도록 생성된 후 cargo-check
에서 비롯됩니다. schema.rs
에 설명된 대로 Rust 구조체가 데이터베이스와 상호 작용하는 방식과의 모든 불일치는 컴파일 오류로 이어집니다.
결론
sqlx-cli
와 diesel-cli
는 Rust에서 데이터베이스 마이그레이션 및 스키마를 관리하기 위한 강력한 솔루션을 모두 제공합니다. sqlx-cli
는 기존 데이터베이스 또는 자체 메타데이터에서 파생된 스키마에 대해 원시 SQL 쿼리를 컴파일 타임에 확인하는 데 뛰어나 SQLx 사용자를 위한 강력한 "오프라인" 쿼리 유효성 검사를 제공하는 반면, diesel-cli
는 ORM 접근 방식을 활용하여 Rust 코드가 유형 검사를 받는 schema.rs
파일을 생성합니다. 라이브 데이터베이스 연결을 영구적으로 사용할 수 없는 시나리오에서도 이러한 도구를 사용하여 스키마 및 마이그레이션을 관리하고 확인할 수 있는 기능은 개발 워크플로우를 크게 간소화하고 CI/CD 파이프라인을 강화하며 궁극적으로 더 안정적인 Rust 애플리케이션으로 이어집니다. 이러한 CLI 도구를 마스터하는 것은 데이터베이스 기반 애플리케이션을 구축하는 모든 Rust 개발자에게 필수적입니다.