sqlx-cli 마이그레이션으로 todos 테이블을 생성한 뒤, 간단한 axum 서버에서 생성, 조회, 수정, 삭제 쿼리를 처리합니다. 모든 연결에는 인증서를 완전히 검증(certificate verification)하는 TLS가 적용됩니다.
사전 요구 사항
- Rust 1.94 이상. 이 가이드는 Rust 1.98.1,
sqlx0.9.0,sqlx-cli0.9.0,axum0.8.9 환경에서 테스트되었습니다. - ClickHouse Cloud 계정
- 데이터베이스 생성에 사용할
psql.CREATE DATABASESQL 문은 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과 몇 가지 보조 크레이트도 함께 추가하십시오:
sqlx-cli를 설치하십시오:
연결 구성
다운로드한 CA 인증서를 프로젝트 디렉터리로 옮긴 후 파일 이름을ca-certificate.pem으로 변경하십시오.
애플리케이션에서 사용할 데이터베이스를 생성하십시오. <PASSWORD>와 호스트를 Connect 모달에 표시된 값으로 바꾸십시오.
.env 파일을 생성하거나 기존 .env 파일의 DATABASE_URL을 수정하여 guide_rust 데이터베이스를 가리키도록 설정하십시오. 애플리케이션과 sqlx-cli는 모두 이 파일에서 DATABASE_URL을 읽습니다.
.env
sslmode와 sslrootcert를 읽습니다. verify-full을 사용하면 서버 인증서가 인스턴스의 CA로 서명되었는지, 그리고 호스트명과 일치하는지 검사합니다. sslrootcert 경로는 명령어를 실행하는 디렉터리를 기준으로 해석됩니다. .env 파일은 버전 관리 대상에서 제외하십시오. 또한 비밀번호에 @, /, # 같은 문자가 포함되어 있다면 퍼센트 인코딩하십시오.
마이그레이션으로 테이블 생성하기
마이그레이션 파일을 생성하십시오: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 문을 닫습니다.
실행 및 확인
서버를 시작합니다.pg_stat_ssl에서 조회한 결과로, 세션이 암호화되어 있음을 확인할 수 있습니다. 두 번째 터미널에서 할 일 항목 2개를 생성하고, 첫 번째 항목을 완료로 표시한 후 두 번째 항목을 삭제하세요:
-w '\n' 옵션은 각 응답 뒤에 줄바꿈을 추가하는 역할만 합니다.
http://localhost:3117/todos를 열어 남은 할 일 목록을 확인하세요:
guide_rust와 public을 차례로 펼친 다음 todos 테이블을 클릭하세요. 그 옆에 있는 _sqlx_migrations 테이블에는 마이그레이션 이력이 저장되어 있습니다. todos 테이블에 남아 있는 행은 다음과 같습니다:
PgBouncer 사용하기
함께 제공되는 PgBouncer를 통해 앱을 연결하려면 Connect 모달에서 via PgBouncer를 선택하고, 앱의DATABASE_URL에서 포트 6432를 사용하십시오. CA 인증서는 동일한 것을 그대로 사용할 수 있습니다. 예를 들어 sqlx-cli용 .env는 직접 연결 설정을 그대로 두고, 앱을 시작할 때 환경 변수를 지정하면 됩니다:
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가 해당 연결을 닫을 때까지 멈춘 상태로 대기합니다.