13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] PostgreSQL

  • [AI] pgvector vs Weaviate: RAG를 위한 벡터 DB 선택 가이드

    [AI] pgvector vs Weaviate: RAG를 위한 벡터 DB 선택 가이드

    pgvector vs Weaviate: RAG를 위한 벡터 DB 선택 가이드

    RAG 애플리케이션을 만들다 보면 결국 부딪히는 질문이 하나 있습니다. pgvector Weaviate 비교를 해보면 도대체 뭐가 더 맞는 선택이냐는 거죠. 저도 홈랩에서 이것저것 붙여 보면서, 처음엔 그냥 PostgreSQL에 확장만 올리면 끝 아닌가 싶었는데요. 실제로 써보니까 운영 방식, 검색 품질 튜닝 포인트, 개발 편의성이 꽤 다르더라고요. 특히 벡터 데이터베이스를 RAG 아키텍처에 넣는 순간, 단순히 저장소 하나 고르는 문제가 아니라 검색 파이프라인 전체 성격이 바뀝니다.

    이번 글에서는 실무 관점에서 pgvector와 Weaviate를 비교해보겠습니다. 제품 소개만 나열하는 글 말고, 제가 직접 구성할 때 어떤 기준으로 판단하는지, 어디서 삽질했는지, 그리고 어떤 팀에 어떤 선택이 더 현실적인지까지 풀어보겠습니다. 혹시 지금 LLM 임베딩(벡터 표현) 저장소를 정해야 하는 상황이라면, 이 글이 꽤 빠르게 방향을 잡는 데 도움이 될 겁니다.

    pgvector Weaviate 비교가 포함된 RAG 아키텍처 개요 다이어그램

    RAG 아키텍처에서 임베딩 생성, 벡터 저장, 검색, 재정렬, LLM 응답 생성까지의 흐름을 한눈에 보여주는 개요 이미지입니다.

    1. 왜 벡터 DB 선택이 RAG 성능을 좌우할까

    많은 분들이 처음에는 모델부터 고르십니다. 어떤 임베딩 모델을 쓸지, 어떤 LLM을 붙일지부터 보게 되거든요. 근데 실제로는 검색 품질과 운영 복잡도가 먼저 발목을 잡는 경우가 많습니다. 이유는 간단합니다.

    • 문서 chunking(분할) 방식이 검색 결과에 직접 영향을 줍니다.
    • 메타데이터 필터링이 약하면 엉뚱한 문서가 섞입니다.
    • 색인 방식이 맞지 않으면 검색 지연 시간이 튑니다.
    • 운영팀이 익숙하지 않은 저장소를 고르면 장애 대응이 느려집니다.

    제가 직접 해보니, RAG는 모델이 다 해주는 구조가 아니더라고요. 오히려 검색기(Retriever)를 얼마나 안정적으로 운영하느냐가 체감 품질을 크게 좌우했습니다. 여기서 pgvector는 기존 PostgreSQL 생태계를 활용한다는 강점이 있고, Weaviate는 아예 벡터 검색 중심으로 설계된 제품이라는 차이가 있습니다.

    2. 핵심 개념 정리: pgvector와 Weaviate를 쉽게 말해보면

    쉽게 말해 보겠습니다.

    pgvector는 PostgreSQL 안에 벡터 기능을 넣는 방식입니다. 이미 PostgreSQL을 쓰고 있다면, 익숙한 테이블과 SQL 위에 벡터 검색을 얹는 느낌입니다. 즉, 관계형 데이터와 임베딩 데이터를 한곳에서 다루기 좋습니다.

    Weaviate는 처음부터 벡터 검색을 중심으로 설계된 오픈소스 벡터 DB입니다. 문서 객체, 벡터, 메타데이터, 검색 API가 비교적 자연스럽게 묶여 있습니다. 그래서 애플리케이션 입장에서는 “벡터 검색용 서비스”처럼 접근하기 편하죠.

    항목 pgvector Weaviate
    기본 성격 PostgreSQL 확장 전용 벡터 데이터베이스
    쿼리 방식 SQL 중심 API 중심
    메타데이터 활용 관계형 모델과 결합이 쉬움 객체 기반 검색과 필터링이 편함
    운영 난이도 DBA 친화적 벡터 검색 기능은 풍부하지만 별도 운영 필요
    적합한 상황 기존 PostgreSQL 스택 유지 벡터 검색 중심 서비스 구축

    여기서 중요한 포인트가 하나 있습니다. pgvector가 단순하고, Weaviate가 고급형이다 이렇게 이분법으로 보면 틀립니다. 실제로는 데이터 모델과 조직 역량에 따라 유불리가 갈립니다. SQL로 조인 많이 하는 환경에서는 pgvector가 훨씬 편할 수 있고, 하이브리드 검색(키워드+벡터 결합 검색)을 적극적으로 쓰려면 Weaviate 쪽이 더 자연스러운 경우가 있습니다.

    3. 어떤 기준으로 골라야 하나: 실무형 비교 체크리스트

    제가 프로젝트 초기에 꼭 보는 기준은 아래 정도입니다.

    1. 기존 데이터가 어디에 있나
      이미 PostgreSQL에 사용자, 문서, 권한, 조직 구조가 들어 있다면 pgvector가 꽤 매력적입니다.
    2. 검색 API를 애플리케이션에서 어떻게 다룰 건가
      SQL로 끝내고 싶으면 pgvector가 편하고, 벡터 검색 서비스 자체를 별도 계층으로 두려면 Weaviate가 어울립니다.
    3. 필터링과 스키마 변경이 얼마나 잦은가
      RAG는 생각보다 메타데이터 조건이 자주 바뀝니다. 문서 타입, 팀, 권한, 작성일 같은 조건이 붙거든요.
    4. 팀이 무엇에 익숙한가
      이거 진짜 큽니다. 낯선 저장소 하나 더 늘어나는 순간 관제, 백업, 장애 대응도 같이 늘어납니다.

    정리하면 이렇습니다.

    • pgvector는 “기존 데이터베이스와 붙어 살기 좋은 선택”입니다.
    • Weaviate는 “벡터 검색을 중심으로 더 빠르게 기능을 확장하기 좋은 선택”입니다.

    4. 실전 구현 1: pgvector로 최소 RAG 저장소 만들기

    이제 손에 잡히게 구성해보겠습니다. 먼저 pgvector입니다. 저는 로컬 테스트할 때 보통 Docker Compose로 PostgreSQL을 띄우고 확장을 활성화합니다. 복잡하게 시작하면 금방 지치거든요.

    4-1. PostgreSQL + pgvector 실행

    services:
      postgres:
        image: pgvector/pgvector:pg16
        container_name: rag-postgres
        environment:
          POSTGRES_USER: rag
          POSTGRES_PASSWORD: ragpass
          POSTGRES_DB: ragdb
        ports:
          - "5432:5432"
        volumes:
          - pgdata:/var/lib/postgresql/data
    
    volumes:
      pgdata:
    

    실행은 간단합니다.

    docker compose up -d

    4-2. 확장과 테이블 생성

    CREATE EXTENSION IF NOT EXISTS vector;
    
    CREATE TABLE documents (
      id BIGSERIAL PRIMARY KEY,
      source TEXT NOT NULL,
      title TEXT NOT NULL,
      content TEXT NOT NULL,
      metadata JSONB DEFAULT '{}'::jsonb,
      embedding VECTOR(1536)
    );
    
    CREATE INDEX idx_documents_metadata ON documents USING GIN (metadata);
    

    여기서 <code>VECTOR(1536) 같은 차원 수는 쓰는 임베딩 모델 차원에 맞춰야 합니다. 저도 처음엔 모델 바꾸고 차원 안 맞아서 에러를 꽤 봤습니다. 이 부분은 진짜 자주 실수합니다.

    4-3. 유사도 검색 쿼리

    SELECT id, title, source, content
    FROM documents
    WHERE metadata ->> 'team' = 'platform'
    ORDER BY embedding <-> '[0.01, 0.02, 0.03]'::vector
    LIMIT 5;
    

    <-> 연산자는 거리 계산에 사용합니다. 실제 서비스에서는 애플리케이션에서 쿼리 임베딩을 만든 뒤 바인딩해서 넣는 방식이 일반적입니다.

    4-4. Python으로 적재하기

    import json
    import psycopg
    
    rows = [
        {
            "source": "runbook-001",
            "title": "PostgreSQL vacuum note",
            "content": "Autovacuum tuning and maintenance checklist",
            "metadata": {"team": "platform", "type": "runbook"},
            "embedding": [0.01, 0.02, 0.03]
        }
    ]
    
    conn = psycopg.connect("postgresql://rag:ragpass@localhost:5432/ragdb")
    with conn, conn.cursor() as cur:
        for row in rows:
            cur.execute(
                """
                INSERT INTO documents (source, title, content, metadata, embedding)
                VALUES (%s, %s, %s, %s, %s)
                """,
                (
                    row["source"],
                    row["title"],
                    row["content"],
                    json.dumps(row["metadata"]),
                    row["embedding"],
                ),
            )
    

    pgvector 쪽의 장점은 여기서 바로 드러납니다. 기존 서비스가 PostgreSQL에 이미 기대고 있으면, 별도 저장소를 추가하지 않고도 RAG 아키텍처의 검색 계층을 꽤 자연스럽게 넣을 수 있습니다.

    pgvector 기반 벡터 데이터베이스 구조와 SQL 검색 흐름 이미지

    문서 테이블, 메타데이터 JSONB, 벡터 컬럼, 인덱스, 유사도 검색 SQL 흐름을 보여주는 구성 이미지입니다.

    5. 실전 구현 2: Weaviate로 벡터 검색 서비스 구성하기

    이번엔 Weaviate입니다. 실제로 써보니까 “아, 이건 벡터 검색을 서비스처럼 다루는 느낌이구나” 싶었습니다. 특히 스키마와 객체 단위 관리가 비교적 직관적이더라고요.

    5-1. Weaviate 실행

    services:
      weaviate:
        image: semitechnologies/weaviate:latest
        container_name: rag-weaviate
        ports:
          - "8080:8080"
        environment:
          QUERY_DEFAULTS_LIMIT: 10
          AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
          PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
          DEFAULT_VECTORIZER_MODULE: 'none'
          CLUSTER_HOSTNAME: 'node1'
        volumes:
          - weaviate_data:/var/lib/weaviate
    
    volumes:
      weaviate_data:
    

    여기서는 외부 임베딩 모델을 쓴다고 가정하고 DEFAULT_VECTORIZER_MODULE을 none으로 뒀습니다. 직접 임베딩을 넣는 패턴이 RAG에서는 꽤 흔하거든요.

    5-2. 컬렉션 성격의 스키마 생성

    import weaviate
    from weaviate.classes.config import Configure, Property, DataType
    
    client = weaviate.connect_to_local()
    
    client.collections.create(
        name="Document",
        vectorizer_config=Configure.Vectorizer.none(),
        properties=[
            Property(name="source", data_type=DataType.TEXT),
            Property(name="title", data_type=DataType.TEXT),
            Property(name="content", data_type=DataType.TEXT),
            Property(name="team", data_type=DataType.TEXT),
            Property(name="doc_type", data_type=DataType.TEXT),
        ],
    )
    
    client.close()
    

    5-3. 데이터 적재와 검색

    import weaviate
    from weaviate.classes.query import Filter
    
    client = weaviate.connect_to_local()
    collection = client.collections.get("Document")
    
    collection.data.insert(
        properties={
            "source": "runbook-001",
            "title": "PostgreSQL vacuum note",
            "content": "Autovacuum tuning and maintenance checklist",
            "team": "platform",
            "doc_type": "runbook",
        },
        vector=[0.01, 0.02, 0.03],
    )
    
    response = collection.query.near_vector(
        near_vector=[0.01, 0.02, 0.03],
        limit=5,
        filters=Filter.by_property("team").equal("platform")
    )
    
    for obj in response.objects:
        print(obj.properties)
    
    client.close()
    

    Weaviate는 이런 식으로 API와 객체 중심으로 흐름이 잘 잡혀 있습니다. SQL 없이도 검색 로직을 애플리케이션 계층에서 비교적 읽기 좋게 표현할 수 있다는 점이 장점입니다. 그리고 하이브리드 검색이나 스키마 중심 운영을 선호하는 팀이라면 꽤 편합니다.

    6. 주의사항과 트러블슈팅: 여기서 많이 막힙니다

    이 섹션은 좀 현실적으로 가보겠습니다. 문서만 보면 다 쉬워 보이는데, 실제로는 여기서 시간 많이 씁니다.

    6-1. 임베딩 차원 불일치

    ⚠️ 가장 흔한 실수입니다. 임베딩 모델을 바꿨는데 스키마 차원은 그대로 두는 경우죠. pgvector에서는 아예 삽입 단계에서 막히고, Weaviate에서도 입력 벡터 형식 검증에서 문제가 날 수 있습니다.

    • 해결법: 차원 수를 코드와 스키마에서 한 번만 정의하고 공통 상수로 관리합니다.
    • 팁: 적재 파이프라인 시작 전에 첫 벡터 길이를 검사하세요.

    6-2. 필터링 없는 유사도 검색

    RAG는 “비슷한 문서”만 찾으면 끝이 아닙니다. 권한, 조직, 문서 타입, 최신성 같은 조건이 붙습니다. 저는 처음에 메타데이터 설계를 대충 했다가 검색은 잘 되는데 엉뚱한 부서 문서가 섞여서 다시 뜯어고쳤습니다.

    • pgvector: JSONB와 일반 컬럼을 같이 써서 필터링 구조를 미리 잡는 게 좋습니다.
    • Weaviate: 속성 설계를 초기에 어느 정도 정리해두면 나중이 편합니다.

    6-3. 인덱스 튜닝을 너무 늦게 시작함

    작은 데이터셋에서는 다 빨라 보입니다. 근데 문서 수가 늘어나면 얘기가 달라집니다. pgvector는 인덱스 전략을 고민해야 하고, Weaviate도 검색 품질과 응답 시간을 같이 봐야 합니다. 저는 테스트 데이터 500건일 때는 차이를 못 느꼈는데, 규모가 커질수록 접근 방식 차이가 훨씬 선명해졌습니다.

    6-4. 운영 관점의 백업/복구

    이건 개발 단계에선 잘 안 보입니다. 하지만 서비스에 들어가면 꼭 봐야 합니다.

    • pgvector: PostgreSQL 백업 체계에 그대로 편승하기 좋습니다.
    • Weaviate: 별도 서비스로 다루는 만큼 백업, 모니터링, 복구 절차를 분리해서 생각해야 합니다.

    혹시 이런 경험 있으신가요? 검색은 잘 되는데 운영 문서가 없어서 배포를 못 하는 상황이요. 저는 이거 몇 번 겪고 나서부터는 기능보다 운영 체크리스트를 먼저 씁니다.

    Weaviate 오픈소스 벡터 DB 구성과 벡터 검색 API 흐름 이미지

    Weaviate의 컬렉션 구조와 필터 기반 near vector 검색 흐름을 설명하는 시각 자료입니다.

    7. 검증과 결과: 어떤 상황에서 무엇이 더 잘 맞았나

    이제 가장 궁금한 부분이죠. 그래서 뭘 고르면 되느냐. 제가 실제로 써보니까 기준은 꽤 분명했습니다.

    상황 더 잘 맞는 선택 이유
    기존 서비스가 PostgreSQL 중심 pgvector 운영 도구와 데이터 모델을 재사용하기 좋음
    문서 검색 서비스를 별도 계층으로 운영 Weaviate 벡터 검색 API 중심 구성이 자연스러움
    권한/조인/트랜잭션이 중요 pgvector 관계형 쿼리와 함께 다루기 편함
    하이브리드 검색과 검색 기능 확장 우선 Weaviate 벡터 검색 중심 기능 사용성이 좋음
    운영팀이 PostgreSQL에 매우 익숙함 pgvector 학습 비용이 낮음
    벡터 검색 전용 제품을 명확히 분리하고 싶음 Weaviate 시스템 역할 구분이 선명함

    검증 포인트도 같이 보셔야 합니다.

    1. 질문 20~30개를 수동으로 만들어 검색 결과를 비교합니다.
    2. 정답 문서가 상위 몇 개 안에 들어오는지 확인합니다.
    3. 메타데이터 필터가 예상대로 적용되는지 봅니다.
    4. 색인 후 반영 시간과 운영 절차를 기록합니다.

    이 과정을 해보면 벡터 DB 선택이 단순 성능 비교가 아니라는 걸 바로 느끼실 겁니다. RAG 아키텍처에서는 검색 정확도, 메타데이터 모델링, 운영 편의성, 팀 역량이 같이 움직입니다.

    pgvector Weaviate 비교 결과와 운영 체크리스트 요약 이미지

    검색 정확도, 필터링, 운영 복잡도, 확장성 같은 평가 항목을 한 화면에 정리한 결과 검증 이미지입니다.

    8. 제 결론: 둘 중 하나가 무조건 정답은 아닙니다

    결론은 좀 싱겁게 들릴 수도 있는데요. pgvector Weaviate 비교에서 절대적인 승자는 없습니다. 대신 선택 기준은 분명합니다.

    • pgvector를 추천하는 경우
      이미 PostgreSQL이 핵심 데이터 저장소이고, 애플리케이션 로직도 SQL 중심이며, 운영 복잡도를 최소화하고 싶을 때입니다.
    • Weaviate를 추천하는 경우
      벡터 검색을 서비스 단위로 분리하고 싶고, 검색 기능 자체를 적극적으로 확장할 계획이 있을 때입니다.

    제가 직접 해보니, 작은 팀이나 내부 업무용 검색은 pgvector가 정말 현실적이었습니다. 반대로 검색 기능이 제품 핵심이고, 문서 탐색 경험을 계속 개선해야 하는 구조라면 Weaviate가 더 손에 잘 맞더라고요. 결국 중요한 건 “뭘 더 잘하느냐”보다 “우리 팀이 어디서 덜 고생하느냐”입니다. 이거 무시하면 나중에 꼭 삽질합니다.

    데이터 구조, 운영 역량, 검색 기능 요구사항에 따라 어떤 선택이 적합한지 요약한 비교 인포그래픽입니다.

    9. 정리와 FAQ: 시작은 어떻게 하는 게 좋을까

    마무리로 아주 실무적으로 정리해보겠습니다.

    9-1. 한 줄 정리

    • 기존 PostgreSQL을 살리고 싶다: pgvector부터 보시면 됩니다.
    • 오픈소스 벡터 DB를 별도 검색 계층으로 쓰고 싶다: Weaviate가 더 자연스러울 수 있습니다.
    • RAG 품질이 안 나온다: DB보다 청킹, 메타데이터, 평가셋부터 점검하세요.

    9-2. 자주 묻는 질문

    Q. 둘 다 오픈소스 벡터 DB 범주로 봐도 되나요?
    엄밀히 보면 pgvector는 PostgreSQL 확장이고, Weaviate는 전용 벡터 데이터베이스에 가깝습니다. 다만 실무에선 둘 다 벡터 검색 저장소 후보로 같이 검토합니다.

    Q. 처음 시작하는데 무엇이 더 쉬운가요?
    PostgreSQL에 익숙하면 pgvector가 쉽습니다. 검색 전용 API 개념으로 접근하고 싶으면 Weaviate가 더 직관적으로 느껴질 수 있습니다.

    Q. 성능은 누가 더 좋나요?
    데이터 크기, 인덱스, 필터 조건, 질의 패턴에 따라 달라집니다. 그래서 벤치마크 숫자 한 줄보다, 내 데이터셋으로 직접 평가셋을 돌려보는 게 훨씬 중요합니다.

    다음 글에서는 RAG 평가셋 만드는 방법과, 검색 결과를 사람이 검수하기 쉽게 정리하는 방법도 다뤄보려고 합니다. 이전 글에서 청킹 전략을 정리했다면 같이 보시면 흐름이 더 잘 잡히실 겁니다. 여기까지 구성해보시면, 단순한 제품 비교를 넘어서 실제 서비스에 맞는 벡터 데이터베이스 선택 기준이 훨씬 선명해질 겁니다. 드디어 됐다 싶을 때가 오거든요. 그 순간부터 RAG가 좀 재밌어집니다. 🎉

  • [클라우드] AWS Aurora vs Cloud SQL 1년 운영 회고

    [클라우드] AWS Aurora vs Cloud SQL 1년 운영 회고

    [클라우드] AWS Aurora vs Cloud SQL 1년 운영 회고

    AWS Aurora vs Cloud SQL 이야기는 관리형 데이터베이스를 검토할 때 거의 한 번씩은 나오더라고요. 저도 홈랩과 실무 환경에서 각각 다른 워크로드를 굴리면서 1년 정도 운영 패턴을 비교해봤는데, 결론부터 말하면 둘 다 좋은 서비스입니다. 다만 비용이 새는 지점, 성능이 흔들리는 순간, 운영자가 신경 써야 하는 포인트가 꽤 다릅니다. 처음엔 “둘 다 관리형 DB(Managed Database, 클라우드 사업자가 운영을 대신해주는 데이터베이스)니까 비슷하겠지?” 싶었는데요. 실제로 써보니까 그 생각이 제일 위험했습니다. 특히 클라우드 DB 비용은 평소엔 조용하다가 트래픽이 늘거나 백업, 복제, 스토리지 I/O가 붙는 순간 티가 확 나거든요.

    이 글에서는 제가 직접 운영하면서 느낀 AWS Aurora vs Cloud SQL 차이를 비용과 성능 회고 중심으로 정리해보겠습니다. 숫자를 억지로 만들기보다, 어떤 상황에서 체감이 갈렸는지, 어떤 판단 기준이 실무에서 먹히는지 위주로 풀어볼게요. 혹시 지금 관리형 데이터베이스 도입을 고민 중이시라면, 스펙표보다 이런 운영 감각이 더 도움이 될 거예요.

    AWS Aurora vs Cloud SQL 전체 아키텍처 비교 이미지

    서비스 구조, 애플리케이션 연결, 읽기 복제본, 백업 영역까지 한눈에 보이는 비교 개요입니다.

    AWS Aurora vs Cloud SQL, 쉽게 말해 뭐가 다를까요?

    쉽게 말해 Aurora는 AWS가 만든 고가용성 중심의 클라우드 네이티브 관계형 데이터베이스 쪽에 더 가깝고, Cloud SQL은 Google Cloud에서 제공하는 전통적인 관리형 관계형 DB를 더 단순하게 운영하는 서비스에 가깝습니다.

    • Aurora: Amazon RDS 계열이지만 내부 구조가 조금 더 분산 스토리지 지향입니다. MySQL, PostgreSQL 호환 엔진을 쓰는 경우가 많고, 읽기 확장과 장애 대응 쪽이 강점으로 자주 언급됩니다.
    • Cloud SQL: MySQL, PostgreSQL, SQL Server 같은 익숙한 엔진을 Google Cloud에서 관리형으로 운영할 수 있게 해줍니다. 설정 흐름이 꽤 직관적이었고, GCP 서비스와 붙이기 편한 편이더라고요.

    여기서 중요한 포인트가 있습니다. AWS Aurora vs Cloud SQL 비교는 단순히 엔진 기능 비교가 아니라 운영 철학 비교에 가깝다는 점입니다. Aurora는 확장성과 고가용성을 어느 정도 전제로 설계된 느낌이고, Cloud SQL은 익숙한 DB 운영을 클라우드 방식으로 깔끔하게 가져가는 쪽에 가깝더라고요.

    항목 AWS Aurora Cloud SQL
    주된 인상 고가용성과 읽기 확장에 강한 편 단순 운영과 GCP 연계가 직관적
    적합한 상황 트래픽 변동이 있고 장애 대응이 중요한 서비스 중소형 서비스, 빠른 구축, 단순한 운영
    비용 체감 포인트 인스턴스 외 스토리지·I/O·복제 구조 인스턴스 크기·스토리지·백업 유지 정책
    운영 난이도 구조 이해가 필요함 상대적으로 단순함

    1년 운영하면서 본 비용 구조의 차이

    운영을 오래 해보면 월 요금 자체보다 비용이 왜 그렇게 나왔는지 설명 가능한가가 더 중요합니다. 처음엔 저도 대시보드만 보고 “이번 달 왜 이렇게 올랐지?” 했었는데, 원인을 추적해보니까 AWS Aurora vs Cloud SQL이 돈 먹는 방식이 좀 다르더라고요.

    Aurora 쪽에서 체감한 비용 포인트

    • 읽기 복제본(Reader)을 붙이면 안정성은 좋아지는데, 생각보다 빨리 월 고정비가 올라갑니다.
    • 스토리지와 I/O 성격을 이해하지 못하면 “인스턴스만 보면 싸 보였는데 총액은 아닌” 상황이 나옵니다.
    • 장애 대응을 위해 멀티 AZ(Multi-AZ, 다중 가용 영역 구성) 관점을 가져가면 운영 품질은 좋아지지만, 당연히 구조가 커집니다.

    Cloud SQL 쪽에서 체감한 비용 포인트

    • 단일 인스턴스로 시작하기 좋아서 초반 비용 예측은 쉬운 편이었습니다.
    • 백업 보관 기간과 고가용성 옵션을 켜기 시작하면 생각보다 빠르게 총액이 올라갑니다.
    • CPU와 메모리 선택이 비교적 직관적이라 작은 팀은 클라우드 DB 비용 설명이 편하더라고요.

    실제로 써보니까 Cloud SQL은 시작 비용의 이해가 쉽고, Aurora는 성장 이후 구조 비용을 반드시 같이 봐야 하는 서비스라는 인상이 강했습니다. 이건 좋고 나쁨의 문제가 아니라, 예상 트래픽과 장애 허용 범위에 따라 판단이 갈리는 부분입니다.

    관리형 데이터베이스 구축, 실제로는 어떻게 달랐나

    이 섹션은 실전 구현 관점으로 볼게요. 콘솔에서 클릭 몇 번으로 만들 수도 있지만, 운영 환경에서는 결국 재현 가능한 설정이 중요하거든요. 저는 Terraform(테라폼, 인프라를 코드로 관리하는 도구)나 CLI(Command Line Interface, 명령줄 도구) 기준으로 생각하는 편입니다.

    Aurora를 배포할 때 제가 보는 순서

    1. 엔진 호환성 선택: Aurora MySQL인지 Aurora PostgreSQL인지 먼저 정합니다.
    2. 가용성 요구사항 확인: 읽기 복제본이 필요한지, 장애 전환 우선순위가 어떤지 봅니다.
    3. 애플리케이션 연결 방식 정리: Writer endpoint와 Reader endpoint를 분리할지 결정합니다.
    4. 백업/모니터링 설정: 성능 지표와 로그 보존 정책을 미리 넣습니다.
    aws rds create-db-cluster \
      --db-cluster-identifier prod-aurora-cluster \
      --engine aurora-postgresql \
      --master-username appuser \
      --manage-master-user-password \
      --backup-retention-period 7

    물론 실제 운영에서는 VPC, 보안 그룹, 파라미터 그룹, 모니터링 설정이 더 붙습니다. 근데 핵심은 간단하거든요. Aurora는 클러스터 관점으로 봐야 한다는 점입니다. 처음엔 인스턴스 하나만 보게 되는데, 나중에 읽기 확장과 장애 대응을 붙이면 사고방식 자체가 달라져야 하더라고요.

    Cloud SQL을 배포할 때 제가 보는 순서

    1. MySQL/PostgreSQL/SQL Server 중 엔진을 먼저 정합니다.
    2. 고가용성 여부와 머신 타입을 결정합니다.
    3. 백업, 유지보수 시간대, Private IP(사설 IP) 연결 여부를 설정합니다.
    4. 애플리케이션과 연결 테스트 후 커넥션 제한을 점검합니다.
    gcloud sql instances create prod-cloudsql \
      --database-version=POSTGRES_15 \
      --tier=db-custom-2-7680 \
      --region=asia-northeast3 \
      --backup-start-time=03:00

    Cloud SQL은 진입 장벽이 꽤 낮습니다. 제가 처음 셋업할 때도 “어? 이건 생각보다 금방 되네?” 싶었거든요. 특히 GCP 네트워크와 붙여 쓰는 구성에서는 흐름이 단순해서 좋았습니다.

    AWS Aurora vs Cloud SQL 구성 흐름 비교 이미지

    클러스터형 구성과 단일 인스턴스 중심 구성이 어떻게 다른지 시각적으로 보여주는 이미지입니다.

    성능 회고: 벤치마크보다 중요했던 것들

    성능 얘기 나오면 다들 TPS(Transaction Per Second, 초당 처리 트랜잭션)나 지연 시간부터 떠올리시죠. 저도 처음엔 그랬습니다. 그런데 1년 정도 운영해보니, 실제로는 피크 타임의 일관성, 장애나 유지보수 시 체감, 읽기/쓰기 분리의 편의성이 더 중요하더라고요.

    Aurora에서 좋았던 점

    • 읽기 트래픽을 분산시키는 전략을 세우기 좋았습니다.
    • 애플리케이션이 Writer/Reader를 구분하도록 설계하면 병목을 줄이기 수월했습니다.
    • 트래픽이 늘어도 구조적으로 대응한다는 느낌이 있어 심리적으로도 편했습니다.

    Cloud SQL에서 좋았던 점

    • 소규모~중간 규모 서비스에서는 성능보다 단순함이 더 큰 장점이었습니다.
    • 문제가 생겼을 때 원인 범위를 좁히기 쉬웠습니다.
    • 운영팀이 많지 않을수록 오히려 효율이 좋았습니다.

    제가 직접 해보니, AWS Aurora vs Cloud SQL에서 성능 우열을 한 줄로 말하는 건 무리였습니다. 쓰기 중심인지, 읽기 비중이 큰지, 서비스가 어느 정도까지 확장될지에 따라 판단이 달라집니다. 읽기 확장과 장애 전환까지 포함한 운영 체감은 Aurora가 더 인상적이었고, 단순하고 예측 가능한 운영 흐름은 Cloud SQL이 더 편했습니다.

    SELECT now(), count(*)
    FROM orders
    WHERE created_at > now() - interval '1 hour';

    이런 단순 쿼리 하나도 실제 운영에서는 애플리케이션 패턴, 인덱스 상태, 연결 수, 캐시 유무에 따라 체감이 확 달라집니다. 그래서 저는 DB 성능을 볼 때 항상 쿼리 플랜, 연결 수, 스토리지 지표를 같이 봤습니다. 벤치마크 숫자 하나만 보면 꼭 삽질합니다 ㅎㅎ

    ⚠️ 운영하면서 실제로 겪었던 문제들

    이 부분이 제일 중요합니다. 관리형 데이터베이스라고 해서 운영 이슈가 사라지진 않거든요. 대신 문제의 종류가 바뀝니다.

    1. 연결 수(Connection) 관리 실패

    애플리케이션 인스턴스 수가 늘면서 DB 연결 수가 같이 폭증했던 적이 있습니다. 처음엔 DB가 느린 줄 알았는데, 실제로는 커넥션 풀(Connection Pool, DB 연결을 재사용하는 방식) 설정이 문제였습니다. 이건 Aurora든 Cloud SQL이든 공통으로 맞닥뜨릴 수 있습니다.

    • 애플리케이션별 최대 연결 수 제한
    • 풀 크기 조정
    • 짧은 배치 작업의 연결 재사용 정책 확인

    2. 백업은 켰는데 복구 테스트를 안 함

    이거 진짜 많이 놓칩니다. 저도 초반에는 백업이 있으니 안심했었는데, 막상 복원 흐름을 점검해보니 RTO(Recovery Time Objective, 목표 복구 시간) 감각이 전혀 없더라고요. 백업이 있다는 것과 복구가 잘 된다는 것은 완전히 다른 문제였습니다.

    3. 읽기 분리를 했는데 애플리케이션이 못 따라옴

    Aurora의 Reader endpoint를 붙였는데도 코드가 모든 트래픽을 쓰기 노드로 보내는 경우가 있었습니다. 구조는 좋아졌는데 앱이 활용을 못 한 거죠. 이때 느낀 게, DB 선택보다 애플리케이션 연결 전략이 먼저라는 점이었습니다.

    spring:
      datasource:
        writer:
          url: jdbc:postgresql://writer-endpoint:5432/app
        reader:
          url: jdbc:postgresql://reader-endpoint:5432/app

    이런 식으로 애플리케이션 레벨에서 역할 분리를 해줘야 효과가 납니다. 안 그러면 좋은 기능도 그냥 비싼 옵션이 됩니다.

    AWS Aurora vs Cloud SQL 트러블슈팅 대시보드 이미지

    실제 운영에서 자주 겪는 경고 지표와 병목 구간을 요약한 트러블슈팅 이미지입니다.

    검증 결과: 어떤 팀에 어떤 선택이 맞았나

    1년 정도 운영 회고를 정리해보면, 제가 내린 기준은 의외로 단순했습니다. “지금 필요한 안정성의 수준이 무엇인가”, “운영팀이 어디까지 관리할 수 있는가”, “클라우드 DB 비용을 설명 가능한가” 이 세 가지였습니다.

    상황 제가 더 선호한 선택 이유
    빠르게 시작해야 하는 신규 서비스 Cloud SQL 설정과 운영 흐름이 단순함
    읽기 부하가 커지고 분산이 필요한 서비스 Aurora 읽기 확장 전략을 세우기 좋음
    소수 인원 운영팀 Cloud SQL 문제 추적과 비용 설명이 상대적으로 쉬움
    장애 대응과 확장 여유를 미리 확보해야 하는 환경 Aurora 구조적으로 대응하기 편함

    여기서 중요한 포인트! 관리형 데이터베이스는 운영을 없애주는 게 아니라, 운영의 종류를 바꿔줍니다. 서버 패치나 스토리지 교체 같은 일은 줄어들 수 있어도, 연결 정책, 백업 복구, 성능 관측, 비용 최적화는 여전히 남습니다.

    제가 실무에서 느낀 건 이렇습니다. Cloud SQL은 “작고 빠르게, 명확하게” 가져가기 좋았고요. Aurora는 “조금 더 큰 구조를 미리 대비하자”는 상황에서 강했습니다. 둘 중 하나가 절대적으로 낫다기보다, AWS Aurora vs Cloud SQL 판단은 서비스의 성장 곡선과 팀의 운영 역량을 같이 봐야 합니다.

    AWS Aurora vs Cloud SQL 비용 및 성능 비교 결과 이미지

    월별 비용 변화, 읽기/쓰기 부하, 장애 대응 체감 포인트를 함께 보여주는 결과 요약 이미지입니다.

    클라우드 DB 비용을 덜 아프게 만드는 운영 팁

    클라우드 DB 비용은 아끼는 기술보다 불필요한 구조를 늦게 도입하는 감각이 더 중요했습니다. 저도 초반에는 “나중에 문제 생기면 어떡하지?” 하면서 옵션을 이것저것 켔었는데요. 그게 꼭 좋은 선택은 아니었습니다.

    • 처음부터 과한 고가용성 구성을 넣지 말고, 서비스 요구사항에 맞춰 단계적으로 키우세요.
    • 백업 보존 기간은 규정과 운영 현실을 기준으로 잡으세요. 막연히 길게 두면 마음은 편한데 비용이 올라갑니다.
    • 읽기 복제본은 실제 읽기 병목이 확인된 뒤 붙여도 늦지 않은 경우가 많습니다.
    • 모니터링 대시보드를 먼저 만드세요. 비용 튀는 원인을 모르면 최적화도 안 됩니다.
    # 점검 예시
    # 1) CPU 사용률
    # 2) 메모리 압박
    # 3) 연결 수
    # 4) 느린 쿼리 로그
    # 5) 스토리지 증가 추세

    이 다섯 가지만 꾸준히 봐도 감이 옵니다. 드디어 됐다! 싶은 순간이 오거든요. 비용 최적화는 대단한 비법보다 관측이 먼저였습니다.

    정리: AWS Aurora vs Cloud SQL, 결국 어떤 기준으로 고를까?

    정리해보겠습니다. AWS Aurora vs Cloud SQL 비교에서 제가 가장 크게 느낀 차이는 “확장과 장애 대응을 어느 시점에 구조로 가져갈 것인가”였습니다. Aurora는 처음엔 조금 무겁게 느껴질 수 있어도 성장 이후 운영 안정감이 좋았고, Cloud SQL은 빠르게 시작하고 단순하게 유지하기에 꽤 좋았습니다.

    • 작게 시작하고 빠르게 출시가 중요하면 Cloud SQL이 잘 맞습니다.
    • 읽기 부하 분산과 고가용성이 중요하면 Aurora 쪽이 더 자연스럽습니다.
    • 클라우드 DB 비용은 인스턴스 가격만 보지 말고 백업, 복제, 스토리지, 연결 구조까지 같이 봐야 합니다.

    저도 처음엔 이름값이나 기능표만 보고 판단하려다가 꽤 돌아갔습니다. 근데 1년 정도 운영하면서 느낀 건 명확했습니다. 좋은 관리형 데이터베이스는 기능이 많은 서비스가 아니라, 우리 팀이 설명 가능하게 운영할 수 있는 서비스라는 점입니다.

    다음 글에서는 읽기 복제본 설계나 PostgreSQL 파라미터 튜닝 같은 실전 운영 주제를 더 깊게 다뤄볼 예정입니다. 이전 글에서 다뤘던 모니터링 대시보드 구성과 함께 보시면 더 이해가 쉬우실 거예요. 혹시 지금 Aurora나 Cloud SQL 중에서 고민 중이시라면, 현재 서비스 트래픽 패턴과 장애 허용 범위를 먼저 적어보세요. 그게 생각보다 답을 빨리 줍니다.

    운영 규모, 비용 예측, 읽기 확장, 장애 대응 기준으로 어떤 선택이 맞는지 한 장으로 정리한 요약 이미지입니다.

    자주 묻는 질문

    Q1. 관리형 데이터베이스면 DBA 역할이 거의 없어지나요?

    아닙니다. OS 패치나 일부 유지보수는 줄어들 수 있지만, 성능 분석, 쿼리 튜닝, 백업 복구 전략, 권한 관리 같은 핵심 운영은 여전히 중요합니다. 특히 대규모 서비스라면 DBA의 역할이 더욱 중요하거든요.

    Q2. 비용만 보면 무조건 Cloud SQL이 유리한가요?

    항상 그렇진 않습니다. 초기에는 단순하게 보일 수 있지만, 고가용성과 백업, 확장 요구가 붙으면 구조가 달라집니다. 반대로 Aurora도 요구사항이 분명하면 비용이 납득 가능한 경우가 많았습니다. 결국 어떤 서비스인지에 따라 다릅니다.

    Q3. 성능 회고를 할 때 제일 먼저 봐야 할 지표는 뭔가요?

    CPU, 메모리, 연결 수, 느린 쿼리, 스토리지 증가 추세부터 보시는 걸 추천드립니다. 이 다섯 개가 기본인데, 여기서 문제 실마리가 꽤 자주 나옵니다. 특히 연결 수와 느린 쿼리 로그는 운영 초기에 많은 이슈를 해결해주거든요.

  • [NAS] Immich NAS 성능 벤치마크와 최적화

    [NAS] Immich NAS 성능 벤치마크와 최적화

    [NAS] Immich NAS 성능 벤치마크와 최적화

    사진이 몇 만 장을 넘어가면, 그때부터는 단순히 저장만 하는 NAS(Network Attached Storage, 네트워크 저장소)로는 답이 잘 안 나오더라고요. 특히 Immich NAS 성능 이슈는 썸네일 생성, 머신러닝(Machine Learning, 기계학습) 분석, 모바일 업로드가 한꺼번에 몰릴 때 바로 체감됩니다. 저도 홈랩에서 Docker on NAS 환경으로 Immich를 굴려보면서 “왜 평소엔 멀쩡한데 오늘만 이렇게 느리지?” 같은 상황을 꽤 겪었습니다. 이번 글은 숫자를 지어내는 벤치마크가 아니라, 실제로 재현 가능한 측정 기준과 최적화 포인트를 정리한 글입니다.

    특히 Synology Docker 환경이나 TrueNAS 계열에서 Immich를 올려 쓰는 분들이라면, CPU만 볼 게 아니라 저장소 I/O(Input/Output, 입출력), 썸네일 작업 큐, PostgreSQL(포스트그레스큐엘, 데이터베이스) 설정까지 같이 봐야 합니다. 여기서 중요한 포인트! Immich 벤치마크는 단순 속도 테스트가 아니라 병목 구간을 찾는 과정이라는 점입니다.

    Immich NAS 성능 구성을 보여주는 전체 아키텍처 다이어그램

    Immich, PostgreSQL, Redis, 업로드 클라이언트, NAS 스토리지 경로가 한눈에 보이는 전체 구성도입니다.

    1. 왜 Immich NAS 성능이 생각보다 중요할까요?

    쉽게 말해 Immich는 그냥 사진을 쌓아두는 앱이 아닙니다. 업로드가 들어오면 메타데이터(metadata, 부가정보)를 정리하고, 썸네일(thumbnail, 미리보기 이미지)을 만들고, 검색을 위한 인덱스(index, 색인)를 쌓고, 경우에 따라 머신러닝 모델이 얼굴이나 장면 분석도 수행합니다. 그러니까 저장소 하나만 빠르다고 끝이 아니고, CPU, 메모리, 디스크, 컨테이너 배치가 같이 맞아야 하더라고요.

    제가 직접 해보니 초반엔 “NAS에 Docker만 올리면 되겠지”라고 생각했었는데, 실제로 써보니까 다음 세 가지가 체감 성능을 많이 갈랐습니다.

    • 대량 초기 인덱싱: 처음 라이브러리를 스캔할 때 CPU와 저장소 부하가 크게 올라갑니다.
    • 동시 업로드: 휴대폰 여러 대가 동시에 백업하면 I/O 대기 시간이 늘어납니다.
    • 썸네일/트랜스코딩: 작은 파일이 아주 많이 만들어져서 HDD 기반 볼륨은 생각보다 불리할 수 있습니다.

    혹시 이런 경험 있으신가요? 업로드는 끝났는데 앱에서 사진이 늦게 보이거나, 검색 결과가 한참 뒤에 반영되는 경우요. 이런 게 바로 사진 관리 솔루션으로서 Immich NAS 성능 문제로 이어집니다.

    2. Immich 벤치마크를 볼 때 기준을 먼저 정해야 합니다

    Benchmark(벤치마크, 성능 측정)라고 하면 숫자부터 보고 싶어지는데요, 사실 환경마다 차이가 너무 커서 절대 수치만 보면 오해하기 쉽습니다. CPU 세대, SSD 캐시 여부, 데이터셋 크기, 파일당 평균 용량, 컨테이너 리소스 제한이 다 다르거든요. 그래서 저는 아래처럼 작업 단위 기준으로 보는 걸 추천합니다.

    측정 항목 왜 중요한가 병목 힌트
    초기 라이브러리 스캔 시간 대량 사진 등록 시 전체 체감 속도에 직결 CPU 또는 디스크 I/O
    업로드 후 사진 표시 지연 모바일 백업 만족도와 직접 연결 썸네일 큐 적체, DB 응답 지연
    검색 반영 속도 메타데이터 처리 성능 확인 가능 DB, 인덱싱 작업 병목
    백그라운드 작업 중 CPU 점유 다른 NAS 서비스와 공존 가능성 판단 ML 작업, 트랜스코딩 부하
    스토리지 사용 패턴 썸네일/DB 경로 최적화 판단 작은 파일 쓰기 지연

    즉, Immich NAS 성능을 제대로 보려면 “몇 초 나왔냐”보다 “어떤 작업에서 느려졌냐”가 더 중요합니다. 저도 처음엔 이게 뭔가 싶었는데, 병목을 구간별로 나눠서 보니 훨씬 명확해지더라고요.

    3. Docker on NAS 환경에서 먼저 확인할 구성

    실전 들어가기 전에, 구성부터 정리하겠습니다. Synology Docker나 일반 리눅스 NAS, 그리고 TrueNAS 계열처럼 컨테이너 앱으로 Immich를 운영하는 경우에도 핵심은 비슷합니다.

    1. 사진 원본 라이브러리 경로와 데이터베이스 저장 경로를 구분합니다.
    2. 가능하면 PostgreSQL 데이터 디렉터리는 SSD 계층에 둡니다.
    3. 업로드 원본과 썸네일 캐시는 같은 풀(pool, 저장소 집합)에 둘지 분리할지 미리 결정합니다.
    4. 백그라운드 작업 시간대를 정합니다. 가족 사진 업로드가 많은 시간과 겹치면 체감이 확 떨어집니다.

    제가 홈랩에서 삽질 좀 했습니다 ㅎㅎ 원본 사진은 대용량 HDD 어레이(array, 디스크 묶음)에 두고, DB랑 캐시까지 같은 볼륨에 몰아뒀더니 작은 파일 I/O가 몰릴 때 응답성이 확 나빠졌습니다. 이후 DB와 캐시 계층을 더 빠른 스토리지로 분리하니 체감이 꽤 좋아졌습니다.

    예시 docker-compose.yml

    services:
      immich-server:
        image: ghcr.io/immich-app/immich-server:release
        container_name: immich_server
        depends_on:
          - redis
          - database
        environment:
          DB_HOSTNAME: database
          DB_USERNAME: immich
          DB_PASSWORD: change_me
          DB_DATABASE_NAME: immich
          REDIS_HOSTNAME: redis
        ports:
          - "2283:2283"
        volumes:
          - /mnt/nas/photo-library:/usr/src/app/upload
          - /etc/localtime:/etc/localtime:ro
        restart: unless-stopped
    
      immich-machine-learning:
        image: ghcr.io/immich-app/immich-machine-learning:release
        container_name: immich_ml
        volumes:
          - /mnt/fast/immich-model-cache:/cache
        restart: unless-stopped
    
      redis:
        image: redis:7
        container_name: immich_redis
        restart: unless-stopped
    
      database:
        image: tensorchord/pgvecto-rs:pg14-v0.2.0
        container_name: immich_db
        environment:
          POSTGRES_USER: immich
          POSTGRES_PASSWORD: change_me
          POSTGRES_DB: immich
        volumes:
          - /mnt/fast/postgres-immich:/var/lib/postgresql/data
        restart: unless-stopped

    버전은 예시일 뿐이고, 실제 배포 전에는 사용 중인 Immich 공식 문서와 현재 이미지 태그를 반드시 다시 확인하셔야 합니다. 중요한 건 구조입니다. 업로드 경로, 모델 캐시, 데이터베이스 경로를 어떻게 나누느냐가 성능에 직접 영향을 줍니다.

    Immich NAS 성능 최적화를 위한 스토리지 마운트 분리 구성 이미지

    업로드 볼륨, 데이터베이스 볼륨, 모델 캐시 볼륨을 서로 다른 스토리지 계층에 배치하는 구성 예시입니다.

    4. Immich 벤치마크를 위한 측정 절차

    이제 본격적으로 측정해보겠습니다. 여기서 제가 추천하는 방식은 동일 데이터셋으로 세 번 반복입니다. 첫 번째는 캐시가 비어 있는 상태, 두 번째는 일부 캐시가 쌓인 상태, 세 번째는 설정 변경 후 비교용입니다.

    1. 컨테이너 상태와 리소스 점유를 먼저 확인합니다.
    2. 테스트용 사진 세트를 준비합니다. 가능하면 파일 크기와 개수가 섞여 있어야 합니다.
    3. 업로드 직후부터 라이브러리 반영 완료 시점까지 시간을 기록합니다.
    4. CPU, 메모리, 디스크 I/O, 컨테이너 로그를 함께 봅니다.
    5. 설정을 하나만 바꾼 뒤 다시 측정합니다. 한 번에 여러 개 바꾸면 원인 파악이 안 됩니다.

    기본 확인 명령어

    docker ps
    
    docker stats
    
    docker logs -f immich_server
    
    docker logs -f immich_db
    
    df -h
    
    iostat -x 1

    여기서 iostat는 디스크 대기 시간을 보기 좋습니다. 만약 NAS 운영체제에서 직접 설치가 어렵다면, 제공되는 모니터링 도구로 대체해도 됩니다. Synology에서는 리소스 모니터(resource monitor), TrueNAS 계열에서는 대시보드와 풀 I/O 그래프를 같이 보면 감이 옵니다.

    업로드 테스트 예시

    time rsync -avh ./sample-photos/ /mnt/nas/photo-library/import-test/
    
    find /mnt/nas/photo-library/import-test -type f | wc -l

    실제로 써보니까 업로드 자체보다, 그 뒤에 이어지는 백그라운드 잡(background job, 백그라운드 작업)이 더 오래 걸리는 경우가 많았습니다. 그래서 저는 업로드 종료 시점과 앱에서 사진이 정상 탐색되는 시점을 따로 적어뒀습니다.

    5. 제가 자주 본 병목 구간과 최적화 포인트

    Immich NAS 성능을 끌어올릴 때는 무작정 CPU를 올리는 것보다 병목별 대응이 더 효과적입니다.

    • DB가 느린 경우: PostgreSQL 데이터 경로를 더 빠른 스토리지로 이동해보세요. Synology Docker나 TrueNAS Docker 환경이라면 기본 스토리지 계층을 확인하고 SSD 풀로 분리하는 걸 추천합니다.
    • 썸네일 생성이 밀리는 경우: 원본 사진 경로는 HDD여도, 캐시와 DB는 SSD 계층이 유리합니다.
    • 컨테이너 간 I/O 경쟁: Immich 외에 미디어 서버, 백업 작업, 다운로드 작업이 동시에 돌면 NAS 전체 응답성이 떨어집니다.
    • 메모리 부족: 스왑(swap, 디스크 기반 가상 메모리)이 발생하면 체감 성능이 급격히 무너집니다.

    저도 처음엔 CPU 사용률만 보고 있었는데, 근데 여기서 함정이 있더라고요. CPU는 40~50% 정도인데도 체감은 엄청 느릴 수 있습니다. 이런 경우는 대체로 디스크 대기나 작은 파일 쓰기 병목이었습니다.

    튜닝 전후를 비교할 때 체크할 것

    항목 튜닝 전 튜닝 후 기대 변화
    업로드 후 썸네일 반영 지연이 길고 들쭉날쭉 반영 속도가 더 일정해짐
    DB 응답 검색/스크롤 시 버벅임 목록 탐색이 부드러워짐
    동시 작업 시 NAS 반응성 다른 서비스도 같이 느려짐 영향 범위가 줄어듦
    백그라운드 작업 완료 시간 예측이 어려움 대체로 패턴이 안정적

    6. ⚠️ 트러블슈팅: 실제로 많이 겪는 문제

    이 섹션은 진짜 경험담 위주입니다. 저도 처음엔 헷갈렸는데, 아래 문제들은 꽤 자주 만났습니다.

    1) 볼륨 권한 문제로 업로드는 되는데 후처리가 꼬이는 경우

    컨테이너 내부 사용자 권한과 NAS 실제 디렉터리 권한이 안 맞으면 생기는 문제입니다. 파일은 생기는데 일부 작업이 실패하거나, 썸네일 생성 로그에 에러가 남을 수 있습니다.

    ls -al /mnt/nas/photo-library
    id
    
    docker exec -it immich_server sh

    해결 포인트: 컨테이너에서 접근하는 UID/GID와 NAS 공유 폴더 권한을 맞추는 겁니다.

    2) 데이터베이스를 느린 HDD 볼륨에 둔 경우

    사진 원본은 순차 읽기 비중이 커서 HDD도 어느 정도 버티는데, DB는 작은 쓰기와 랜덤 접근이 잦거든요. 그래서 같은 HDD 풀에 몰아두면 검색 반영과 라이브러리 응답성이 같이 떨어질 수 있습니다.

    해결 포인트: PostgreSQL 경로를 더 빠른 SSD 계층으로 옮기고, 원본 라이브러리와 분리해보세요. 특히 Synology Docker나 TrueNAS Docker 환경에서는 별도 SSD 볼륨을 준비해두는 것이 중요합니다.

    3) 머신러닝 작업이 몰리면서 NAS 전체가 답답해지는 경우

    얼굴 인식이나 장면 분석이 동시에 많이 돌면 CPU 자원을 꽤 씁니다. 이럴 때는 백업 작업, 미디어 인덱싱 작업과 시간이 겹치지 않게 분산하는 게 좋습니다.

    해결 포인트: 초기 분석은 야간으로 미루고, 낮에는 업로드 위주로 운영해보세요.

    Immich NAS 성능 벤치마크 결과를 확인하는 모니터링 대시보드 이미지

    업로드 이후 백그라운드 작업 큐가 쌓이고, CPU와 디스크 사용량이 함께 움직이는 모습을 보여주는 대시보드 예시입니다.

    7. 검증: 어떤 결과가 나오면 최적화가 잘 된 걸까요?

    여기서 중요한 건 절대 점수보다 일관성입니다. 제가 직접 해보니 최적화가 잘 된 환경은 다음 특징이 있었습니다.

    • 업로드 후 사진 표시까지 걸리는 시간이 들쭉날쭉하지 않습니다.
    • 대량 업로드 중에도 웹 인터페이스 탐색이 완전히 무너지지 않습니다.
    • 검색과 스크롤이 이전보다 자연스럽게 유지됩니다.
    • 백그라운드 작업이 끝나는 패턴이 예측 가능해집니다.

    즉, Immich 벤치마크의 핵심은 “최고 속도”보다 “실사용 중 안정성”입니다. 숫자 하나만 보고 판단하면 실제 사용감과 어긋날 때가 많습니다.

    가능하다면 아래처럼 간단한 기록표를 만들어 두세요.

    # 예시 기록 항목
    # 테스트 날짜
    # 사진 수 / 총 용량
    # 원본 저장소 위치
    # DB 저장소 위치
    # 업로드 완료 시각
    # 라이브러리 반영 완료 시각
    # 썸네일 생성 완료 체감 시각
    # CPU / 메모리 / I/O 특이사항

    이렇게 해두면 Synology Docker 환경과 다른 NAS 환경을 비교할 때도 훨씬 객관적으로 볼 수 있습니다. 다음 글에서는 모니터링 스택까지 붙여서 좀 더 자동화된 방식으로 다뤄볼 예정입니다.

    8. 정리 + 자주 묻는 질문

    정리해보면, 사진 관리 솔루션으로서 Immich는 정말 매력적입니다. 다만 NAS 위에서 잘 돌리려면 애플리케이션만 보는 게 아니라 스토리지 구조와 컨테이너 배치까지 같이 봐야 하더라고요. 저도 처음엔 단순히 이미지 올리고 끝인 줄 알았는데, 실제로는 Docker on NAS 환경 설계가 성능의 절반 이상을 좌우했습니다. 드디어 됐다! 싶은 순간은, 업로드와 탐색이 동시에 자연스럽게 돌아갈 때 오더군요.

    • Q. CPU가 높지 않은데도 느린 이유는 뭔가요?
      A. 디스크 I/O나 DB 응답 지연일 가능성이 큽니다. 작은 파일 쓰기가 많은 구간을 의심해보세요.
    • Q. 사진 원본도 SSD에 둬야 하나요?
      A. 꼭 그렇진 않습니다. 다만 DB와 캐시 계층은 더 빠른 스토리지가 체감에 유리합니다.
    • Q. TrueNAS Docker로 검색해도 되나요?
      A. 검색 키워드로는 많이 쓰지만, 실제 운영 방식은 NAS 플랫폼 버전에 따라 앱 또는 컨테이너 구성 방식이 다를 수 있으니 현재 환경 기준으로 확인하시는 게 좋습니다.
    • Q. Immich NAS 성능 최적화에서 가장 먼저 할 일은?
      A. 원본, DB, 캐시 경로를 분리해서 병목 위치를 확인하는 겁니다.
    Immich NAS 성능 최적화 전후를 요약한 인포그래픽

    병목 구간, 저장소 배치, 체크리스트를 한 장으로 요약한 인포그래픽입니다.

    이전 글에서 NAS 볼륨 설계와 백업 전략을 다뤘다면, 이번 글은 그 위에 Immich를 얹었을 때의 실제 운영 포인트에 더 가깝습니다. 다음 글에서는 로그와 모니터링을 붙여서, 어떤 지표를 보면 병목이 바로 보이는지 이어서 정리해보겠습니다. Immich NAS 성능 문제로 답답하셨다면, 오늘은 숫자보다 구조부터 다시 보시는 걸 추천드립니다.