728x90
한줄요약 & 배경
- SQLx = ORM 아님. DSL 없이 그대로 SQL을 쓰되, 컴파일 타임에 실제 DB와 연결해 쿼리를 검증합니다.
- 비동기/순수 Rust(Postgres, MySQL/MariaDB 드라이버는 100% Rust; SQLite만 C 라이브러리 연동). 기본적으로 unsafe 금지로 안정성↑.
- 멀티 런타임 & TLS:
tokio / async-std / actix
+native-tls / rustls
호환. - 멀티 DB: PostgreSQL / MySQL / MariaDB / SQLite. (MSSQL은 0.7 이후 제거, 재작성 예정)
- 운영 기능: 풀링(
Pool
), 준비문 캐시, 비동기 Row 스트리밍, 중첩 트랜잭션(세이브포인트), AnyPool(런타임 드라이버 교체), LISTEN/NOTIFY(Postgres). - 라이선스: MIT/Apache-2.0 듀얼 — 오픈소스·상업 모두 OK.
- .env의 DATABASE_URL 지정만으로도 컴파일 타임 체크가 손쉬움.
결론: 대규모 서비스에서도 통할 만큼 기능·테스트·운영성이 강화되었고, ORM 없이 SQL을 직접 쓰고 싶은 팀에 강력 추천.
아키텍처·핵심 특징
구분 | 핵심 |
---|---|
비동기 | async/await 기반, 고동시성 I/O |
타입/안전 | query! , query_as! 로 컴파일 타임 SQL 검증 |
DB 지원 | Postgres / MySQL / MariaDB / SQLite |
런타임/TLS | tokio/async-std/actix + native-tls/rustls |
운영 기능 | Pool (풀링), 준비문 캐시, 스트리밍, 세이브포인트 |
알림 | Postgres LISTEN/NOTIFY |
드라이버 교체 | Any/AnyPool (URL 스킴으로 런타임 선택) |
안전성 | 기본 #![forbid(unsafe_code)] (SQLite 시 일부 예외) |
라이선스 | MIT / Apache-2.0 듀얼 |
설치 & 기능 플래그 선택 가이드
1. Cargo.toml (권장 예시: Tokio + rustls + Postgres)
[dependencies]
sqlx = { version = "0.8", features = [
"runtime-tokio",
"tls-rustls-aws-lc-rs",
"postgres",
"macros",
"migrate",
"uuid", "chrono", "json"
] }
- 런타임:
runtime-tokio
/runtime-async-std
- TLS:
tls-native-tls
또는tls-rustls-*
- DB:
postgres
/mysql
/sqlite
(※mssql
은 드라이버 재작성 대기) - 확장:
macros
(컴파일 타임 검증),migrate
(마이그),derive
(FromRow),uuid/chrono/json
등
2. .env로 컴파일 타임 체크 활성화
DATABASE_URL=postgres://postgres:password@localhost/test
Tip: 개발·CI에선 샘플 스키마 DB를 띄워 두고 DATABASE_URL만 맞추면 매크로가 실제 DB에 붙어 쿼리를 검증합니다.
빠른 시작 (Pool 권장)
use sqlx::postgres::PgPoolOptions;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect("postgres://postgres:password@localhost/test")
.await?;
// 런타임 준비문 예시
let row: (i64,) = sqlx::query_as("SELECT $1")
.bind(150_i64)
.fetch_one(&pool).await?;
assert_eq!(row.0, 150);
Ok(())
}
- Pool 중심 아키텍처: 연결 수 제어, 준비문 캐시, 재사용성 향상.
- (MySQL/MariaDB는 파라미터 플레이스홀더
?
, Postgres는$1
형식)
컴파일 타임 쿼리 검증 (핵심 가치)
1. query! (익명 레코드)
let rows = sqlx::query!(
r#"
SELECT country, COUNT(*) AS count
FROM users
WHERE organization = $1
GROUP BY country
"#,
organization
)
.fetch_all(&pool) // Vec<{ country: String, count: i64 }>
.await?;
2. query_as! (명명된 타입)
struct Country { country: String, count: i64 }
let rows = sqlx::query_as!(
Country,
r#"
SELECT country, COUNT(*) AS count
FROM users
WHERE organization = $1
GROUP BY country
"#,
organization
)
.fetch_all(&pool).await?;
- 장점: 바인딩 개수·타입·컬럼 호환성 컴파일 시 검증.
- 필수: 빌드 시
DATABASE_URL
로 실 DB 스키마에 붙어 확인. - 오프라인 모드:
sqlx prepare
로 메타를 캐시해 CI 속도·안정성 확보.
300x250
마이그레이션 & 오프라인 빌드
1. 마이그레이션 워크플로우
# 마이그 추가 (reversible 추천: -r)
sqlx migrate add -r add_users_table
# 적용
sqlx migrate run
# 컴파일타임 검증 메타 캐시 (오프라인 빌드)
sqlx prepare --merged
// 앱 시작 시 자동 적용(선택)
sqlx::migrate!("./migrations").run(&pool).await?;
2. 빌드 가속 (매크로 최적화)
[profile.dev.package.sqlx-macros]
opt-level = 3
운영 팁: DB 변경(PR)에는 DDL 영향·권한 변화·PII 영향·롤백 계획 기재를 필수화하세요.
운영·성능 베스트 프랙티스
- Pool 파라미터:
max_connections
,min_connections
,acquire_timeout
,idle_timeout
을 트래픽·DB 한도에 맞춤. - 준비문 캐시:
query()
경로는 연결별 자동 캐싱. DDL 이후 캐시 무효화·재준비 고려. - 대량삽입: Postgres는
COPY
, 배치 Insert + 트랜잭션 묶기. - 타임아웃 이중화: 앱(요청/쿼리) + DB(
statement_timeout
). - 관측성:
tracing
연동, 슬로우 쿼리 경보, 영향 행 수 로깅(민감정보 제외). - 페이징: 대량 테이블은 키셋 페이징(OFFSET 회피) 권장.
- Runtimes: tokio 표준화(서버 프레임워크 대부분 호환)로 팀 러닝커브↓.
보안 가이드 & 점검표
1. 인젝션 방지 (가장 중요)
- 항상 바인딩 사용. 문자열 결합/포매팅 금지.
// ✅ 안전 sqlx::query("SELECT * FROM users WHERE email = $1").bind(email); // ❌ 금지 // sql = format!("SELECT * FROM users WHERE email = '{}'", email);
- LIKE 검색 시
%
/_
이스케이프 정책 문서화(기본 유틸 제공).
2. 시크릿·자격증명
DATABASE_URL
등은 시크릿 매니저(Vault/Cloud Secret) 사용.- 로그/패닉 메시지에 절대 비밀번호/접속문자열/PII 노출 금지.
- 개발용
.env
는 로컬 한정, 리포지토리 커밋 금지.
3. TLS 강제
- 연결 문자열에서 TLS 필수: Postgres
sslmode=require
, MySQLssl-mode=REQUIRED
. - 내부 CA 사용 시 루트 번들 배포·로테이션 체계화.
4. 최소 권한 원칙
- 앱 계정은 DDL 금지, 필요한 DML/SELECT만.
- 마이그 계정 분리(DDL 허용).
- 스키마·테이블·뷰 단위 정밀 권한.
5. 감사·모니터링
- 로그인 실패/권한 오류/DDL 시도를 SIEM(Elastic/Wazuh 등)으로 수집.
- DML 감사가 필요하면 트리거+감사 테이블(변경 전/후·주체·트랜잭션ID).
- 애플리케이션 레벨에 요청ID/주체ID를 함께 기록.
6. 스키마 방어
CHECK
/ENUM
으로 입력 범위 제한.- 멀티테넌트는 RLS(Row Level Security, Postgres) 검토.
7. 에러/로깅 정책
- SQL 원문·파라미터 원값 로그 금지(특히 PII).
- 마스킹 규칙: 이메일
a***@ex.com
, 전화010-****-****
.
8. CI 보안 가드레일
query!/query_as!
사용률 지표화(메트릭/배지).sqlx prepare
없는 PR은 CI 실패.- 마이그 PR 템플릿에 보안 영향/롤백 체크리스트.
9. MSSQL 이용 조직 메모
- SQLx의 MSSQL 드라이버는 재작성 대기. 현재는 tiberius + 얇은 저장소 레이어로 운영 → SQLx MSSQL 안정화 후 마이그 경로 확보.
- 보안 점검표(요약)
- 바인딩 100% 사용
- 컴파일 타임 검증 매크로 적용
- DB 계정 최소권한/분리
- TLS 강제 및 CA 관리
- 로그 마스킹/민감정보 차단
- DDL/DCL/실패 로그인 SIEM 연동
- 슬로우 쿼리/타임아웃 가드
- 마이그 PR 보안 항목/롤백 계획
- 백업·복구 DR 리허설(스키마 버전 포함)
SQLx 생태계 & 유사 도구
- SeaORM / Welds: SQLx 위에서 동작하는 ORM. CRUD 생산성↑, 직접 SQL 컨트롤↓.
- SeaQuery: 타입 세이프 쿼리 빌더. 복잡한 동적 쿼리에 유용.
- SQLPage: SQL 중심 웹 프레임워크.
- Diesel: 강력한 타입세이프 DSL(주로 동기), 러닝커브↑.
- 결론: 명시적 SQL + 컴파일 검증을 원하면 SQLx가 가장 투명하고, 필요 시 ORM/빌더를 보완적으로 채택.
실전 코드 스니펫 모음
1. Pool & 타임아웃·TLS
use sqlx::postgres::PgPoolOptions;
use std::time::Duration;
let pool = PgPoolOptions::new()
.max_connections(20)
.min_connections(5)
.acquire_timeout(Duration::from_secs(3))
.idle_timeout(Duration::from_secs(300))
.connect("postgres://app:***@db.internal/prod?sslmode=require")
.await?;
2. 중첩 트랜잭션(세이브포인트)
let mut tx = pool.begin().await?;
sqlx::query!("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amt, src)
.execute(&mut *tx).await?;
let mut sp = tx.begin().await?; // savepoint
sqlx::query!("INSERT INTO ledger (src, dst, amt) VALUES ($1,$2,$3)", src, dst, amt)
.execute(&mut *sp).await?;
sp.commit().await?;
tx.commit().await?;
3. 스트리밍 + 매핑
use futures_util::TryStreamExt;
use sqlx::{Row, postgres::PgRow};
let mut rows = sqlx::query("SELECT id, email, is_active FROM users WHERE email LIKE $1")
.bind(format!("%{}%", keyword)) // ← LIKE는 바인딩 + 사전 이스케이프 유틸 권장
.fetch(&pool);
while let Some(row) = rows.try_next().await? {
let email: String = row.try_get("email")?;
// ...
}
또는
#[derive(sqlx::FromRow)]
struct User { id: i64, email: String, is_active: bool }
let users = sqlx::query_as::<_, User>(
"SELECT id, email, is_active FROM users WHERE is_active = $1"
)
.bind(true)
.fetch_all(&pool).await?;
4. 키셋 페이징(Postgres)
#[derive(sqlx::FromRow)]
struct User { id: i64, email: String }
let rows = sqlx::query_as!(
User,
r#"
SELECT id, email
FROM users
WHERE ($1::bigint IS NULL OR id < $1)
ORDER BY id DESC
LIMIT $2
"#,
last_id, limit as i64
).fetch_all(&pool).await?;
5. 마이그·오프라인 메타
sqlx migrate add -r add_idx_users_email
sqlx migrate run
sqlx prepare --merged
적용 로드맵
- 런타임 표준화(tokio + rustls) & DB 표준(Postgres 권장) 확정
- 템플릿 리포지토리:
sqlx
+migrate!
+tracing
+ CI에서sqlx prepare
강제 - PR 체크리스트: 바인딩/컴파일 검증/권한/PII/타임아웃/슬로우쿼리/인덱스
- 시크릿 매니저 도입(
DATABASE_URL
·CA 번들) - SIEM 연동: 로그인 실패/DDL/DCL/슬로우쿼리/타임아웃/권한오류
- 분기별 DR 리허설(스키마 버전 호환/롤백 확인)
최종 결론
- SQLx는 “직접 SQL”과 “컴파일 타임 안전성”을 동시에 잡는 선택지입니다.
- 운영·보안 필수 요소(풀링, TLS, 최소권한, 로그 마스킹, CI 가드레일, 감사)는 본 가이드대로 표준화하면 인젝션·시크릿 노출·성능 사고를 크게 줄일 수 있습니다.
- 필요 시 SeaORM/SeaQuery 등 생태계 도구로 보완하되, 핵심 쿼리는
query!
기반으로 명시적·감사 가능한 구조를 유지하세요.
728x90
그리드형(광고전용)
댓글