연결 최적화

Completed

데이터베이스 연결은 비용이 많이 드는 리소스입니다. 각 연결은 서버 메모리를 사용하고, 인증 오버헤드가 필요하며, 서버 제한에 대해 계산됩니다. 벡터 쿼리를 자주 수행하는 AI 애플리케이션의 경우 리소스를 소모하지 않고 높은 처리량을 달성하기 위해서는 효율적인 연결 관리가 필수적입니다.

비고

이 단원의 코드 예제에서는 Python(psycopg) 및 .NET(Npgsql)에 대한 연결 관리 패턴을 보여 줍니다. 이러한 라이브러리는 자주 업데이트됩니다. 현재 API 세부 정보는 psycopg 설명서 및 Npgsql 설명서를 참조하세요.

연결 오버헤드

새 PostgreSQL 연결을 만들려면 대기 시간을 추가하는 여러 단계가 포함됩니다.

  1. TCP 핸드셰이크: 네트워크 연결 설정(일반적으로 1~3회 왕복)
  2. TLS 협상: 연결 암호화(Azure Database for PostgreSQL에 필요)
  3. 인증: 자격 증명 확인(암호 또는 토큰 교환)
  4. 서버 프로세스 할당: PostgreSQL은 각 연결에 대한 백 엔드 프로세스를 생성합니다.
  5. 세션 초기화: 세션 매개 변수 설정 및 구성 로드

이 시퀀스는 네트워크 대기 시간 및 서버 부하에 따라 50-200밀리초가 걸립니다. 초당 수천 개의 요청을 처리하는 권장 엔진의 경우 요청당 새 연결을 만들면 실제 쿼리 실행보다 연결 설정에 더 많은 시간이 소요됩니다.

Azure Database for PostgreSQL은 컴퓨팅 계층에 따라 동시 연결을 제한합니다. 버스트 가능한 B1ms는 50개의 연결을 허용하고, 범용 2 vCore는 859개의 연결을 허용하고, 범용 4 vCore는 1,718개의 연결을 허용하고, 메모리 최적화 4개 vCore는 3,437개의 연결을 허용하며, 메모리 최적화 16개 vCore는 5,000개의 연결을 허용합니다. 이러한 제한을 초과하면 연결 오류가 발생합니다. 요청당 연결을 만드는 애플리케이션은 트래픽이 급증하는 동안 이러한 제한에 빠르게 도달할 수 있습니다.

PgBouncer를 사용하여 연결 풀링

PgBouncer는 애플리케이션과 PostgreSQL 사이에 있는 경량 연결 풀러입니다. 데이터베이스 연결 풀을 유지 관리하고 클라이언트 연결을 멀티플렉싱하여 필요한 실제 데이터베이스 연결 수를 크게 줄입니다.

Azure Database for PostgreSQL에는 범용 및 메모리 최적화 컴퓨팅 계층에 대한 기본 제공 PgBouncer 지원이 포함되어 있습니다. 버스트 가능 티어는 이 기능을 지원하지 않습니다. Azure Portal 또는 CLI를 통해 PgBouncer를 사용하도록 설정합니다. 사용하도록 설정되면 5432(직접 PostgreSQL 포트) 대신 포트 6432(PgBouncer 포트)를 통해 연결합니다. PgBouncer 연결 문자열은 .를 사용합니다 postgresql://user:password@myserver.postgres.database.azure.com:6432/mydb.

az postgres flexible-server parameter set \
    --resource-group myResourceGroup \
    --server-name myserver \
    --name pgbouncer.enabled \
    --value true

PgBouncer는 각각 서로 다른 장단점이 있는 세 가지 풀링 모드를 지원합니다. 세션 모드는 클라이언트가 전체 세션에 대한 서버 연결을 보유한다는 것을 의미합니다(연결 끊기 전까지). 이 모드는 모든 PostgreSQL 기능을 지원하지만 최소한의 연결 감소를 제공합니다. 트랜잭션 모드는 클라이언트가 트랜잭션 중에만 서버 연결을 보유한다는 것을 의미합니다. 트랜잭션 사이에 연결은 풀로 돌아갑니다. 이 모드는 대부분의 애플리케이션에서 잘 작동하며 연결 요구 사항을 크게 줄입니다. 명령어 모드는 클라이언트가 각 명령어에 대해서만 연결을 얻습니다. 이 모드는 최대 연결 감소를 제공하지만 복수 문 트랜잭션을 지원하지 않습니다. 벡터 검색 워크로드의 경우 트랜잭션 모드가 일반적으로 가장 적합합니다.

PgBouncer는 풀 동작, 연결 제한 및 시간 제한 처리를 제어하는 여러 매개 변수를 노출합니다. 버스트 트래픽 패턴이 있는 벡터 검색 워크로드의 경우 이러한 매개 변수를 튜닝하면 연결 가용성과 리소스 사용량의 균형을 맞추는 데 도움이 됩니다. 구성 pgbouncer.default_pool_size (동시성 요구 사항에 따라 20-50), pgbouncer.max_client_conn (트래픽이 많은 애플리케이션의 경우 5000개 이상), pgbouncer.pool_mode (트랜잭션) 및 pgbouncer.query_wait_timeout (30-120초)

트랜잭션 모드는 각 트랜잭션이 커밋되거나 롤백된 후 풀에 대한 연결을 반환합니다. 이는 여러 PostgreSQL 기능에 영향을 줍니다. 세션 변수는 트랜잭션 간에 다시 설정되므로 적용된 SET 설정은 트랜잭션 간에 유지되지 않습니다. 트랜잭션 내에서 SET LOCAL를 사용하거나 서버에서 기본값을 구성합니다. 명명된 준비된 문은 연결에 기반하여 준비된 문이 작동하지 않을 수 있습니다. 트랜잭션 모드에서는 다른 연결이 할당된 경우 한 트랜잭션에서 만든 준비된 문을 다음 트랜잭션에서 사용할 수 없습니다. 이러한 기능에는 영구 연결이 필요하고 트랜잭션 풀링과 호환되지 않으므로 LISTEN/NOTIFY가 작동하지 않습니다. 벡터 검색 애플리케이션의 경우 쿼리는 일반적으로 세션별 상태 없이 간단한 선택이므로 이러한 제한 사항은 거의 문제가되지 않습니다.

애플리케이션 수준 연결 풀링

PgBouncer 대신 애플리케이션이 연결 풀을 직접 관리할 수 있습니다. 이렇게 하면 연결 수명 주기를 더 세부적으로 제어할 수 있으며 애플리케이션 프레임워크와 통합됩니다.

psycopg에 대한 연결 풀링을 제공하는 패키지인 psycopg_pool입니다. 애플리케이션 수준 풀을 사용하면 연결 수명 주기, 유휴 시간 제한 동작 및 상태 검사를 제어할 수 있습니다. 또한 애플리케이션의 오류 처리 및 로깅과 자연스럽게 통합됩니다. PgBouncer와 결합하면 애플리케이션 풀은 로컬 연결 관리를 처리하고 PgBouncer는 서버 쪽 멀티플렉싱을 처리합니다. with pool.connection() 컨텍스트 관리자는 예외가 발생하더라도 블록이 종료되면 풀에 대한 연결을 자동으로 반환합니다.

from psycopg_pool import ConnectionPool

# Create a connection pool
pool = ConnectionPool(
    conninfo="postgresql://user:password@myserver.postgres.database.azure.com:6432/mydb",
    min_size=5,      # Minimum connections to maintain
    max_size=20,     # Maximum connections allowed
    max_idle=300,    # Close idle connections after 5 minutes
    max_lifetime=3600  # Recycle connections after 1 hour
)

# Use connections from the pool
def search_similar_products(query_embedding, limit=10):
    with pool.connection() as conn:
        with conn.cursor() as cur:
            cur.execute("""
                SELECT id, name, embedding <=> %s AS distance
                FROM products
                ORDER BY embedding <=> %s
                LIMIT %s
            """, (query_embedding, query_embedding, limit))
            return cur.fetchall()

Npgsql에는 기본적으로 사용하도록 설정된 기본 제공 연결 풀링이 포함되어 있으므로 별도의 패키지가 필요하지 않습니다. 풀은 연결 문자열에 지정한 매개 변수에 따라 연결 만들기, 재사용 및 삭제를 자동으로 관리합니다. 각 고유 연결 문자열은 자체 풀을 유지하므로 애플리케이션 전체에서 일관된 연결 문자열을 사용하면 효율적인 풀 사용률을 보장합니다. conn.Close()을 호출하거나 연결이 처리되면, 제거되지 않고 풀로 돌아갑니다. 와 같은 Minimum Pool Size=5;Maximum Pool Size=20;Connection Idle Lifetime=300;Connection Lifetime=3600연결 문자열 매개 변수를 통해 풀링을 구성합니다.

풀 크기는 성능 및 리소스 사용량 모두에 영향을 줍니다. 풀을 너무 작게 설정하면 요청이 사용 가능한 연결을 대기하여 대기 시간이 늘어나게 됩니다. 너무 큰 값을 설정하면 메모리가 낭비되고 데이터베이스 서버가 과부하가 될 수 있습니다. 적절한 크기는 트래픽 패턴, 쿼리 기간 및 데이터베이스를 공유하는 애플리케이션 인스턴스 수에 따라 달라집니다. 대기하지 않고 기준 트래픽을 처리할 수 있을 만큼 최소 크기를 크게 유지합니다. 데이터베이스에서 처리할 수 있는 최대 크기를 애플리케이션 인스턴스 수로 나눈 값으로 제한합니다. 애플리케이션 인스턴스가 10개이고 데이터베이스가 1,000개의 연결을 지원하는 경우, 인스턴스당 최대 연결 수를 100개로 설정하여 여유 공간을 남겨둡니다. 연결은 메모리 누수 가능성이나 오래된 캐시 계획을 보유할 수 있으므로 건강을 유지하기 위해 주기적으로(30-60분마다) 재활용합니다.

AI 워크로드에 대한 세션 관리

일부 벡터 쿼리는 서버 전체 기본값이 허용하는 것보다 더 많은 리소스를 쿼리에 할당하는 세션 수준 설정의 이점을 활용합니다.

큰 결과 집합을 정렬하는 벡터 유사성 쿼리는 증가된 work_mem이점을 누릴 수 있습니다. 를 사용하여 SET LOCAL work_mem = '256MB'특정 세션 또는 트랜잭션에 대해 설정합니다. SET LOCAL 는 현재 트랜잭션 내에서만 적용됩니다. 트랜잭션이 종료되면 설정이 기본값으로 되돌려지며 풀된 연결에 안전합니다.

서로 다른 정확도 기준을 가진 쿼리에 대해 hnsw.ef_search 또는 ivfflat.probes을 조정하십시오. 정확도가 중요한 쿼리에서는 더 높은 재현율을 위해 SET LOCAL hnsw.ef_search = 200를 사용하고, 대략적인 결과가 허용되는 더 빠른 쿼리에서는 SET LOCAL hnsw.ef_search = 20를 사용하십시오. 이 패턴을 사용하면 다른 쿼리에 영향을 주지 않고 특정 사용 사례에 따라 정확도와 속도의 균형을 맞출 수 있습니다.

효율적인 SDK 사용 패턴

연결 관리 외에도 데이터베이스 상호 작용을 구성하는 방법은 성능에 영향을 줍니다.

네트워크 왕복은 모든 데이터베이스 작업에 대기 시간을 추가합니다. 여러 데이터 조각이 필요한 경우, 단일 쿼리로 데이터를 가져오면 각 쿼리에 대한 네트워크 전송, 쿼리 구문 분석 및 결과 직렬화와 관련된 오버헤드가 제거됩니다. 여러 항목에 대한 포함을 검색하는 AI 애플리케이션의 경우 일괄 처리는 총 대기 시간을 수백 밀리초에서 한 자릿수로 줄일 수 있습니다. 개별 쿼리를 사용하여 여러 왕복하는 대신 단일 쿼리 WHERE id = ANY(%s) 를 사용하여 ID 목록을 전달합니다.

많은 수의 벡터를 로드하는 경우 PostgreSQL COPY 명령은 개별 INSERT 문보다 훨씬 빠릅니다. COPY 는 개별 SQL 문을 구문 분석하는 오버헤드를 우회하여 데이터를 이진 또는 텍스트 형식으로 테이블에 직접 스트리밍합니다. 일괄 처리 파이프라인 또는 초기 데이터 마이그레이션 COPY 에서 포함 데이터를 로드하는 경우 로드 시간을 시간에서 분으로 줄일 수 있습니다. COPY 는 초당 수십만 개의 행을 로드할 수 있지만 개별 삽입은 초당 수천 개의 행으로 제한됩니다.

애플리케이션이 작업을 병렬 처리할 수 있는 경우 비동기 데이터베이스 작업은 스레드를 차단하지 않고 여러 쿼리를 동시에 실행하여 처리량을 향상시킵니다. 이 패턴은 여러 벡터 컬렉션을 동시에 검색하거나 벡터 검색을 다른 데이터 검색과 결합해야 하는 AI 워크로드에 유용합니다. 비동기 풀은 풀 크기 제한을 유지하면서 동시 작업에서 연결을 효율적으로 관리합니다. AsyncConnectionPool와 asyncio.gather를 사용하여 psycopg_pool에서 여러 검색을 동시에 실행합니다.

연결 복원력

네트워크 문제, 서버 다시 시작 및 장애 조치(failover)는 데이터베이스 연결을 중단할 수 있습니다. 강력한 애플리케이션은 이러한 작업을 정상적으로 처리합니다.

클라우드 환경에서는 유지 관리 중에 네트워크 블립, 연결 재설정 및 짧은 서버 사용 불가와 같은 일시적인 오류가 불가피합니다. 지수 백오프를 사용하여 재시도 논리를 구현하면 즉각적인 재시도 시도로 서버를 압도하지 않고 애플리케이션이 이러한 임시 문제에서 정상적으로 복구할 수 있습니다. 임의 지터를 추가하여 여러 애플리케이션 인스턴스가 동시에 다시 시도되지 않도록 합니다. 예외를 OperationalError catch하여 대기 시간을 (2 ** attempt) + random.uniform(0, 1)로 계산하고, 최대 시도 횟수까지 재시도합니다.

시간 제한으로 인해 데이터베이스가 느리거나 연결할 수 없는 경우 애플리케이션이 무기한 대기할 수 없습니다. 연결 시간 제한은 새 연결을 설정할 때 대기하는 시간을 제한하고 문 시간 제한은 쿼리 실행 시간을 제한합니다. 벡터 검색 애플리케이션의 경우, 가장 느린 합법적인 쿼리를 수용할 수 있도록 설정하면서도 허용되는 지연 시간을 초과하는 쿼리에는 빠르게 실패하는 타임아웃을 선택합니다. 와 같은 connect_timeout=10options=-c statement_timeout=30000매개 변수를 사용하여 연결 문자열에서 시간 제한을 구성합니다. 벡터 쿼리의 경우 허용되는 가장 느린 쿼리를 수용하는 문 시간 제한을 설정합니다. 30초 시간 제한은 복잡한 벡터 검색에 적합합니다. 대화형 애플리케이션은 더 낮은 값을 사용할 수 있습니다.

모든 풀 연결이 사용 중이고 새 요청이 도착하면 풀은 요청을 큐에 추가(대기 시간 추가)하거나 즉시 거부해야 합니다. 어느 옵션도 이상적이지 않으므로 풀 사용률을 모니터링하면 고갈이 자주 발생하기 전에 크기를 조정하는 데 도움이 됩니다. 자원이 고갈될 경우, 명확한 오류 메시지를 반환하면 클라이언트가 비예측 가능하게 타임아웃되는 것보다는 자체 재시도 논리를 구현하는 데 도움이 됩니다. PoolTimeout 예외를 {"error": "Service temporarily busy, please retry"}와 같은 우아한 오류를 반환하여 처리합니다. 고갈이 자주 발생하는 경우 풀 사용률을 모니터링하고 크기를 조정합니다.

추가 리소스