원격-리인덱스(Remote-Reindex)를 통한 검색엔진 업그레이드 (Elasticsearch 6 to OpenSearch 1.3)
스푼라디오 서비스는 다양한 콘텐츠의 검색 기능을 제공하기 위해 검색 엔진으로 AWS 관리형 ES(Elasticsearch) 6을 사용하고 있었습니다. 안정적인 서비스 운영이라는 측면에서 AWS 관리형 서비스는 매력적이지만 그만큼 커스텀한 사용에 제한된 부분도 많습니다. 최근 서비스 운영 중 ES 6 에 일부 제한된 기능에 불편함을 느끼고 이를 해소하고자 AWS에게 문의 하게 되었습니다. “준비하고 있다”, “좀 더 상위 버전을 이용해라” 라는 아쉬운 답변밖에 얻지 못하였지만 그 결과 AWS 관리형 ES를 계속해서 사용할 계획이라면 버전 업그레이드가 꼭 필요하겠다는 의사 결정을 할 수 있었습니다. 이번 포스트는 AWS 환경에서 ES 버전 업그레이드를 진행하면서 얻었던 경험을 공유합니다.
AWS 관리형 ES는 특정 버전 별로 지원하는 플러그인이 제한되며 별도의 플러그인 설치도 불가능합니다.
OpenSearch 1.3 업그레이드
이번 업그레이드 목표 버전은 OS(OpenSearch) 1.3입니다. 원래라면 ES 7로 업그레이드해야 하지만 ES는 7.10 버전 이후 라이센스 정책 변경으로 AWS에서 더 이상 사용 불가능하게 되어, OS로 검색 엔진을 변경하게 되었습니다. 이후 ES와 OS를 통틀어 검색엔진이라고 명시하겠습니다.
OS는 아마존이 ES 7.10 에서 포크(Fork)한 오픈 소스 프로젝트로 ES와 높은 호환성을 가지고 있습니다. ES 7.10 버전 안에서는 일부 소수의 기능을 제외하고는 동일한 인터페이스와 기능을 제공하고 있습니다. 심지어 OS에 기존 ES 클라이언트를 그대로 사용하여도 무리가 없을 정도입니다.
물론 ES와 OS 사이에 100% 호환을 보장하는 것은 아니기 때문에 사전에 기능적인 테스트는 필요합니다.
추가로 AWS 비용 절감과 검색엔진 성능 향상을 위해서 인스턴스 타입으로 그래비톤(Graviton) 프로세서 그리고 스토리지 타입으로 gp3로 변경하는 작업을 함께 진행하였습니다.
최근 OS에 적용되는 gp3 스토리지 타입의 볼륨 크기마다 적용될 수 있는 최대 IOPS 및 처리량이 개선되어 대량의 데이터를 처리하는데 좀 더 나은 성능을 기대할 수 있습니다.
인-플레이스 (In-Place) 업그레이드
AWS는 검색 엔진 업그레이드를 위해 다양한 방식을 지원하고 있습니다. 가장 기본적인 방식으로 인-플레이스 업그레이드가 있습니다. 인-플레이스 업그레이드는 AWS 콘솔에서 원하는 버전을 선택하고 몇 번의 클릭만으로 거의 무중단으로 업그레이드를 수행할 수 있습니다.
인-플레이스 업그레이드 다운타임 (Downtime), 전용 마스터 노드가 있는 경우 완전한 무중단 업그레이드가 가능하며, 그렇지 않은 경우는 마스터 노드 선출을 위한 초 단위의 다운 타임이 있을 수 있다고 AWS는 설명하고 있습니다.
다만 일부 버전 구간에서는 제한된 업그레이드-패스(Upgrade-Path)를 가지고 있습니다. 예를 들어 ES 6에서 ES 7로 업그레이드하기 위해서는 반드시 6.8 버전 이상이어야 합니다. 즉, 사용하고 있는 버전이 ES 6.7 이라면 `6.7 → 6.8 → 7.x` 와 같이 두 번의 인-플레이스 업그레이드를 수행해야 합니다.
인-플레이스 업그레이드는 기존 클러스터(Cluster) 환경에 버전만을 변경한 동일한 환경을 구성합니다. 따라서 업그레이드와 동시에 설정 변경이 필요한 경우 별도의 작업이 필요합니다. 변경하고자 하는 설정에 따라 검색엔진을 재배포(Blue/Green Deployment)하거나 변경이 불가능 할 수 있습니다. 이번 업그레이드의 경우, 변경 사항에 포함되었던 인스턴스 타입을 그래비톤 프로세서 타입으로 변경하는 부분이 불가능하여 다른 방법을 찾아야만 했습니다.
원격-리인덱스(Remote-Reindex) 업그레이드
AWS는 검색엔진 도메인(Domain) 간 원격-리인덱스를 지원합니다. 즉 타겟이 되는 버전의 검색엔진 도메인을 새로 생성하고 하위 버전의 기존 도메인과 원격-리인덱스를 수행하는 방식으로 업그레이드를 진행할 수 있습니다. 이후 리인덱스의 대상이되는 두 검색엔진을 타켓-도메인(Target-Domain)과 소스-도메인(Source-Domain)로 명시하겠습니다. 타켓-도메인은 새롭게 생성된 상위버전의 검색엔진이며 소스-도메인은 기존 하위 버전의 검색엔진을 뜻합니다.
AWS OS에서 도메인이란 OS 클러스터와 유사한 의미를 같습니다. 정확하게는 클러스터와 인스턴스 개수, 타입, 스토리지 등 모든 설정의 집합을 의미합니다.
원격-리인덱스는 타켓-도메인이 소스-도메인에게 리인덱스 요청을 보내는 방식으로 동작합니다. 따라서 원격-리인덱스 수행을 위해서, 타겟-도메인은 소스-도메인에 접근 가능한 권한이 있어야 합니다. 스푼라디오 환경의 경우 검색엔진이 VPC 내부에 위치하여 타겟-도메인과 소스-도메인 사이에 커넥션(Connection) 이라는 추가적인 설정이 필요하였습니다.
VPC 내부가 아닌 인터넷 도메인 환경으로 공개적으로 접근이 가능한 경우, 서명된 IAM 증명서(Signed IAM Credentials)가 필요합니다.
커넥션은 그림과 같이 타겟-도메인이 접근이 필요한 소스-도메인에게 접근을 위한 연결을 요청하고 소스-도메인이 이를 승인하는 방식으로 이루어집니다. 커넥션이 완료되면 그 결과로 타겟-도메인이 소스-도메인에 접근할 때 사용할 수 있는 엔드포인트(Endpoint)가 생성됩니다.
커넥션을 포함한 원격-리인덱스에 자세한 내용은 다음 문서 링크에서 확인할 수 있습니다.
이후 타겟-도메인은 아래와 같이 소스-도메인에 원격-리인덱스 요청을 할 수 있습니다.
# 소스에 원격-리인덱스 요청
POST _reindex?wait_for_completion=false
{
"source":{
"remote":{
"host":"{connection-source-endpoint}",
"external": true # 소스와 타겟 둘다 VPC 내부에 존재하는 경우 사용
},
"index":"{source-index-name}"
},
"dest":{
"index":"{target-index-name}"
}
}
Elasticsearch 6 → OpenSearch 1.3 호환성
앞서 언급한 것과 같이 OS 1.3은 ES 7.10과 높은 호환성을 가지고 있습니다. 따라서 ES 6 → OS 1.3 업그레이드 호환성을 고려할 때 ES 6 → ES 7 업그레이드 호환성으로 치환하여 생각하여도 무리가 없습니다. 따라서 이번 설명에서도 ES 6 → ES 7 업그레이드를 언급하면 호환성 문제를 설명하겠습니다.
업그레이드 호환성 문제를 조사할 때 OS 관련 문서가 부족하여 상당 부분 ES 문서를 참조하였습니다.
ES 6 → ES 7 업그레이드는 메이저 버전이 변경된 만큼 단순히 검색엔진 버전만을 올린다면, 기존 검색엔진을 사용하던 애플리케이션 동작에 문제가 있을 수 있습니다. ES 6 → ES 7 업그레이드에서의 가장 큰 변화는 제거된 맵핑-타입(Mapping-Type) 입니다.
맵핑-타입이 무엇이고, 삭제된 이유가 무엇인지는 다음 문서 링크에서 참조할 수 있습니다.
문서에서는 맵핑-타입이 제거됨에 따라 변경되는 다양한 API를 설명하고 있습니다. 언뜻 보기에는 맵핑-타입이 더 이상 사용 불가능한 것으로 보일 수 있으나, 인덱스 맵핑 생성 시 `include_type_name=true` 옵션 추가로 ES 7도 맵핑-타입을 그대로 사용할 수도 있습니다. 이렇게 타입과 함께 생성된 인덱스는 API 변경 없이 그대로 사용이 가능합니다. 즉 검색엔진 업그레이드에 대응한 애플리케이션 코드 수정 없이 동작이 가능합니다.
사실 ES는 6부터 맵핑-타입 제거를 준비하고 있었습니다. 그러한 이유로 ES 6은 기본값으로 맵핑-타입을 사용하게 되어 있지만 선택사항(Optional)으로 사용하지 않을 수도 있습니다. 이후 ES 7은 반대로 기본 값으로 맵핑-타입을 사용하지 않도록 하고 선택사항으로 사용할 수 있으면 ES 8에서는 완전히 제거됩니다.
# mapping-type과 함께 인덱스 생생
PUT /{index-name}?include_type_name=true
{
"mappings": {
"{type}": {
"properties": {
...
}
}
}
}
새로운 검색엔진 버전에 맞게 인덱스에 맵핑-타입을 함께 제거하는 것도 고려 하였으나, 수정해야 하는 애플리케이션 범위가 넓어, 우선 검색엔진 업그레이드를 진행하며 관련 대응은 단계적으로 수행하기로 결정하였습니다.
리인덱스 성능 최적화
PUT {target_index}
{
"settings": {
"refresh_interval": -1,
"number_of_replicas": 0
}
리인덱스 성능 향상을 위해서, 타겟-도메인에 생성된 인덱스에 위와 같은 설정으로 인덱스 리프레싱(Refreshing)과 리플리케이션(Replication)를 비활성화할 수 있습니다.
두 설정에 대한 자세한 내용은 다음 문서 링크에서 확인할 수 있습니다.
리인덱스 수행 시, 각 데이터는 인덱스 수행을 위해서 우선 인-메모리(In-Memory) 버퍼에 적재 됩니다. 버퍼에 적재된 데이터는 `refresh_interval` 주기로 처리 되어 디스크에 써지고, 최종적으로 검색 가능한 상태가 됩니다. 이러한 리프레싱(Refreshing) 작업은 리소스-집약적인(Resource-Intensive) 작업으로, 대량의 데이터를 한번에 인덱스 하는 리인덱스 작업에서 잦은 리프레싱은 성능에 큰 영향을 줄 수 있습니다. `number_of_replicas`도 마찬가지입니다. 인덱스 수행 시 추가로 생성해야 하는 복제본이 있다면 리인덱스 성능에 영향이 있을 수 있습니다. 실제로 약 700만 건의 데이터 기준, 90분 정도 소요되던 리인덱스 작업을 위 설정으로 30분으로 단축할 수 있었습니다.
해당 설정 값은 동적 설정이 가능한 부분으로 리인덱스 전에 비활성화 하며 완료 이후에 다시 원하는 값은 재 설정하여 서비스를 운영할 수 있습니다.
그 외에도 리인덱스 수행 배치-사이즈(Batch-Size) 조정으로도 최적화를 할 수 있습니다. 리인덱스 대상이 되는 도큐먼트(Document) 사이즈가 클수록 배치-사이즈를 작게 설정하는 것을 추천합니다.
검색엔진을 사용한 콘텐츠 관리 구조 in 스푼라디오
아래 그림은 스푼라디오에서 검색엔진을 통해 콘텐츠 정보를 어떻게 관리하고 있는지 보여줍니다. 서비스 내부에 많은 애플리케이션이 존재하지만, 콘텐츠 관점으로만 본다면 크게 콘텐츠 생산자(Contents Provider)와 콘텐츠 소비자(Contents Consumer) 그룹으로 나눌 수 있습니다.
콘텐츠 생산자는 콘텐츠의 생성/변경/삭제 정보를 이벤트 형식으로 발행합니다. 이렇게 발행된 이벤트는 이벤트-큐를 통해 콘텐츠 동기화 서비스(Contents-Synchronizer)에 전달되고 최종적으로 검색엔진에 반영됩니다. 즉 모든 콘텐츠 정보의 변화는 콘텐츠 동기화 서비스를 통해서만 이루어지게 됩니다. 그리고 콘텐츠 소비자는 검색엔진에 질의하여 사용자에게 콘텐츠 정보를 전달합니다.
무중단 검색엔진 업그레이드 in 스푼라디오
검색엔진 업그레이드는 아래 3가지 기본 규칙을 세워서 진행하였습니다.
- 리인덱스 도중 검색엔진 데이터에 변화가 있어서는 안 됩니다. 검색엔진의 리인덱스는 수행 도중 변경된 데이터의 인덱스 적용을 보장하지 않습니다. 즉 리인덱스 수행 중 변경된 정보는 누락될 수 있습니다.
- 리인덱스 동안 발생한 콘텐츠 변화는 누락되어선 안 됩니다. 리인덱스 중 콘텐츠 변화 적용이 지연될 수는 있지만 결과적 일관성(Eventual Consistency)을 유지해 합니다.
- 리인덱스 동안 콘텐츠 질의는 계속해서 가능해야 합니다. 리인덱스 중 콘텐츠 질의 서비스가 중단되어선 안 됩니다. 사용자는 계속해서 서비스 이용이 가능해야 합니다.
이러한 규칙을 따르며 아래와 같은 절차로 검색엔진 업그레이드를 수행하였습니다. 사전에 타켓-도메인 생성 및 설정은 완료된 상태입니다.
- 콘텐츠 동기화 서비스는 리인덱스 수행 동안 일시적으로 콘텐츠 이벤트 소비를 중단(Suspend)합니다. 그 결과 콘텐츠 이벤트는 검색엔진에 적용되지 않고 이벤트-큐에 쌓이게 됩니다. 콘텐츠 소비자는 일부 지연된 콘텐츠를 질의 하겠지만 서비스 사용에는 어려움이 없는 상태입니다.
- 원격-리인덱스를 수행합니다. 리인덱스 수행 도중에도 콘텐츠 소비자는 소스-도메인에 계속해서 콘텐츠 질의가 가능합니다.
- 원격-리인덱스가 완료되면 콘텐츠 소비자는 타켓-도메인을 바라보도록 변경하고 새로운 검색엔진과 정상 동작하는지 확인합니다.
- 콘텐츠 동기화 서비스도 타켓-도메인을 바라보도록 변경하며, 이벤트-큐에 누적되어 있던 콘텐츠 이벤트를 다시 소비하도록 합니다. 이때 리인덱스 동안 발생하였던 콘텐츠 이벤트는 누락 없이 모두 검색엔진에 반영됩니다.
마치며…
이번 검색엔진 업그레이드는 업그레이드할 버전의 타겟-도메인을 생성하고 원격-리인덱스를 수행하는 방식으로 진행하였습니다. 업그레이드와 동시에 인스턴스 타입을 그래비톤으로 함께 변경해야 한다는 제약사항으로 선택한 방법이지만, 대부분의 다른 상황에서는 간편한 인-플레이스 업그레이드로 충분할 것으로 생각됩니다.
최근 사내에서 콘텐츠 검색 품질을 높이기 위해 많은 노력을 하고 있습니다. 이번 검색 엔진 업그레이드도 그 일환 중 하나입니다. 이번 업그레이드로 당장 큰 변화는 없지만 새로운 환경에서 많은 것을 시도해 볼 수 있는 발판을 마련했다고 생각합니다.