From b949ca766f6bde3b375a13298df08fdd182fb793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B8=B0=EB=AF=BC?= Date: Wed, 5 Aug 2026 17:49:10 +0900 Subject: [PATCH 1/6] =?UTF-8?q?docs:=20#97=20PostgreSQL=2017=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=20=ED=99=98=EA=B2=BD=20=EC=A0=84=ED=99=98=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=84=A4=EA=B3=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...postgresql17-pgvector-local-environment.md | 222 ++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 docs/design/Gimini-3-#97-postgresql17-pgvector-local-environment.md diff --git a/docs/design/Gimini-3-#97-postgresql17-pgvector-local-environment.md b/docs/design/Gimini-3-#97-postgresql17-pgvector-local-environment.md new file mode 100644 index 00000000..9a5bc0cd --- /dev/null +++ b/docs/design/Gimini-3-#97-postgresql17-pgvector-local-environment.md @@ -0,0 +1,222 @@ +# #97 PostgreSQL 17 및 pgvector 0.8.1 로컬 실행 환경 지원 추가 구현 + +## 1. 배경 + +현재 로컬 데이터베이스 실행 경로는 OpenSQL/PostgreSQL 14.6에 강하게 결합되어 있다. + +- `docker/opensql/Dockerfile`은 `tmaxopensql/postgres:14.6`을 기반으로 pgvector 0.8.0을 컴파일한다. +- `docker/opensql/init-and-start.sh`은 `/usr/pgsql-14`와 `/var/lib/pgsql/14/data`를 사용한다. +- `docker-compose.yml`은 `linux/amd64`를 강제하고 `opensql_data` 볼륨을 사용한다. +- Local/Test Datasource의 기본 `sslmode=require`는 SSL을 활성화하지 않은 로컬 컨테이너와 맞지 않는다. +- Claim 성능 Benchmark는 PostgreSQL 14.6을 실행 전제조건으로 고정한다. +- README는 더 이상 사용하는 로컬 기준이 아닌 OpenSQL 14.6 실행 절차를 안내한다. + +공식 OpenSQL 17.8 설치 환경은 Rocky Linux 9.7 x86-64 단일 서버다. macOS arm64 개발 환경에서 +공급사 설치 파일을 직접 실행하는 대신, 로컬 개발은 PostgreSQL 17 + pgvector 0.8.1로 표준화한다. +공식 OpenSQL 호환성은 별도 원격 환경에서 같은 검증 SQL과 애플리케이션 회귀 시나리오로 확인한다. + +## 2. 목표 + +1. macOS arm64와 x86-64 개발 환경에서 동일한 로컬 DB 구성을 실행한다. +2. PostgreSQL 17과 pgvector 0.8.1 버전을 재현 가능하게 고정한다. +3. 기존 JDBC, Flyway, `vector(1024)`, HNSW 검색 계약을 유지한다. +4. PostgreSQL 14 데이터 볼륨을 PostgreSQL 17에서 잘못 재사용하지 않는다. +5. 공급사 설치 파일, 라이선스, 다운로드 정보와 비밀정보가 Git 및 Docker Build Context에 유입되지 않게 한다. +6. 로컬 PostgreSQL과 공식 OpenSQL 원격 환경의 공통 검증 기준을 문서화한다. + +## 3. 환경 분리 결정 + +| 구분 | 로컬 개발 | 공식 최종 검증 | +| --- | --- | --- | +| DBMS | PostgreSQL 17 | OpenSQL 17.8 | +| Vector Extension | pgvector 0.8.1 | pgvector 0.8.1 | +| 실행 환경 | Docker Desktop, host architecture | Rocky Linux 9.7 x86-64 Single | +| 설치 파일 | 공개 pgvector Docker Image | 공급사 제공 파일, Repository 외부 보관 | +| 데이터 | 새 PostgreSQL 17 전용 Volume | 원격 검증용 격리 Database | +| 목적 | 반복 개발 및 회귀 테스트 | 공식 호환성 확인 | + +로컬 기본 이미지는 공개 공식 이미지 `pgvector/pgvector:0.8.1-pg17`을 사용한다. 이미지가 제공하는 +PostgreSQL 공식 Entry Point를 그대로 사용하므로 공급사 전용 시작 Script와 PostgreSQL 14 경로가 +필요하지 않다. + +## 4. 로컬 컨테이너 설계 + +### 4.1 PostgreSQL Service + +- `docker-compose.yml`에서 Custom Build 대신 `pgvector/pgvector:0.8.1-pg17`을 직접 사용한다. +- `platform: linux/amd64`를 제거해 Image Manifest가 Host Architecture를 선택하도록 한다. +- 기존 기본 접속 계약을 유지한다. + - Host: `localhost` + - Port: `55432` + - Database: `app` + - User: `app` + - Password: 환경변수 `DB_PASSWORD`, 로컬 기본값만 Compose에서 제공 +- Health Check는 PostgreSQL 표준 `pg_isready`를 사용한다. +- MinIO, Embedding Server, Ollama Service는 수정하지 않는다. + +### 4.2 Vector Extension 초기화 + +`docker/postgres/init/001-enable-vector.sql`을 `/docker-entrypoint-initdb.d/`에 Read-only로 +Mount한다. + +~~~sql +CREATE EXTENSION IF NOT EXISTS vector; +~~~ + +PostgreSQL 공식 Entry Point는 새 Data Directory를 초기화할 때 이 SQL을 대상 Database에서 실행한다. +따라서 Spring Boot가 기동해 Flyway V32를 적용하기 전에 `vector` Type과 HNSW Access Method가 +준비된다. + +### 4.3 데이터 볼륨 + +- PostgreSQL 17은 새 Named Volume `docgrid_postgres17_data`를 사용한다. +- Mount 경로는 `/var/lib/postgresql/data`다. +- 기존 `opensql_data`는 자동 삭제, Mount 또는 In-place Upgrade하지 않는다. +- 기존 데이터 이전이 필요하면 `pg_dump`/`pg_restore` 기반의 별도 작업으로 분리한다. + +새 Volume을 사용하는 이유는 PostgreSQL Major Version이 다른 Data Directory의 직접 재사용을 +차단하고, Rollback 시 기존 PostgreSQL 14 데이터를 보존하기 위해서다. + +## 5. 애플리케이션 연결 설계 + +### 5.1 Local Profile + +`application-local.yml`의 기본 `DB_SSLMODE`를 `disable`로 변경한다. 로컬 Docker Network는 +SSL을 활성화하지 않으므로 기본 실행이 실제 컨테이너 설정과 일치해야 한다. 환경변수 Override 계약은 +유지한다. + +### 5.2 Test Profile + +`application-test.yml`도 기본 `DB_SSLMODE=disable`을 사용한다. 기존 격리 Schema와 +`public` Search Path는 유지한다. + +~~~text +currentSchema={TEST_DB_SCHEMA},public +~~~ + +격리 Schema에는 Flyway Table과 애플리케이션 Table을 생성하고, `public`은 pgvector Extension이 +제공하는 `vector` Type과 Operator를 찾는 용도로 사용한다. + +### 5.3 Production Profile + +`application-prod.yml`의 `PROD_DB_URL`, `PROD_DB_USERNAME`, `PROD_DB_PASSWORD` 계약은 +변경하지 않는다. 운영 SSL Mode는 운영 URL에서 명시한다. + +## 6. Schema 및 검색 호환성 + +이번 작업은 Flyway Migration을 추가하거나 이미 적용된 Migration을 수정하지 않는다. + +- `embeddings.vector`: `vector(1024) NOT NULL` +- `search_queries.query_vector`: `vector(1024)` +- `embeddings.vector`: `vector_cosine_ops` 기반 HNSW Index +- Java Mapping: 기존 `float[]`과 PostgreSQL `PGobject` 변환 유지 +- 검색: 기존 `<=>` Cosine Distance Query 유지 + +PostgreSQL 17 및 pgvector 0.8.1에서 기존 계약이 그대로 동작하는지는 실제 Database를 사용해 검증한다. + +## 7. Benchmark 환경 가드 + +`EmbeddingJobClaimPerformanceBenchmark` 실행 전 다음을 검증한다. + +1. 전용 Test Schema 사용 +2. Benchmark 전용 PostgreSQL Application Name 사용 +3. `SHOW server_version` 결과가 17 계열 +4. `vector` Extension Version이 정확히 0.8.1 +5. Hikari Pool Size와 MXBean 준비 + +성능 합격 기준 자체는 변경하지 않는다. 환경 전환 후의 실제 결과는 새 Test Result 문서에 기록해 +이전 PostgreSQL 14.6 결과와 환경 Fingerprint를 구분한다. + +## 8. API 및 상태 계약 + +이 작업에서 REST API, 요청/응답 DTO, 인증·권한, Domain Entity 상태 전이는 변경하지 않는다. +DB Runtime과 개발 환경만 교체한다. + +## 9. 공급사 파일 및 보안 경계 + +- OpenSQL 설치 Archive, 압축 해제 비밀번호, 다운로드 URL과 라이선스 XML은 Repository 내부에 두지 않는다. +- 공급사 파일은 Repository 외부 접근 제한 디렉터리 또는 원격 Rocky Linux 서버에서만 관리한다. +- 공급사 파일을 Docker Build Context에 복사하거나 Mount하지 않는다. +- 실제 운영 DB Password와 라이선스 정보는 문서, Commit, Log와 Test Result에 기록하지 않는다. +- Repository 내부에서 공급사 파일 유입이 감지되면 작업을 중단하고 추적 여부부터 확인한다. + +## 10. 변경 대상 + +| 영역 | 변경 | +| --- | --- | +| 설계 | 이 문서 추가 | +| Container | `docker-compose.yml`, `docker/postgres/init/001-enable-vector.sql` | +| 기존 Container | `docker/opensql/`의 로컬 기본 구성 제거 | +| Application | `application-local.yml`, `application-test.yml` | +| Benchmark | `EmbeddingJobClaimPerformanceBenchmark` | +| 운영 문서 | `README.md`, `docs/local-db.md` | +| 검증 기록 | `docs/test-results/Gimini-3-#97-postgresql17-pgvector-local-environment.md` | + +과거 `docs/test-results/` 문서는 당시 실행 사실을 보존해야 하므로 소급 수정하지 않는다. + +## 11. 검증 계획 + +### 11.1 정적 검증 + +~~~bash +docker compose config +git status --short --ignored +rg '14\.6|pgsql-14|opensql_data|linux/amd64' docker-compose.yml docker README.md src/main src/test +~~~ + +### 11.2 Database 검증 + +~~~sql +SHOW server_version; +SELECT extversion FROM pg_extension WHERE extname = 'vector'; + +SELECT format_type(a.atttypid, a.atttypmod) +FROM pg_attribute a +JOIN pg_class c ON c.oid = a.attrelid +WHERE c.relname = 'embeddings' + AND a.attname = 'vector'; + +SELECT indexdef +FROM pg_indexes +WHERE tablename = 'embeddings' + AND indexdef ILIKE '%USING hnsw%'; +~~~ + +### 11.3 애플리케이션 회귀 검증 + +- `./gradlew test` +- 실제 PostgreSQL 기반 Claim 동시성 통합 테스트 +- 문서 인덱싱 완료·실패·Lease 복구·Worker Polling 통합 테스트 +- Vector 저장 및 검색 통합 테스트 +- Claim Benchmark 환경 사전검증과 Smoke 실행 +- Local Profile Application 기동 및 Hibernate `ddl-auto=validate` + +## 12. Rollback + +1. Application과 PostgreSQL 17 Container를 중지한다. +2. Compose와 Local/Test 설정 Commit을 Revert한다. +3. 기존 PostgreSQL 14 `opensql_data`는 삭제하지 않고 이전 구성에서 다시 Mount한다. +4. PostgreSQL 17 전용 Volume은 명시적인 사용자 확인 없이 삭제하지 않는다. +5. 두 환경 간 데이터 자동 변환을 시도하지 않는다. + +## 13. 커밋 분리 + +1. 상세 설계 문서 +2. PostgreSQL 17 + pgvector 0.8.1 Container Runtime +3. Local/Test 연결 및 Benchmark 환경 가드 +4. 로컬 실행 및 공식 OpenSQL 원격 검증 Runbook +5. 실제 회귀 검증 결과 + +## 14. 완료 조건 + +- PostgreSQL 17 + pgvector 0.8.1 로컬 DB가 Host Architecture 강제 없이 기동된다. +- PostgreSQL 14 볼륨을 재사용하거나 삭제하지 않는다. +- Vector Extension이 Flyway보다 먼저 준비된다. +- Flyway 전체 Migration과 Hibernate Schema Validation이 통과한다. +- `vector(1024)` 저장과 HNSW Index가 유지된다. +- Local/Test 연결 기본값이 로컬 컨테이너와 일치한다. +- Production Datasource 계약은 바뀌지 않는다. +- 공급사 파일과 비밀정보가 Git 및 Docker Build Context에 포함되지 않는다. +- 전체 테스트와 실제 PostgreSQL 회귀 검증 결과가 기록된다. + +Closes #97 From d9dcaef21e9b6937810e927ee3ad6b26dc7d623a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B8=B0=EB=AF=BC?= Date: Wed, 5 Aug 2026 17:51:26 +0900 Subject: [PATCH 2/6] =?UTF-8?q?chore:=20#97=20PostgreSQL=2017=20=EB=B0=8F?= =?UTF-8?q?=20pgvector=200.8.1=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20?= =?UTF-8?q?=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ docker-compose.yml | 19 ++++++++--------- docker/opensql/Dockerfile | 16 --------------- docker/opensql/init-and-start.sh | 24 ---------------------- docker/opensql/vars.yml | 6 ------ docker/postgres/init/001-enable-vector.sql | 2 ++ 6 files changed, 13 insertions(+), 57 deletions(-) delete mode 100644 docker/opensql/Dockerfile delete mode 100644 docker/opensql/init-and-start.sh delete mode 100644 docker/opensql/vars.yml create mode 100644 docker/postgres/init/001-enable-vector.sql diff --git a/.gitignore b/.gitignore index 896ec8b0..d94593bb 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ out/ ### Environment ### .env .env.properties + +# 공급사 설치 파일과 라이선스는 Repository 외부에 보관한다. +/.local-vendor/ diff --git a/docker-compose.yml b/docker-compose.yml index d7c89641..93df7910 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,7 @@ services: postgres: - build: - context: ./docker/opensql - dockerfile: Dockerfile - platform: linux/amd64 - container_name: local-opensql + image: pgvector/pgvector:0.8.1-pg17 + container_name: docgrid-postgres17 environment: POSTGRES_DB: ${DB_NAME:-app} POSTGRES_USER: ${DB_USER:-app} @@ -12,14 +9,14 @@ services: ports: - "${DB_PORT:-55432}:5432" volumes: - - opensql_data:/var/lib/pgsql - - ./docker/opensql/vars.yml:/tmp/settings/vars/vars.yml:ro + - postgres17-data:/var/lib/postgresql/data + - ./docker/postgres/init/001-enable-vector.sql:/docker-entrypoint-initdb.d/001-enable-vector.sql:ro healthcheck: - test: ["CMD-SHELL", "/usr/pgsql-14/bin/pg_isready -U app -d app"] + test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] interval: 10s timeout: 5s retries: 10 - start_period: 90s + start_period: 30s networks: - docgrid-local @@ -81,8 +78,8 @@ networks: docgrid-local: volumes: - opensql_data: - name: opensql_data + postgres17-data: + name: docgrid_postgres17_data minio-data: huggingface-cache: ollama-data: diff --git a/docker/opensql/Dockerfile b/docker/opensql/Dockerfile deleted file mode 100644 index 6a17a984..00000000 --- a/docker/opensql/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM tmaxopensql/postgres:14.6 - -USER root - -RUN curl -L https://github.com/pgvector/pgvector/archive/refs/tags/v0.8.0.tar.gz \ - -o /tmp/pgvector.tar.gz \ - && tar -xzf /tmp/pgvector.tar.gz -C /tmp \ - && cd /tmp/pgvector-0.8.0 \ - && make PG_CONFIG=/usr/pgsql-14/bin/pg_config \ - && make install PG_CONFIG=/usr/pgsql-14/bin/pg_config \ - && rm -rf /tmp/pgvector.tar.gz /tmp/pgvector-0.8.0 - -COPY init-and-start.sh /usr/local/bin/init-and-start.sh -RUN chmod +x /usr/local/bin/init-and-start.sh - -CMD ["/usr/local/bin/init-and-start.sh"] diff --git a/docker/opensql/init-and-start.sh b/docker/opensql/init-and-start.sh deleted file mode 100644 index 071c3db1..00000000 --- a/docker/opensql/init-and-start.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -set -e - -PGDATA="${PGDATA:-/var/lib/pgsql/14/data}" - -# Temporarily start postgres (local socket only) for initialization -pg_ctl start -D "$PGDATA" -l /tmp/pg_init.log -o "-h ''" -w - -# Create docgrid database if not exists -psql -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'docgrid'" | grep -q 1 \ - || psql -d postgres -c "CREATE DATABASE docgrid OWNER docgrid;" - -# Enable pgvector in docgrid database -psql -d docgrid -c "CREATE EXTENSION IF NOT EXISTS vector;" - -# Allow TCP connections from any host (needed for host-machine Spring Boot) -grep -qxF "host all all 0.0.0.0/0 scram-sha-256" "$PGDATA/pg_hba.conf" \ - || echo "host all all 0.0.0.0/0 scram-sha-256" >> "$PGDATA/pg_hba.conf" - -# Stop temp postgres cleanly before handing off -pg_ctl stop -D "$PGDATA" -m fast -w - -# Start postgres in foreground (replaces this process) -exec postgres diff --git a/docker/opensql/vars.yml b/docker/opensql/vars.yml deleted file mode 100644 index d5b9d1d7..00000000 --- a/docker/opensql/vars.yml +++ /dev/null @@ -1,6 +0,0 @@ -pg_owner: docgrid -pg_group: docgrid -pg_superuser: docgrid -pg_superuser_password: docgrid1234 -pg_database: postgres -use_system_user: false diff --git a/docker/postgres/init/001-enable-vector.sql b/docker/postgres/init/001-enable-vector.sql new file mode 100644 index 00000000..4eed64d3 --- /dev/null +++ b/docker/postgres/init/001-enable-vector.sql @@ -0,0 +1,2 @@ +-- Flyway V32가 vector(1024) 타입과 HNSW 인덱스를 만들기 전에 Extension을 준비한다. +CREATE EXTENSION IF NOT EXISTS vector; From 78143d0f77d0f9a2ef88ac23e051ba26f2eda381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B8=B0=EB=AF=BC?= Date: Wed, 5 Aug 2026 17:52:39 +0900 Subject: [PATCH 3/6] =?UTF-8?q?chore:=20#97=20=EB=A1=9C=EC=BB=AC=20Postgre?= =?UTF-8?q?SQL=20=EC=97=B0=EA=B2=B0=20=EA=B8=B0=EB=B3=B8=EA=B0=92=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 3 ++- src/main/resources/application-local.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 94bf81a9..f89142f5 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,14 @@ # .env.example - 이 파일을 복사해서 .env로 만들고 값을 채우세요 # cp .env.example .env -# OpenSQL-PG local DB (docker-compose 기본값) +# PostgreSQL 17 + pgvector 0.8.1 local DB (docker-compose 기본값) DB_HOST=localhost DB_PORT=55432 DB_NAME=app DB_USER=app DB_PASSWORD=local_password DB_SCHEMA=public +DB_SSLMODE=disable SPRING_PROFILES_ACTIVE=local # MinIO (docker-compose 기본값) diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 42167598..5b1d8efe 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -4,7 +4,7 @@ spring: on-profile: local datasource: - url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:app}?currentSchema=${DB_SCHEMA:public}&sslmode=${DB_SSLMODE:require} + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:app}?currentSchema=${DB_SCHEMA:public}&sslmode=${DB_SSLMODE:disable} username: ${DB_USER:app} password: ${DB_PASSWORD:local_password} driver-class-name: org.postgresql.Driver From 42540441187bc883392c298a95122b31db02790a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B8=B0=EB=AF=BC?= Date: Wed, 5 Aug 2026 17:54:07 +0900 Subject: [PATCH 4/6] =?UTF-8?q?test:=20#97=20PostgreSQL=2017=20Benchmark?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=20=EA=B8=B0=EC=A4=80=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 +- .../EmbeddingJobClaimPerformanceBenchmark.java | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 8b4d9990..09e288e1 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -4,7 +4,7 @@ spring: on-profile: test datasource: - url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:app}?currentSchema=${TEST_DB_SCHEMA:docgrid_test},public&sslmode=${DB_SSLMODE:require} + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:app}?currentSchema=${TEST_DB_SCHEMA:docgrid_test},public&sslmode=${DB_SSLMODE:disable} username: ${DB_USER:app} password: ${DB_PASSWORD:local_password} driver-class-name: org.postgresql.Driver diff --git a/src/test/java/com/opensource/docgrid/domain/embedding/benchmark/EmbeddingJobClaimPerformanceBenchmark.java b/src/test/java/com/opensource/docgrid/domain/embedding/benchmark/EmbeddingJobClaimPerformanceBenchmark.java index bafa4f2e..29ad5162 100644 --- a/src/test/java/com/opensource/docgrid/domain/embedding/benchmark/EmbeddingJobClaimPerformanceBenchmark.java +++ b/src/test/java/com/opensource/docgrid/domain/embedding/benchmark/EmbeddingJobClaimPerformanceBenchmark.java @@ -66,7 +66,7 @@ import lombok.extern.slf4j.Slf4j; /** - * 실제 OpenSQL에서 Embedding Job Claim 처리량과 자원 경합의 재현 가능한 기준선을 수집하는 Benchmark. + * PostgreSQL 17과 pgvector 0.8.1에서 Embedding Job Claim 처리량과 자원 경합의 기준선을 수집하는 Benchmark. * *

Spring이 관리하는 실제 Claim Service를 Worker 수별로 동시에 호출해 Transaction Commit을 포함한 * 호출 지연과 Queue 소진 시간을 측정한다. Production 코드를 변경하지 않고 Hikari Pool과 PostgreSQL @@ -90,6 +90,8 @@ class EmbeddingJobClaimPerformanceBenchmark { private static final String RESULT_PREFIX = "CLAIM_PERFORMANCE_RESULT"; private static final String MEDIAN_PREFIX = "CLAIM_PERFORMANCE_MEDIAN"; private static final String ENVIRONMENT_PREFIX = "CLAIM_PERFORMANCE_ENV"; + private static final String EXPECTED_POSTGRES_VERSION_PREFIX = "17."; + private static final String EXPECTED_PGVECTOR_VERSION = "0.8.1"; private static final int WARM_UP_WORKER_COUNT = 10; private static final int WARM_UP_JOB_COUNT = positiveIntegerProperty( @@ -218,12 +220,13 @@ private void validateBenchmarkEnvironment() throws SQLException { .as("PostgreSQL 대기 Session을 구분할 Worker Application Name이 적용되어야 한다") .isEqualTo(WORKER_APPLICATION_NAME); assertThat(jdbcTemplate.queryForObject("SHOW server_version", String.class)) - .as("설계 기준 OpenSQL PostgreSQL 14.6 환경이어야 한다") - .startsWith("14.6"); + .as("설계 기준 PostgreSQL 17 환경이어야 한다") + .startsWith(EXPECTED_POSTGRES_VERSION_PREFIX); assertThat(jdbcTemplate.queryForObject( "SELECT extversion FROM pg_extension WHERE extname = 'vector'", String.class - )).as("pgvector Extension이 준비되어야 한다").isNotBlank(); + )).as("설계 기준 pgvector 0.8.1 Extension이 준비되어야 한다") + .isEqualTo(EXPECTED_PGVECTOR_VERSION); HikariDataSource hikariDataSource = hikariDataSource(); assertThat(hikariDataSource.getMaximumPoolSize()).isEqualTo(DB_CONNECTION_POOL_SIZE); @@ -851,7 +854,7 @@ private Map collectEnvironmentFingerprint() { "SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid()", Boolean.class )); - fingerprint.put("configuredSslMode", environmentValue("DB_SSLMODE", "require")); + fingerprint.put("configuredSslMode", environmentValue("DB_SSLMODE", "disable")); fingerprint.put("javaVersion", System.getProperty("java.version")); fingerprint.put("jvm", ManagementFactory.getRuntimeMXBean().getVmName()); fingerprint.put("jvmMaxHeapBytes", Runtime.getRuntime().maxMemory()); From e6f6ef3bf967600129c32b871e426bf272e707a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B8=B0=EB=AF=BC?= Date: Wed, 5 Aug 2026 17:55:50 +0900 Subject: [PATCH 5/6] =?UTF-8?q?docs:=20#97=20PostgreSQL=2017=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=20=EB=B0=8F=20OpenSQL=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=A0=88=EC=B0=A8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 ++- docs/local-db.md | 213 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 docs/local-db.md diff --git a/README.md b/README.md index 991f309a..10a49cbe 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,22 @@ ## Local DB -로컬 개발 DB는 Docker Compose로 실행합니다. 기본 DB 이미지는 OpenSQL-PG 호환 이미지인 `tmaxopensql/postgres:14.6`입니다. +로컬 개발 DB는 Docker Compose로 실행합니다. 기본 이미지는 PostgreSQL 17과 pgvector 0.8.1을 +함께 제공하는 `pgvector/pgvector:0.8.1-pg17`입니다. ```bash cp .env.example .env -docker pull tmaxopensql/postgres:14.6 -docker compose up -d +docker compose pull postgres +docker compose up -d postgres docker compose ps ./gradlew bootRun --args='--spring.profiles.active=local' ``` -DB 기본 접속 정보는 `localhost:55432`, database `app`, user `app`, password `local_password`입니다. +DB 기본 접속 정보는 `localhost:55432`, database `app`, user `app`입니다. 로컬 기본 +`DB_SSLMODE`는 `disable`이며 실제 비밀번호와 운영 접속정보는 환경변수로 주입합니다. + +PostgreSQL 17은 `docgrid_postgres17_data` 전용 볼륨을 사용합니다. 기존 PostgreSQL 14 +`opensql_data` 볼륨을 재사용하거나 자동 삭제하지 않습니다. 자세한 내용은 [docs/local-db.md](docs/local-db.md)를 참고하세요. diff --git a/docs/local-db.md b/docs/local-db.md new file mode 100644 index 00000000..34d07564 --- /dev/null +++ b/docs/local-db.md @@ -0,0 +1,213 @@ +# PostgreSQL 17 + pgvector 0.8.1 로컬 DB 실행 Runbook + +## 1. 목적 + +로컬 개발과 자동화된 회귀 검증은 PostgreSQL 17 + pgvector 0.8.1을 사용한다. 공식 OpenSQL 17.8은 +Rocky Linux 9.7 x86-64 원격 환경에서 별도로 검증한다. + +이 Runbook은 다음 경계를 유지한다. + +- 로컬 Docker 실행에 공급사 설치 파일이나 라이선스를 사용하지 않는다. +- PostgreSQL 14의 기존 `opensql_data` 볼륨을 PostgreSQL 17에서 재사용하지 않는다. +- 실제 비밀번호, 다운로드 URL, 압축 해제 정보와 라이선스 내용은 문서와 Git에 기록하지 않는다. + +## 2. 사전 요구사항 + +- Docker Desktop 또는 Docker Engine + Compose Plugin +- Java 17 +- Repository Root에 `.env.example`을 복사해 만든 `.env` + +```bash +cp .env.example .env +``` + +`.env`는 Git 추적 대상이 아니다. 기본 개발값을 바꿔야 할 때만 로컬 파일에서 수정하고, 실제 운영 +비밀정보를 `.env.example`에 기록하지 않는다. + +## 3. 기본 환경 + +| 항목 | 기본값 | +| --- | --- | +| Image | `pgvector/pgvector:0.8.1-pg17` | +| Host | `localhost` | +| Port | `55432` | +| Database | `app` | +| User | `app` | +| SSL Mode | `disable` | +| Data Volume | `docgrid_postgres17_data` | + +Compose는 Host Architecture를 강제하지 않는다. 공식 Image Manifest가 macOS arm64와 x86-64에 맞는 +Image를 선택한다. + +## 4. 최초 기동 + +```bash +docker compose pull postgres +docker compose up -d postgres +docker compose ps postgres +docker compose logs postgres +``` + +PostgreSQL의 공식 Entry Point가 새 Data Volume을 초기화할 때 +`docker/postgres/init/001-enable-vector.sql`을 실행한다. 이 SQL은 Flyway보다 먼저 +`vector` Extension을 만든다. + +Health 상태가 `healthy`가 된 뒤 Application을 기동한다. + +```bash +./gradlew bootRun --args='--spring.profiles.active=local' +``` + +Spring Boot 기동 과정에서 Flyway 전체 Migration과 Hibernate `ddl-auto=validate`가 통과해야 한다. + +## 5. 버전과 Schema 확인 + +기본 Database/User를 사용할 때 다음 명령으로 실제 버전을 확인한다. + +```bash +docker compose exec postgres psql -U app -d app -c "SHOW server_version;" +docker compose exec postgres psql -U app -d app -c "SELECT extversion FROM pg_extension WHERE extname = 'vector';" +``` + +기대 결과: + +- `server_version`: 17 계열 +- `extversion`: `0.8.1` + +Flyway 적용 후 Vector Column과 HNSW Index를 확인한다. + +```bash +docker compose exec postgres psql -U app -d app -c "SELECT format_type(a.atttypid, a.atttypmod) FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid WHERE c.relname = 'embeddings' AND a.attname = 'vector';" +docker compose exec postgres psql -U app -d app -c "SELECT indexdef FROM pg_indexes WHERE tablename = 'embeddings' AND indexdef ILIKE '%USING hnsw%';" +``` + +기대 결과: + +- `embeddings.vector`: `vector(1024)` +- `vector_cosine_ops`를 사용하는 HNSW Index 존재 + +## 6. 테스트 + +전체 단위 테스트: + +```bash +./gradlew test +``` + +실제 PostgreSQL 통합 테스트를 실행할 때는 Test Profile이 격리 Schema와 `public` Search Path를 +사용한다. + +```bash +DB_HOST=localhost \ +DB_PORT=55432 \ +DB_NAME=app \ +DB_USER=app \ +DB_PASSWORD=local_password \ +DB_SSLMODE=disable \ +./gradlew test -Dgroups=integration +``` + +로컬 `.env`에서 접속값을 바꿨다면 명령의 값도 동일하게 맞춘다. 실제 비밀번호가 포함된 명령 출력은 +Test Result 문서에 복사하지 않는다. + +## 7. 정지와 재기동 + +Data Volume을 유지한 채 정지: + +```bash +docker compose stop postgres +``` + +재기동: + +```bash +docker compose up -d postgres +``` + +`docker compose down`도 기본적으로 Named Volume을 삭제하지 않는다. `docker compose down -v`는 +PostgreSQL뿐 아니라 다른 Service Volume까지 삭제할 수 있으므로 이 Runbook의 정상 정리 명령으로 +사용하지 않는다. + +## 8. PostgreSQL 14 볼륨과 Rollback + +- 기존 `opensql_data`는 Compose에서 더 이상 참조하지 않지만 자동 삭제하지 않는다. +- PostgreSQL 17은 `docgrid_postgres17_data`만 사용한다. +- PostgreSQL Major Version이 다른 Data Directory를 직접 Mount하지 않는다. +- 데이터 이전이 필요하면 별도 이슈에서 `pg_dump`와 `pg_restore` 절차를 검증한다. + +Rollback이 필요한 경우: + +1. Application과 PostgreSQL 17 Service를 중지한다. +2. PostgreSQL 17 전환 Commit을 Revert한다. +3. 이전 Compose 구성에서 기존 `opensql_data`를 다시 Mount한다. +4. 어떤 Named Volume도 사용자 확인 없이 삭제하지 않는다. + +## 9. 공식 OpenSQL 17.8 원격 검증 + +공식 OpenSQL 검증 환경: + +| 항목 | 조건 | +| --- | --- | +| OS | Rocky Linux 9.7 | +| Architecture | x86-64 | +| 구성 | Single | +| DBMS | OpenSQL 17.8 | +| Vector Extension | pgvector 0.8.1 | + +공급사 설치 Archive와 라이선스는 Repository 외부의 접근 제한 위치에서만 관리한다. 다운로드 URL, +압축 해제 비밀번호, 라이선스 XML과 원격 서버 인증정보를 Commit, Issue, Log 또는 Test Result에 +기록하지 않는다. + +원격 설치가 완료되면 다음 순서로 검증한다. + +1. `SHOW server_version`과 `SELECT version()` 확인 +2. `vector` Extension 0.8.1 확인 +3. 격리 Database에서 Flyway 전체 Migration 적용 +4. Hibernate Schema Validation 확인 +5. `vector(1024)` 저장·조회 확인 +6. HNSW Cosine 검색 확인 +7. Claim 동시성, 인덱싱 완료·실패·Lease 복구·Worker Polling 회귀 테스트 +8. 로컬 PostgreSQL 결과와 차이가 있으면 독립된 호환성 이슈로 기록 + +원격 접속정보는 환경변수로만 주입한다. + +```bash +DB_HOST= \ +DB_PORT= \ +DB_NAME= \ +DB_USER= \ +DB_PASSWORD= \ +DB_SSLMODE= \ +./gradlew test -Dgroups=integration +``` + +위 Placeholder를 실제 값으로 바꾼 명령은 Shell History, 문서와 CI Log에 남지 않도록 실행 환경의 +Secret 주입 기능을 사용한다. + +## 10. 문제 해결 + +### Port 충돌 + +`55432`가 사용 중이면 `.env`의 `DB_PORT`를 바꾸고 Application과 테스트 명령에도 같은 값을 +적용한다. + +### vector Extension 없음 + +`docker-entrypoint-initdb.d` Script는 새 Data Directory에서만 자동 실행된다. 새 PostgreSQL 17 +Volume인데 Extension이 없다면 초기화 Log를 확인한다. 기존 PostgreSQL 14 Volume을 Mount해 해결하려 +하지 않는다. + +### Flyway V32 실패 + +`vector` Extension Version과 Test Profile의 `currentSchema={test-schema},public` Search Path를 +먼저 확인한다. 이미 적용된 Migration 파일은 수정하지 않는다. + +### SSL 연결 실패 + +로컬 기본값은 `DB_SSLMODE=disable`이다. 공식 원격 환경에서는 서버 정책에 맞는 SSL Mode와 인증서를 +환경변수로 주입한다. + +## 11. 관련 문서 + +- [상세 설계](design/Gimini-3-#97-postgresql17-pgvector-local-environment.md) +- [GitHub Issue #97](https://github.com/DocGrid/backend/issues/97) From 2b81a985ea93e319b238e8dfd7aba66b66bfe4ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B8=B0=EB=AF=BC?= Date: Wed, 5 Aug 2026 18:05:11 +0900 Subject: [PATCH 6/6] =?UTF-8?q?test:=20#97=20PostgreSQL=2017=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20=EA=B2=80=EC=A6=9D=20=EA=B2=B0=EA=B3=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...postgresql17-pgvector-local-environment.md | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 docs/test-results/Gimini-3-#97-postgresql17-pgvector-local-environment.md diff --git a/docs/test-results/Gimini-3-#97-postgresql17-pgvector-local-environment.md b/docs/test-results/Gimini-3-#97-postgresql17-pgvector-local-environment.md new file mode 100644 index 00000000..d56cf055 --- /dev/null +++ b/docs/test-results/Gimini-3-#97-postgresql17-pgvector-local-environment.md @@ -0,0 +1,265 @@ +# #97 PostgreSQL 17 및 pgvector 0.8.1 로컬 실행 환경 검증 결과 + +## 1. 검증 개요 + +- 실행 일자: 2026-08-05 +- Branch: `feature/97` +- 검증 기준 Commit: `e6f6ef3bf967600129c32b871e426bf272e707a0` +- Host OS: macOS 26.5.2 +- Host Architecture: arm64 +- CPU: Apple M5, 10 Core +- Docker Engine: 29.4.1 +- Container Image: `pgvector/pgvector:0.8.1-pg17` +- Container Image Architecture: `linux/arm64` +- Database: PostgreSQL 17.8 +- Vector Extension: pgvector 0.8.1 +- Database SSL: 비활성화 + +공급사 설치 Archive, 다운로드 정보와 라이선스는 검증에 사용하지 않았고 Repository 및 Docker +Build Context에도 포함하지 않았다. + +## 2. 정적 검증 + +### 2.1 Compose 해석 + +```bash +docker compose --env-file /dev/null config +``` + +결과: 성공 + +- 기본 Port: `55432:5432` +- PostgreSQL Image: `pgvector/pgvector:0.8.1-pg17` +- Data Volume: `docgrid_postgres17_data:/var/lib/postgresql/data` +- 초기화 SQL: `docker/postgres/init/001-enable-vector.sql` +- Health Check: Container의 `POSTGRES_USER`, `POSTGRES_DB` 사용 +- `platform: linux/amd64` 강제 없음 +- PostgreSQL 14 전용 실행 경로 없음 + +### 2.2 Gradle 리소스와 Test Compile + +| 명령 | 결과 | +| --- | --- | +| `./gradlew processResources` | 성공 | +| `./gradlew testClasses` | 성공 | +| `./gradlew build` | 성공, 1초 | + +## 3. Docker 기동 및 데이터 격리 + +### 3.1 Image Pull 및 최초 기동 + +```bash +docker compose --env-file /dev/null pull postgres +docker compose --env-file /dev/null up -d postgres +``` + +결과: + +- `docgrid-postgres17` Container: `healthy` +- 새 `docgrid_postgres17_data` Volume 생성 +- 실제 Image Architecture: `linux/arm64` +- 중지 상태였던 같은 Compose Service의 기존 Container 객체는 새 PostgreSQL 17 Container로 재생성 + +### 3.2 기존 볼륨 보존 + +검증 후 확인된 관련 Named Volume: + +```text +docgrid_claim_performance_opensql_data +docgrid_postgres17_data +opensql_data +``` + +기존 `opensql_data`와 Claim 성능 측정용 PostgreSQL 14 Volume은 삭제되거나 PostgreSQL 17 Container에 +Mount되지 않았다. + +## 4. Database 정상 시나리오 + +### 4.1 버전과 Extension + +```text +server_version = 17.8 (Debian 17.8-1.pgdg12+1) +vector extversion = 0.8.1 +``` + +`001-enable-vector.sql`이 새 Data Directory 초기화 과정에서 실행돼 Flyway보다 먼저 +`vector` Extension을 준비했다. + +### 4.2 Flyway와 Hibernate + +Local Profile 기동 결과: + +- PostgreSQL 17.8 연결 성공 +- Flyway Migration 37개 검증 및 적용 성공 + - Versioned Migration: V1~V35 + - Repeatable Migration: 2개 +- 최종 Schema Version: V35 +- Hibernate `ddl-auto=validate` 성공 +- Tomcat 8080 기동 성공 +- Application 시작 시간: 3.271초 +- 기동 확인 후 Application Process만 `SIGINT`로 종료 + +### 4.3 Vector Column과 HNSW + +```text +embeddings.vector = vector(1024) +CREATE INDEX embeddings_vector_idx + ON public.embeddings + USING hnsw (vector vector_cosine_ops) +``` + +기존 `vector(1024)` 저장 계약과 Cosine Distance용 HNSW Index가 PostgreSQL 17 + pgvector 0.8.1에서도 +그대로 적용됐다. + +## 5. 전체 테스트 + +### 5.1 최초 실행의 설정 오류 + +첫 `./gradlew test` 실행은 558개 중 11개 Context 초기화 테스트가 실패했다. + +원인: + +```text +Could not resolve placeholder 'JWT_SECRET' +``` + +이 실패는 PostgreSQL, Flyway 또는 pgvector 호환성 오류가 아니라 필수 Test 환경변수를 주입하지 않은 +실행 설정 오류였다. Repository 설정 파일에 Secret을 추가하지 않고 실행 Process에만 임시 Test 값을 +주입해 재실행했다. + +### 5.2 설정 보정 후 전체 테스트 + +```bash +JWT_SECRET= \ +DB_HOST=localhost \ +DB_PORT=55432 \ +DB_NAME=app \ +DB_USER=app \ +DB_PASSWORD= \ +DB_SSLMODE=disable \ +./gradlew test +``` + +결과: + +```text +BUILD SUCCESSFUL in 19s +558 tests, 0 failed +``` + +기본 Test Task는 `benchmark`, `minio-integration`, `claim-concurrency` Tag를 제외한다. + +## 6. Claim 동시성 통합 테스트 + +```bash +./gradlew claimConcurrencyTest +``` + +결과: + +```text +BUILD SUCCESSFUL in 7s +``` + +실제 PostgreSQL 17에서 다중 Worker의 PENDING Job Claim 경쟁, 단일 Claim 소유권과 Claim Token +정합성 테스트가 통과했다. + +## 7. Claim Benchmark Smoke + +환경 전제조건과 Claim 흐름을 확인하기 위해 성능 기준선 전체 측정 대신 작은 Smoke Profile을 실행했다. + +조건: + +- Warm-up Job: 10 +- 측정 Job: Profile별 20 +- Worker: 1, 2 +- 반복: 1 +- Hikari Pool: 20 +- Sampling Interval: 10ms + +환경 가드 결과: + +- PostgreSQL Server Version: 17.8 +- pgvector Version: 0.8.1 +- Configured SSL Mode: `disable` +- Test Schema: `docgrid_embedding_job_claim_performance_test` +- Hikari Pool Size: 20 + +측정 결과: + +| Worker | TPS | Queue 소진 | P95 | Hikari 대기 | PostgreSQL Lock 대기 | Deadlock | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 170.13 | 117.56ms | 7.40ms | 0 | 0 | 0 | +| 2 | 353.34 | 56.60ms | 8.96ms | 0 | 0 | 0 | + +두 Profile 모두 다음 정합성을 만족했다. + +- Worker 오류 0 +- PENDING Job 0 +- 불완전 소유권 0 +- 중복 Claim Token 0 +- 잘못된 LOCKED Event 0 +- Rollback 0 +- Deadlock 0 + +결과: + +```text +BUILD SUCCESSFUL in 16s +``` + +이 수치는 환경 가드와 실행 경로를 확인하기 위한 작은 Smoke 결과이며, 정식 처리량 기준선으로 사용하지 +않는다. + +## 8. 오류 및 경계 시나리오 + +### 8.1 필수 Secret 누락 + +- 입력: `JWT_SECRET` 없이 전체 테스트 실행 +- 결과: Spring Context 초기화 단계에서 명확하게 실패 +- 조치: Repository에 Secret을 추가하지 않고 Test Process에만 임시 값 주입 +- 재실행: 전체 테스트 성공 + +### 8.2 Test Schema와 Local Schema 분리 + +전체 테스트는 `docgrid_test` 격리 Schema에 Flyway를 적용했다. Local Profile을 별도로 기동하기 +전에는 `public.flyway_schema_history`가 없는 것이 정상이며, Local 기동 후 `public`에 37개 +Migration이 적용됐다. + +### 8.3 PostgreSQL Major Version 볼륨 분리 + +- PostgreSQL 17 Container Mount: `docgrid_postgres17_data` +- 기존 PostgreSQL 14 `opensql_data`: 보존, 미Mount +- 자동 In-place Upgrade: 수행하지 않음 + +## 9. 미실행 및 후속 검증 + +- Rocky Linux 9.7 x86-64의 공식 OpenSQL 17.8 원격 검증은 아직 실행하지 않았다. +- Claim Benchmark 정식 기준선은 기본 Job 수와 반복 수로 별도 실행해야 한다. +- MinIO 실제 Object 경쟁 Test와 외부 Embedding Server E2E는 이번 DB Runtime 전환 범위에서 별도 + 실행하지 않았다. + +공식 OpenSQL 환경이 준비되면 [로컬 DB Runbook](../local-db.md)의 동일 SQL과 회귀 시나리오로 +호환성을 확인하고, 차이가 있으면 독립된 이슈로 기록한다. + +## 10. 최종 판정 + +| 완료 조건 | 결과 | +| --- | --- | +| PostgreSQL 17 기동 | 통과 | +| pgvector 0.8.1 | 통과 | +| Host Architecture 자동 선택 | 통과, linux/arm64 | +| 새 Volume 분리 | 통과 | +| 기존 PostgreSQL 14 Volume 보존 | 통과 | +| Flyway V1~V35 + Repeatable 2개 | 통과 | +| Hibernate Schema Validation | 통과 | +| `vector(1024)` | 통과 | +| HNSW `vector_cosine_ops` | 통과 | +| 전체 기본 테스트 | 통과, 558개 | +| Claim 동시성 테스트 | 통과 | +| Benchmark 환경 가드 및 Smoke | 통과 | +| 공식 OpenSQL 17.8 원격 검증 | 후속 작업 | + +로컬 PostgreSQL 17 + pgvector 0.8.1 전환 범위는 완료 조건을 충족했다. + +Closes #97