엘리스의 통합검색 기능을 개선하여 유저가 원하는 결과를 내도록 개선한 경험이다.

https://academy.elice.io 여기서 써볼수있다

유저가 겪는 문제

대부분의 유저는 이름이 "LangChain~"으로 시작하는 상품을 검색하기 위해 보통 "Lang"까지만 검색해본다.

하지만 우리의 통합검색 기능은 "Lang"을 입력했을때 "LangChain~" 상품을 보여주지 않았다.

 

왜 이런 현상이 일어나는 것일까? elasticsearch의 analyzer 동작 원리를 잠깐 살펴보고 가자.

 

elasticsearch analyzer 동작 원리

elasticsearch는 문자열이 들어오면 analyzer를 통해 character filter -> tokenizer -> token filter 순서로 전처리를 한다

 

예를 들어  "<strong>LangChain</strong>으로 나만의 업무" 라는 문장이 있다고 가정해보자

 

먼저 character filter는 토큰화 이전에 문자열을 가공하는 역할을 한다.

character filter는 문자열을 어떻게 정리할 것인지에 따라 HTML 태그 제거, 패턴 기반 치환등 여러 종류가 있다.

만약 HTML 태그 제거 character filter를 사용한다면 "LangChain으로 나만의 업무" 와 같은 문자열로 변환된다.

 

다음으로 tokenizer는 문자열을 토큰화(분리)하는 역할을 한다.

tokenizer는 문장을 어떻게 나눌 것인지에 따라 공백 기반 분할, 패턴 기반 분할 등 여러 종류가 있다

공백 기반 Tokenizer를 사용한다면 ["LangChain으로", "나만의", "업무"] 와 같이 문자열을 분리한다.

 

마지막으로 token filter는 토큰화된 문자들을 다시 가공하는 역할을 한다.

token filter 또한 문자열을 어떻게 정리할 것인지에 따라 대소문자 변환, 중복 제거 등 여러 종류가 있다 

대소문자 변환 token filter를 사용한다면 ["langchain으로", "나만의", "업무"] 와 같이 가공한다.

 

이처럼 analyzer는 원본 텍스트를 검색엔진(elasticsearch)이 이해할 수 있는 형태로 변환하는 역할을 한다.

 

문제 원인

다시 유저가 겪던 문제로 돌아가서 원인을 파악해보았는데, 아래와 같았다

  • 현재 공백 기준으로 토큰화하는 tokenizer를 사용하고 있다 (standard tokenizer)
  • fuzziness 허용 편집거리는 1이다 (오타 1개까지 허용)
    • 즉 "Lang_" 까지 편집거리를 허용해주는데,
      실제 저장된 token인 "LangChain으로"는 편집거리가 훨씬 커서 매칭되지 않는다

해결방법

요구사항에 맞는 적절한 analyzer를 생성하여 해결하였다

{
  "analyzer": {
    "camel_analyzer": {
      "filter": ["lowercase"],
      "char_filter": ["camel_filter"],
      "tokenizer": "nori_mixed"
    }
  },
  "char_filter": {
    "camel_filter": {  // 1
      "pattern": "(?<=\\p{Lower})(?=\\p{Upper})",
      "type": "pattern_replace",
      "replacement": " "
    }
  },
  "tokenizer": {
    "nori_mixed": {  // 2
      "type": "nori_tokenizer",
      "decompound_mode": "mixed"
    }
  }
}
  1. CamelCase character filter 를 적용해 입력을 전처리 한다
    • ["LangChain으로 나만의 업무"] → ["lang chain으로 나만의 업무"]
  2. 전처리 결과에 nori_tokenizer를 적용해 조사를 분리한다
    • ["lang chain으로 나만의 업무"] → ["lang", "chain", "으로", "나", "만", "의", "업무"]

위 analyzer를 index에 적용하고, reindex한 후에 "lang"으로 검색하면,

["lang", "chain", "으로", "나", "만", "의", "업무"]에 lang이 포함되어 있으므로 가중치가 높게 계산될 것이다.

결과

의도한대로 "Lang"으로 검색하면 "LangChain~"이름을 가진 상품이 잘 나온다. 👍

 

 

Ref.

'DB' 카테고리의 다른 글

[PostgreSQL] 운영중인 DB 조용히 갈아끼우기  (0) 2025.07.27
[DB] MongoDB 장점  (0) 2022.06.01
[DB] MongoDB 자료구조  (0) 2022.06.01
[DB] Lock 이해하기  (0) 2022.05.20
[DB] 상황에 맞는 인덱스 사용법  (0) 2022.05.20

 

회사의 모든 데이터베이스를 CNPG로 일원화하고자 하는 정책에 따라, 현재 운영 중인 Azure PostgreSQL 서비스를 자체 관리형 CloudNativePG(CNPG) 클러스터로 마이그레이션해야 하는 상황이 되었다.

 

만약 운영 서비스와 연결되지 않은 데이터베이스였다면 단순히 dump & restore 방식으로 쉽게 해결할 수 있었겠지만, 실제 서비스가 연결되어 있어 다른 접근 전략이 필요했다.

 

팀 내에서도 실시간으로 운영 중인 데이터베이스를 마이그레이션해본 경험이 없었기 때문에 팀원들과 충분히 논의하며 마이그레이션 계획을 수립했다.

 

검토 결과 두 가지 선택지가 있었다.

  • 무중단 마이그레이션 (복제 지연 위험 존재)
  • 다운타임 허용 마이그레이션 (데이터 정합성 보장)

우리 서비스의 특성상 교육 시간대만 피하고 사전 공지를 한다면 짧은 다운타임이 발생해도 비즈니스에 큰 영향이 없었기 때문에, 데이터 정합성을 우선시하는 방향으로 결정했다.

마이그레이션 순서

다운타임 허용 마이그레이션 순서는 아래와 같이 정리했다

  • DB 실시간 복제 실행 → 기존 앱 중단 → 데이터 정합성 확인 → 새로운 앱 배포

마이그레이션 방식 선택

마이그레이션 방식으로는 CDC와 Logical Replication 두 가지를 검토했다.

두 방식 모두 내부적으로 WAL(Write Ahead Logging)을 사용한다는 공통점이 있지만, CDC는 Kafka와 같은 추가 이벤트 스트림 컴포넌트가 필요하다는 차이점이 있다.

  • CDC: Source DB → WAL → Debezium → Kafka → Target(PostgreSQL, Elasticsearch 등)
  • Logical Replication: Source DB → WAL → Target DB

이번 케이스에서는 Debezium, Kafka 등의 추가 컴포넌트 설정이 불필요하고, 동일한 PostgreSQL 간의 단순 마이그레이션이므로 Logical Replication을 선택했다.

도구 선택

Logical Replication 구현을 위해 별도의 서드파티 도구 없이 PostgreSQL 내장 기능만으로 동작하는 pgcopydb(https://github.com/dimitri/pgcopydb)를 선택했다.


테스트 환경 세팅

가장 먼저 개발서버에서 사전 테스트를 진행하기 위해 운영환경과 동일한 환경을 세팅하였다

docker compose로 진행했으며, source/target DB에 대해 postgresql.conf를 적절히 작성하였다

version: '3'
services:
    postgres_source:
        image: postgres:13
        environment:
            - POSTGRES_DB=source-db
            - POSTGRES_USER=hanbin
            - POSTGRES_PASSWORD=thisispassword
        volumes:
            - postgres_13-data:/var/lib/postgresql/data
            - ./postgres/postgresql.conf:/etc/postgresql/postgresql.conf:ro
        ports:
            - "127.0.0.1:5432:5432"
        shm_size: 1g
        command: [
            "-c", "config_file=/etc/postgresql/postgresql.conf",
         ]
    postgres_target:
        image: postgres:16
        environment:
            - POSTGRES_DB=target-db
            - POSTGRES_USER=hanbin
            - POSTGRES_PASSWORD=thisispassword
        volumes:
            - postgres_16-data:/var/lib/postgresql/data
            - ./postgres/postgresql.conf:/etc/postgresql/postgresql.conf:ro
        ports:
            - "127.0.0.1:5433:5432"
        shm_size: 1g
        command: [
            "-c", "config_file=/etc/postgresql/postgresql.conf",
        ]
volumes:
    postgres_source-data:
        driver: local
    postgres_target-data:
        driver: local

 


테스트 순서

자세한 순서는 아래와 같다

1. Bastion VM pgcopydb 설치 및 연결 테스트
2. Source DB유저에게 모든 sequence 권한 부여
3. Target DB유저에게 임시로 superuser 권한 부여
4. DB 동기화
5. 복제 상황 모니터링
6. 복제 지연 최소화 대기 및 데이터 검증
7. api, worker, cron pod 제거
8. 복제 지연 없음 확인
9. pgcopydb 프로세스 종료
10. replication slot, origin 삭제
11. target DB유저 superuser 권한 제거
12. api, worker, cron 재배포

순서대로 차근차근 진행해보자

1. Bastion VM pgcopydb 설치 및 연결 테스트
sudo apt update
sudo apt install pgcopydb

mkdir -p pgcopydb-migration
cd pgcopydb-migration

export PGCOPYDB_SOURCE_PGURI="postgres://source-db:thisispassword@127.0.0.1:5432/source-db"
export PGCOPYDB_TARGET_PGURI="postgres://target-db:thisispassword@127.0.0.1:5433/target-db"

pgcopydb ping --source "$PGCOPYDB_SOURCE_PGURI" --target "$PGCOPYDB_TARGET_PGURI"
# 기대 출력
## INFO Successfully could connect to source database
## INFO Successfully could connect to target database
2. Source DB유저에게 모든 sequence 권한 부여
-- source-db 유저에게 모든 sequence 권한 부여
GRANT SELECT, USAGE ON ALL SEQUENCES IN SCHEMA public TO "source-db";
3. Target DB유저에게 임시로 superuser 권한 부여
-- 임시로 superuser 권한 부여
ALTER USER "target-db" WITH SUPERUSER;
4. DB 동기화
nohup pgcopydb clone --follow \
  --source "$PGCOPYDB_SOURCE_PGURI" \
  --target "$PGCOPYDB_TARGET_PGURI" \
  --no-owner \
  --no-acl \
  --table-jobs 1 \
  --index-jobs 1 \
  --verbose \
  --dir . \
  > ./pgcopydb-migration.log 2>&1 &

# 프로세스 ID 저장
echo $! > ./pgcopydb-migration.pid

# 실시간 로그 확인
tail -f ./pgcopydb-migration.log

# 진행 상황 확인
./replication_monitoring.sh
6. 복제 지연 최소화 대기 및 데이터 검증
복제지연 확인
SELECT
    slot_name,
    active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) as lag_size,
    pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) as lag_bytes
FROM pg_replication_slots
WHERE slot_name = 'pgcopydb';

-- Result --
 slot_name | active | lag_size | lag_bytes
-----------+--------+----------+-----------
 pgcopydb  | t      | 541 kB   |    553920
(1 row)
데이터 일관성 검증
pgcopydb compare schema --verbose
pgcopydb compare data --verbose

약 541KB 만큼의 복제 지연이 있다는 의미이다.

# 데이터 정합성 확인
pgcopydb compare data

# Output
16:11:44.270 162208 INFO   Running pgcopydb version 0.17-1.pgdg22.04+1 from "/usr/bin/pgcopydb"
16:11:44.357 162208 INFO   Using work dir "/tmp/pgcopydb"
16:11:44.358 162208 INFO   SOURCE: Connecting to "postgres://target-db@127.0.0.1:5432/target-db?keepalives=1&keepalives_idle=10&keepalives_interval=10&keepalives_count=60"
16:11:44.361 162208 INFO   Re-using catalog caches
16:11:44.365 162208 INFO   Starting 4 table compare processes
                    Table Name | ! |                      Source Checksum |                      Target Checksum
-------------------------------+---+--------------------------------------+-------------------------------------
                 public.user |   |  9136aaf4-0f32-cd2b-850f-49270ff7c03 |  9136aaf4-0f32-cd2b-850f-49270ff7c03

만약 두 데이터가 다른 경우 Checksum이 다르고, 두번째 컬럼에 !가 나타난다

7. api, worker, cron pod 제거

사용중인 CD tool에서 직접 내렸다

8. 복제 지연 없음 확인

6번 과정에서 진행한 작업을 반복하여 확인한다

9. replication slot, origin 삭제
-- SOURCE DB에서
SELECT pg_drop_replication_slot('pgcopydb');

-- TARGET DB에서
SELECT pg_replication_origin_drop('pgcopydb');
10. target DB유저 superuser 권한 제거
-- superuser 권한 제거
ALTER USER "target-db" WITH NOSUPERUSER;
11. api, worker, cron 재배포

사용중인 CD tool에서 직접 다시 올렸다

 


실제로 진행할때는 개발환경에서 권한과 postgresql 버전 등등을 정확히 동일하게 세팅 하지 않았어서 운영환경에 반영할때 잡음이 많았다.

좀더 꼼꼼하게 개발환경을 세팅했으면 좋았을텐데 아쉬웠다.

 

팀 내에서 경험이 없던 작업이라 문서화에 신경을 많이 썼는데, 문서화가 제일 피곤했다

 

데이터 정합성과 다운타임을 모두 잡는 해결책이 있을지 궁금해졌다.

'DB' 카테고리의 다른 글

[Elasticsearch] 유저가 원하는 검색결과 보여주기 (analyzer)  (1) 2025.07.31
[DB] MongoDB 장점  (0) 2022.06.01
[DB] MongoDB 자료구조  (0) 2022.06.01
[DB] Lock 이해하기  (0) 2022.05.20
[DB] 상황에 맞는 인덱스 사용법  (0) 2022.05.20

+ Recent posts