Skip to main content
sqlx는 연결 풀(connection pool)과 마이그레이션(migrations) 기능이 내장된 비동기 순수 Rust SQL 툴킷입니다. 이 가이드에서는 sqlx-cli 마이그레이션으로 todos 테이블을 생성한 뒤, 간단한 axum 서버에서 생성, 조회, 수정, 삭제 쿼리를 처리합니다. 모든 연결에는 인증서를 완전히 검증(certificate verification)하는 TLS가 적용됩니다.

사전 요구 사항

  • Rust 1.94 이상. 이 가이드는 Rust 1.98.1, sqlx 0.9.0, sqlx-cli 0.9.0, axum 0.8.9 환경에서 테스트되었습니다.
  • ClickHouse Cloud 계정
  • 데이터베이스 생성에 사용할 psql. CREATE DATABASE SQL 문은 SQL 콘솔에서 실행할 수도 있습니다.

ClickHouse Managed Postgres 서비스 생성

ClickHouse Cloud 콘솔에서 New service를 클릭한 후 Postgres를 선택하십시오. 몇 분 내에 인스턴스가 준비됩니다. 자세한 단계별 안내는 빠른 시작 가이드를 참조하십시오.

연결 정보 확인하기

서비스의 왼쪽 사이드바에서 Connect를 클릭하십시오. Directly가 선택된 상태를 유지하고 Use SSL을 켜십시오. 이제 url 탭의 연결 URL이 sslmode=verify-full&sslrootcert=<service-name>-ca-certificate.pem으로 끝나며, 포트는 5432입니다. Download CA certificate를 클릭하십시오. 나중에 Settings → CA Certificate에서 다운로드할 수도 있습니다. 이 인증서는 인스턴스마다 고유하므로, 드라이버는 이를 사용해 실제로 해당 서버와 통신하고 있는지 검증할 수 있습니다. 이 가이드에서는 앱과 sqlx-cli 모두 포트 5432를 통해 Postgres에 직접 연결합니다. axum 서버는 장시간 실행되는 프로세스이고 PgPool이 자체 연결 풀을 관리하므로, Postgres 앞에 별도의 풀러를 둘 필요가 없습니다. 마이그레이션에는 항상 직접 연결을 사용해야 합니다. 앱 인스턴스를 여러 개 실행한다면 PgBouncer 사용하기를 참조하십시오.

프로젝트 설정하기

프로젝트를 생성한 후 Postgres 드라이버, Tokio 런타임, rustls TLS 백엔드를 활성화한 sqlx를 추가하고, axum과 몇 가지 보조 크레이트도 함께 추가하십시오:
동일한 TLS 백엔드를 사용하여 sqlx-cli를 설치하십시오:
macOS에서는 native-tls가 아닌 rustls를 사용하십시오ClickHouse Managed Postgres는 TLS 1.3만 허용합니다. macOS에서 native-tls 백엔드는 시스템의 Secure Transport 라이브러리를 사용하는데, 이 라이브러리는 TLS 1.3을 지원하지 않습니다. 별도 옵션 없이 cargo install sqlx-cli로 설치하면 native-tls가 사용되며, 이후 모든 명령어가 error occurred while attempting to establish a TLS connection: One or more parameters passed to a function were not valid.(또는 bad protocol version) 오류와 함께 실패합니다. tls-native-tls 기능으로 빌드한 앱도 마찬가지입니다. Linux에서는 native-tls가 OpenSSL을 사용하므로 연결은 되지만, ca-certificate.pem에서 첫 번째 인증서만 로드합니다. 반면 rustls는 모든 플랫폼에서 TLS 1.3을 지원하며 파일에 포함된 모든 인증서를 로드합니다.
기존 앱에 추가하시나요?cargo new는 건너뛰십시오. 프로젝트에서 위의 cargo add 및 cargo install 명령어를 실행한 다음 연결 구성으로 넘어가세요. 앱에서 SQLite와 함께 sqlx를 사용하고 있다면 다음 작업을 수행하세요.
  • Cargo.toml의 sqlx 기능에서 sqlite를 제거하고, SqlitePool을 PgPool로 대체하세요.
  • ? 플레이스홀더를 $1, $2 등으로 변경하세요.
  • 마이그레이션을 Postgres 구문으로 재작성하세요. 예를 들어 INTEGER PRIMARY KEY AUTOINCREMENT 대신 bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY를 사용합니다.

연결 구성

다운로드한 CA 인증서를 프로젝트 디렉터리로 옮긴 후 파일 이름을 ca-certificate.pem으로 변경하십시오. 애플리케이션에서 사용할 데이터베이스를 생성하십시오. <PASSWORD>와 호스트를 Connect 모달에 표시된 값으로 바꾸십시오.
프로젝트 루트에 .env 파일을 생성하거나 기존 .env 파일의 DATABASE_URL을 수정하여 guide_rust 데이터베이스를 가리키도록 설정하십시오. 애플리케이션과 sqlx-cli는 모두 이 파일에서 DATABASE_URL을 읽습니다.
.env
sqlx는 URL에서 sslmode와 sslrootcert를 읽습니다. verify-full을 사용하면 서버 인증서가 인스턴스의 CA로 서명되었는지, 그리고 호스트명과 일치하는지 검사합니다. sslrootcert 경로는 명령어를 실행하는 디렉터리를 기준으로 해석됩니다. .env 파일은 버전 관리 대상에서 제외하십시오. 또한 비밀번호에 @, /, # 같은 문자가 포함되어 있다면 퍼센트 인코딩하십시오.
항상 sslmode=verify-full을 설정하십시오sslmode를 지정하지 않으면 sqlx는 prefer를 사용합니다. 이 경우 연결은 암호화되지만, sslrootcert를 설정했더라도 모든 인증서를 허용합니다. verify-full을 사용하면 CA 인증서가 없거나 잘못된 경우 UnknownIssuer 오류가 발생하고, IP 주소처럼 인증서와 일치하지 않는 호스트를 사용하면 NotValidForNameContext 오류(sqlx-cli에서는 certificate not valid for name)가 발생합니다. sslrootcert 경로가 잘못되었으면 No such file or directory 오류가 발생합니다.

마이그레이션으로 테이블 생성하기

마이그레이션 파일을 생성하십시오:
migrations 디렉터리에 새로 생성된 파일을 열고 테이블 정의를 추가하십시오:
migrations/<timestamp>_create_todos.sql
마이그레이션을 적용합니다:
sqlx migrate run은 적용된 마이그레이션을 _sqlx_migrations 테이블에 기록하고, 이후 호출할 때는 새 마이그레이션만 실행합니다. migrations 디렉터리는 코드와 함께 커밋하십시오. 앱이 시작될 때 마이그레이션을 적용하려면 main에서 sqlx::migrate!().run(&pool).await?를 호출하면 됩니다. 단, 풀이 직접 연결을 사용해야 합니다.

API 빌드하기

src/main.rs의 내용을 라우트마다 쿼리를 하나씩 실행하는 간단한 axum 서버 코드로 대체하세요. 기존 애플리케이션에 적용하려면 main에서와 같은 방식으로 풀을 생성한 다음, Todo 구조체와 핸들러, 라우트를 기존 라우터에 추가하세요.
src/main.rs
값은 항상 .bind를 사용해 매개변수($1)로 전달되며, SQL 문자열에 직접 포맷팅해 넣지 않습니다. 고유한 SQL 문자열은 연결마다 한 번씩만 준비(prepare)되어 최대 100개까지 저장되는 SQL 문 캐시에 보관되며, sqlx는 캐시에서 SQL 문을 제거할 때 해당 SQL 문을 닫습니다.
컴파일 시점에 검사되는 쿼리이 가이드에서는 query_as 함수를 사용합니다. 이 함수는 실행 시점에 SQL을 검사하므로 cargo build 시 데이터베이스가 필요하지 않습니다. 반면 query! 및 query_as! 매크로는 컴파일 시점에 데이터베이스를 기준으로 SQL과 컬럼 타입을 검사합니다. 이 매크로는 빌드 중에 .env에서 DATABASE_URL을 읽으므로, 이 값은 직접 연결(direct connection)을 가리키도록 유지하십시오. CI 환경처럼 데이터베이스 없이 빌드해야 하는 경우에는 cargo sqlx prepare를 실행하고, 생성된 .sqlx 디렉터리를 커밋한 후 SQLX_OFFLINE=true를 설정하십시오.

실행 및 확인

서버를 시작합니다.
첫 번째 줄은 pg_stat_ssl에서 조회한 결과로, 세션이 암호화되어 있음을 확인할 수 있습니다. 두 번째 터미널에서 할 일 항목 2개를 생성하고, 첫 번째 항목을 완료로 표시한 후 두 번째 항목을 삭제하세요:
각 요청은 영향을 받은 행을 반환합니다. -w '\n' 옵션은 각 응답 뒤에 줄바꿈을 추가하는 역할만 합니다.
브라우저에서 http://localhost:3117/todos를 열어 남은 할 일 목록을 확인하세요:
콘솔에서 데이터를 확인하려면 서비스의 왼쪽 사이드바에서 SQL 콘솔을 열고 guide_rust와 public을 차례로 펼친 다음 todos 테이블을 클릭하세요. 그 옆에 있는 _sqlx_migrations 테이블에는 마이그레이션 이력이 저장되어 있습니다. todos 테이블에 남아 있는 행은 다음과 같습니다:

PgBouncer 사용하기

함께 제공되는 PgBouncer를 통해 앱을 연결하려면 Connect 모달에서 via PgBouncer를 선택하고, 앱의 DATABASE_URL에서 포트 6432를 사용하십시오. CA 인증서는 동일한 것을 그대로 사용할 수 있습니다. 예를 들어 sqlx-cli용 .env는 직접 연결 설정을 그대로 두고, 앱을 시작할 때 환경 변수를 지정하면 됩니다:
PgBouncer를 거치면 pg_stat_ssl은 PgBouncer 자체의 Postgres 연결 정보를 보고하므로, 확인 결과로 none이 출력됩니다. 하지만 애플리케이션과 PgBouncer 간의 연결은 여전히 검증됩니다. 잘못된 CA 인증서를 사용하면 마찬가지로 UnknownIssuer 오류가 발생하며 실패합니다. PgBouncer는 트랜잭션 풀링 모드(transaction pooling mode)로 실행되므로, 트랜잭션마다 서로 다른 Postgres 연결에서 실행될 수 있습니다. sqlx를 사용할 때는 다음 사항에 유의하십시오.
  • 연결하려면 main.rs와 같이 .extra_float_digits(None)을 지정해야 합니다. sqlx는 기본적으로 extra_float_digits를 시작 매개변수로 전송하는데, PgBouncer는 unsupported startup parameter: extra_float_digits 오류를 반환하며 연결을 거부합니다. 같은 이유로 PgConnectOptions::options도 unsupported startup parameter in options 오류로 실패합니다.
  • Prepared statement는 기본 설정 그대로 동작합니다. sqlx는 캐시에서 제거한 SQL 문을 프로토콜 수준의 Close 메시지로 해제하며, PgBouncer는 트랜잭션 내부에서도 이 메시지를 처리합니다. statement_cache_capacity(0)은 설정하지 마십시오. 설정할 필요가 없을 뿐 아니라, 캐시를 끄면 sqlx가 이름이 지정된 SQL 문을 계속 준비하면서도 이를 닫지 않습니다.
  • 트랜잭션 외부에서 실행되는 쿼리에는 .persistent(false)를 사용하지 마십시오. 이 경우 sqlx는 이름 없는 SQL 문을 준비한 뒤 두 번째 왕복 요청에서 실행하는데, PgBouncer가 이 요청을 다른 Postgres 연결로 보낼 수 있습니다. 그러면 쿼리가 unnamed prepared statement does not exist 오류로 실패하거나, 아무런 오류 없이 다른 클라이언트의 SQL 문을 실행해 잘못된 결과를 반환할 수 있습니다.
  • SET으로 지정한 세션 설정은 풀의 after_connect 후크에서 지정한 경우를 포함해 해당 연결에 유지되지 않으며, 다른 클라이언트로 누출될 수도 있습니다. 대신 트랜잭션 내부에서 SET LOCAL을 사용하십시오.
  • **마이그레이션(Migrations)**에는 직접 연결을 사용해야 합니다. sqlx-cli와 query! 매크로는 항상 extra_float_digits를 전송하므로 PgBouncer를 통해 연결할 수 없습니다. sqlx::migrate!는 세션 수준의 권고 잠금(pg_advisory_lock)을 획득하는데, PgBouncer를 거치면 잠금 해제 요청이 다른 Postgres 연결로 전달될 수 있습니다. 이렇게 되면 풀링된 연결에 잠금이 계속 남아 있게 되고, 이후의 마이그레이션은 직접 연결을 통한 경우라도 PgBouncer가 해당 연결을 닫을 때까지 멈춘 상태로 대기합니다.

다음 단계

  • 연결: 연결 문자열(connection string), PgBouncer, TLS
  • 설정: max_connections 등 Postgres 및 PgBouncer 매개변수
  • 읽기 레플리카: 두 번째 PgPool로 읽기 전용 쿼리를 레플리카에 전송
  • 보안: IP 액세스 목록 및 프라이빗 네트워킹
마지막 수정일 2026년 10월 14일