사전 요구 사항
- .NET SDK 10. 이 가이드는 .NET SDK 10.0.401, EF Core 10.0.12,
Npgsql.EntityFrameworkCore.PostgreSQL10.0.3(Npgsql 10.0.3) 환경에서 테스트되었습니다. - ClickHouse Cloud 계정
- 데이터베이스 생성에 사용할
psql.CREATE DATABASESQL 문은 SQL 콘솔에서 실행할 수도 있습니다.
ClickHouse Managed Postgres 서비스 생성
ClickHouse Cloud 콘솔에서 New service를 클릭한 후 Postgres를 선택하십시오. 몇 분 내에 인스턴스가 준비됩니다. 단계별 안내는 빠른 시작 가이드를 참조하십시오.연결 정보 확인하기
서비스의 왼쪽 사이드바에서 Connect를 클릭하십시오. Directly는 선택된 상태로 두고 Use SSL을 켠 다음, 비밀번호와 서버 호스트명을 복사하십시오. 이어서 Download CA certificate를 클릭하십시오. 인증서는 나중에 Settings → CA Certificate에서도 다운로드할 수 있습니다. 이 인증서는 인스턴스마다 고유하므로 Npgsql은 이를 사용해 연결 대상이 실제 서버인지 검증할 수 있습니다. 이 가이드에서는 애플리케이션과 EF Core 마이그레이션 모두 포트5432를 통해 Postgres에 직접 연결합니다. ASP.NET Core 애플리케이션은 장기간 실행되는 프로세스이고 Npgsql이 이미 애플리케이션 내부에서 연결을 풀링하므로(기본값: 연결 문자열당 최대 100개) 별도의 풀러가 필요하지 않습니다. 애플리케이션 인스턴스를 여러 개 실행한다면 PgBouncer 사용하기를 참조하십시오.
Connect 모달에는 .NET 탭이 없으며, Npgsql은 url 탭의 postgresql:// URL을 지원하지 않습니다. 대신 Keyword=Value 형식의 연결 문자열을 사용합니다. URL의 각 값을 다음과 같이 Npgsql 키워드에 대응시키십시오.
프로젝트 설정
ASP.NET Core 프로젝트를 생성하고 Npgsql EF Core provider와 EF Core 디자인 타임 패키지를 추가한 후,dotnet ef 명령줄 도구를 설치하세요:
연결 구성
다운로드한 CA 인증서를 프로젝트 디렉터리로 옮긴 후 파일 이름을ca-certificate.pem으로 변경하십시오.
애플리케이션에서 사용할 데이터베이스를 생성하십시오. <PASSWORD>와 호스트를 Connect 모달에 표시된 값으로 바꾸십시오.
appsettings.json이나 소스 제어(source control)에 노출되지 않도록 Secret Manager를 사용하여 연결 문자열(connection string)을 저장하십시오:
Development 환경에서 사용자 시크릿(user secrets)을 읽으며, dotnet run과 dotnet ef는 기본적으로 이 환경을 사용합니다. 프로덕션 환경에서는 그 대신 환경 변수 ConnectionStrings__TodoDb를 설정하십시오.
SSL Mode=VerifyFull을 지정하면 Npgsql은 서버 인증서가 Root Certificate에 지정된 CA로 서명되었는지, 그리고 인증서가 호스트명과 일치하는지 확인합니다. Npgsql은 Root Certificate의 상대 경로를 현재 작업 디렉터리 기준으로 해석하며, dotnet run과 dotnet ef를 사용할 때는 프로젝트 디렉터리가 작업 디렉터리가 됩니다. 다른 위치에서 앱을 실행한다면 절대 경로를 사용하십시오.
CA가 잘못되었거나 누락된 경우, 또는 호스트가 인증서와 일치하지 않는 경우(예: IP 주소로 연결하는 경우) 연결이
Exception while performing SSL handshake 오류와 함께 실패하며, 이때 내부 오류는 The remote certificate was rejected by the provided RemoteCertificateValidationCallback입니다. Root Certificate 파일이 존재하지 않으면 내부 오류는 Could not find file입니다. 다음 사항에도 유의하십시오.- Npgsql의 기본값은
SSL Mode=Prefer이며,SSL Mode=Require역시Root Certificate를 설정하더라도 인증서를 검증하지 않습니다. 항상SSL Mode=VerifyFull을 설정하십시오. - Npgsql은
PGSSLROOTCERT와PGPASSWORD환경 변수는 읽지만PGHOST와PGSSLMODE는 읽지 않습니다. 따라서 Connect 모달의 env 탭에 있는 변수만으로는 충분하지 않습니다.
모델 정의
다음과 같이 엔터티 클래스를 정의하는Todo.cs 파일을 생성하십시오.
Todo.cs
DbContext를 정의하는 TodoDb.cs 파일을 생성하십시오:
TodoDb.cs
Program.cs의 내용을 다음 코드로 교체하세요. 이 코드는 Npgsql provider를 사용해 TodoDb를 등록하고, 작업마다 엔드포인트를 하나씩 매핑합니다. 기존 앱에서는 UseNpgsql을 사용하는 AddDbContext 호출만 추가하면 됩니다:
Program.cs
/db/tls 엔드포인트는 애플리케이션 자신의 데이터베이스 세션에 대해 pg_stat_ssl을 조회하므로, 연결이 암호화되어 있는지 확인할 수 있습니다.
스키마 생성
모델로부터 마이그레이션을 생성한 후 적용하십시오.dotnet ef migrations add는 마이그레이션을 Migrations 폴더에 생성하고, dotnet ef database update는 이를 적용한 뒤 __EFMigrationsHistory 테이블에 기록합니다. 처음 실행하면 EF Core는 이 테이블을 생성하기 전에 __EFMigrationsHistory에 대한 SELECT 실패 로그를 남기는데, 이는 무시해도 됩니다.
EF Core는 마이그레이션을 적용하기 전에 잠금을 획득하여, 두 배포가 동일한 데이터베이스를 동시에 마이그레이션하지 못하도록 합니다. Npgsql에서 이 잠금은 세션 수준의 권고 잠금(advisory lock)이 아니라 마이그레이션 트랜잭션 내에서 __EFMigrationsHistory에 거는 LOCK TABLE이므로, 트랜잭션이 끝나면 해제됩니다.
실행 및 확인
3115 포트에서 앱을 시작합니다:
DELETE 요청은 204 No Content를 반환하므로 해당 줄은 비어 있습니다.
앱의 데이터베이스 세션이 TLS를 사용하는지 확인하십시오.
guide_dotnet과 public을 차례로 펼친 다음 Todos 테이블을 클릭하십시오. EF Core는 클래스 이름과 속성 이름을 따옴표로 묶은 식별자로 사용하므로, 테이블 이름은 "Todos"이고 컬럼 이름은 "Id", "Title" 등입니다. 테이블에는 남은 행 하나가 들어 있습니다.
PgBouncer 사용
Npgsql은 애플리케이션 인스턴스당 최대 100개의 연결(Maximum Pool Size)을 엽니다. 여러 인스턴스의 연결 수를 합쳐 Postgres 연결 한도에 근접한다면, 함께 제공되는 PgBouncer를 통해 애플리케이션을 연결하십시오. Connect 모달에서 via PgBouncer를 선택하고, 연결 문자열(connection string)의 포트를 6432로 변경하십시오:
- Prepared statement: EF Core는 SQL 문을 준비(prepare)하지 않으며, Npgsql의 자동 준비 기능도 기본적으로 꺼져 있으므로(
Max Auto Prepare=0) 쿼리는 이름 없는 SQL 문을 사용합니다. 이름 없는 SQL 문은 PgBouncer를 거쳐도 정상적으로 동작하므로 이 상태를 유지하십시오. PgBouncer를 거치면Max Auto Prepare또는NpgsqlCommand.Prepare로 생성한 prepared statement가08P01: prepared statement did not exist오류와 함께 간헐적으로 실패하며(No Reset On Close=true로 방지할 수 있음), 마이그레이션으로 컬럼 타입이 변경된 뒤에는 새 연결에서도cached plan must not change result type오류와 함께 계속 실패합니다. prepared statement가 필요하다면 직접 연결하십시오. - 세션 상태(Session state):
search_path를 포함해SET으로 지정한 설정은 트랜잭션 간에 유지되지 않습니다. 대신 트랜잭션 내부에서SET LOCAL을 사용하십시오. PgBouncer는Search Path및Options연결 문자열 키워드를unsupported startup parameter오류와 함께 거부하지만,Timezone과Application Name은 정상적으로 동작합니다. - 마이그레이션(Migrations): 마이그레이션 잠금은 트랜잭션이 끝나면 함께 해제되는 테이블 잠금이므로
dotnet ef database update도 PgBouncer를 거쳐 정상적으로 동작합니다. - TLS 확인:
pg_stat_ssl은 PgBouncer와 Postgres 사이의 연결 정보를 나타내므로/db/tls는none을 반환합니다. 하지만 애플리케이션과 PgBouncer 간의 연결은 여전히 검증되며, 잘못된 CA 인증서를 사용하면 직접 연결할 때와 동일하게 실패합니다.
다음 단계
- 연결: 연결 문자열(connection string), PgBouncer, TLS
- 설정:
max_connections등 Postgres 및 PgBouncer 매개변수 - 읽기 레플리카(Read Replica): 읽기 전용 쿼리를 레플리카로 전송
- 보안: IP 액세스 목록 및 프라이빗 네트워킹