todos 리소스를 제공하는 과정을 다룹니다.
사전 요구 사항
- Elixir 1.15 이상 및 Erlang/OTP. 이 가이드는 Elixir 1.20.4, Erlang/OTP 29, Phoenix 1.8.15,
ecto_sql3.14.0, Postgrex 0.22.4 환경에서 테스트되었습니다. - ClickHouse Cloud 계정
ClickHouse Managed Postgres 서비스 생성
ClickHouse Cloud 콘솔에서 New service를 클릭한 후 Postgres를 선택하십시오. 몇 분 내에 인스턴스가 준비됩니다. 자세한 단계별 안내는 빠른 시작 가이드를 참조하십시오.연결 정보 확인하기
서비스를 열고 왼쪽 사이드바에서 Connect를 클릭하세요. 모달에는 사용자 이름, 비밀번호, 서버, 포트가 표시되며, Directly / via PgBouncer 토글이 있습니다. Directly를 선택한 상태에서 Use SSL을 켜세요. 비밀번호와 서버 호스트명을 복사한 다음 Download CA certificate를 클릭하세요. CA 인증서는 서비스마다 고유합니다. 나중에 Settings → CA Certificate에서 다운로드할 수도 있습니다. 직접 연결과 PgBouncer 중 어느 쪽을 사용해야 합니까? Phoenix 앱은 장기 실행 프로세스이며, Ecto는 자체 연결 풀(pool_size, 기본값 10)을 유지합니다. 이 가이드에서는 포트 5432로 Postgres에 직접 연결합니다. 여러 앱 인스턴스의 연결 수를 합쳐 Postgres 연결 한도에 가까워진다면 PgBouncer 사용하기를 참조하세요.
Phoenix 앱 생성
Phoenix 프로젝트 생성기를 설치한 후 앱을 생성하세요. 기본 데이터베이스로는 Postgres가 사용됩니다:데이터베이스 연결 구성
다운로드한 CA 인증서를 앱의priv 디렉터리로 옮기고 파일 이름을 ca-certificate.pem으로 변경하십시오. 다운로드한 파일의 이름은 서비스 이름을 기준으로 지정되며, 예를 들면 your-service-ca-certificate.pem과 같습니다:
priv 디렉터리의 파일도 릴리스에 포함됩니다.
config/dev.exs에서 Hello.Repo 구성의 username, password, hostname, database 줄을 url 및 ssl로 대체하세요.
config/dev.exs
ssl이 키워드 리스트(keyword list)이면 Postgrex는 인증서 검증(verify: :verify_peer)과 호스트명 검사를 활성화하고, URL의 호스트명을 TLS 서버 이름으로 전송합니다. 따라서 CA 인증서만 전달하면 됩니다. ssl: true로는 연결되지 않습니다. 이 설정은 운영 체제의 CA 저장소를 기준으로 서버 인증서를 검사하는데, 이 저장소에는 서비스의 CA가 포함되어 있지 않기 때문입니다.
새 데이터베이스 이름을 사용하여 DATABASE_URL이 서비스를 가리키도록 설정하십시오. 이 가이드에서는 guide_phoenix를 사용합니다:
mix ecto.create는 CREATE DATABASE를 실행할 때 동일한 TLS 설정을 사용하여 기본 postgres 데이터베이스에 연결합니다.
JSON 리소스 생성 및 마이그레이션 실행
todos에 사용할 컨텍스트, 스키마, 마이그레이션, JSON 컨트롤러를 생성합니다:lib/hello_web/router.ex에서 /api 스코프(scope)의 주석을 해제하고 여기에 리소스를 추가하세요:
lib/hello_web/router.ex
schema_migrations 테이블에 기록하므로, 이후에 다시 호출하면 새 마이그레이션만 실행합니다.
실행 및 확인
연결이 암호화되어 있는지 확인하십시오. 다음 쿼리는 현재 연결의pg_stat_ssl 정보를 조회합니다.
3116 포트에서 서버를 시작하세요:
PATCH 요청을 보내면 업데이트된 todo가 반환됩니다:
http://localhost:3116/api/todos를 열어 남은 할 일 목록을 확인하세요.
guide_phoenix와 public을 차례로 펼친 다음 todos 테이블을 클릭하세요. public 스키마에는 schema_migrations도 있습니다.
프로덕션 환경 구성
프로덕션 환경에서 Phoenix는config/runtime.exs에서 데이터베이스 설정을 읽습니다. if config_env() == :prod do 블록 안의 Hello.Repo 구성에서 # ssl: true, 줄을 CA 인증서 경로로 대체하십시오.
config/runtime.exs
Application.app_dir는 빌드 또는 릴리스 내부의 경로를 찾아 주므로, 앱이 어느 디렉터리에서 시작되든 해당 설정이 정상적으로 동작합니다. 배포 환경에서 DATABASE_URL을 설정하되, 이때도 쿼리 문자열(query string)은 포함하지 마십시오.
앱을 SQLite로 시작했다면 config/runtime.exs는 DATABASE_URL 대신 DATABASE_PATH를 읽습니다. database_path 변수와 해당 config :hello, Hello.Repo 호출을 다음 코드로 대체하십시오.
config/runtime.exs
--database sqlite3로 생성한 앱은 lib/<your_app>/application.ex에 있는 Ecto.Migrator 자식 프로세스를 통해 릴리스 시작 시 대기 중인 마이그레이션도 실행합니다. 이 동작은 Postgres에서도 그대로 유지됩니다.
PgBouncer 사용하기
애플리케이션 인스턴스 전체에서 여는 직접 연결 수가 너무 많다면, 대신 함께 제공되는 PgBouncer를 통해 연결하십시오. Connect 모달에서 via PgBouncer를 선택하고,DATABASE_URL에 포트 6432를 지정한 다음, config/dev.exs와 config/runtime.exs 모두에서 repo 구성에 prepare: :unnamed를 추가하십시오:
config/dev.exs
-
Prepared statement: Ecto는 기본적으로 모든 쿼리를 이름이 지정된(named) prepared statement로 실행하며, 함께 제공되는 PgBouncer는 이름이 지정된 SQL 문을 지원합니다. Postgrex는 SQL
DEALLOCATE를 사용하지 않고, 항상 프로토콜 수준의Close메시지로 SQL 문을 해제합니다. 그렇더라도 기본값인prepare: :named를 사용하면 PgBouncer를 거칠 때 다음 두 가지 문제가 발생합니다.- SQL 텍스트 길이가 약 4 KB인 쿼리가 시간 초과될 때까지 응답 없이 멈출 수 있습니다. 이 경우 호출 측은 약 1분 후
DBConnection.ConnectionError를 받습니다. - 마이그레이션으로 컬럼의 타입이 변경되면, 해당 컬럼을 읽는 쿼리가 앱을 재시작한 후에도 계속
cached plan must not change result type오류와 함께 실패합니다. PgBouncer가 서버에 있는 기존 prepared statement를 재사용하기 때문입니다.
prepare: :unnamed를 사용하면 Postgrex가 쿼리를 실행할 때마다 다시 준비하므로 두 문제가 모두 해결됩니다. 다만 쿼리마다 네트워크 왕복이 한 번씩 추가됩니다. - SQL 텍스트 길이가 약 4 KB인 쿼리가 시간 초과될 때까지 응답 없이 멈출 수 있습니다. 이 경우 호출 측은 약 1분 후
-
마이그레이션: Ecto의 기본 마이그레이션 잠금을 사용하면
mix ecto.migrate가 PgBouncer를 통해서도 정상적으로 동작합니다. 이 잠금은 트랜잭션 안에서schema_migrations테이블을 잠그는 방식입니다. PgBouncer 연결에는migration_lock: :pg_advisory_lock를 설정하지 마십시오. 세션 수준의 advisory lock이 다른 Postgres 연결에서 해제될 수 있으며, 이 경우failed to release advisory lock오류가 발생하고 잠금이 해제되지 않은 채 남습니다. 그 결과 포트5432에서의 실행을 포함한 이후의 모든 실행이 이 잠금이 풀리기를 기다리게 됩니다. 직접 연결로 마이그레이션을 실행하려면mix ecto.migrate명령에 포트5432를 사용하는DATABASE_URL을 설정하십시오. -
세션 상태:
after_connect콜백에서 실행하는SET search_path등의SETSQL 문은 현재 트랜잭션이 사용하는 Postgres 연결에만 적용됩니다. Postgrexparameters옵션에search_path를 지정하면unsupported startup parameter: search_path오류가 발생합니다.Postgrex.Notifications가 사용하는LISTEN과NOTIFY를 쓰려면 직접 연결이 필요합니다.
문제 해결
CLIENT ALERT: Fatal - Unknown CA:cacertfile이 해당 서비스의 CA 인증서를 가리키지 않거나ssl: true로 설정한 경우입니다. Settings → CA Certificate에서 인증서를 다시 다운로드하십시오. CA는 서비스마다 다릅니다.hostname_check_failed와 함께 발생하는CLIENT ALERT: Fatal - Bad Certificate:DATABASE_URL의 호스트가 서버 인증서와 일치하지 않는 경우입니다(예: IP 주소를 사용한 경우). Connect 모달에 표시된 호스트명을 사용하십시오.Invalid CA certificate file ... no such file or directory:cacertfile경로가 잘못된 경우입니다.priv/ca-certificate.pem파일이 존재하는지 확인하십시오.
다음 단계
- 연결: 연결 문자열(connection string), PgBouncer 및 TLS
- 설정:
max_connections등 Postgres 및 PgBouncer 매개변수 - 읽기 레플리카: Ecto 레플리카 리포지토리를 사용하여 읽기 요청을 레플리카로 라우팅
- ClickHouse로 동기화: 분석을 위해 Phoenix 테이블을 ClickHouse로 복제