오프라인 스키마 관리: 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_tablesqlx-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 개발자에게 필수적입니다.