Decrypt Elasticsearch: 이 검색 및 분석 엔진에 대한 심층 탐색 | JD Cloud 기술 팀

저자: JD손해보험 Guan Shunli

열리는

최근 Elasticsearch를 사용하여 초상화 시스템을 구현했으며 dmp의 데이터 미들 플랫폼 기능이 실현되었습니다. 동시에 경쟁 제품의 아키텍처 선택을 조사했습니다. 그리고 redis 등의 원칙을 재검토했습니다. 이로써 es에 대한 요약 및 검토를 수행합니다. 나는 인터넷에서 초상화를 완성하기 위해 Elasticsearch를 사용하는 사람을 본 적이 없습니다. 처음으로 해보려고 합니다.

배경을 살펴본 후 메모리 시스템을 데이터베이스로 사용하는 한 가지를 먼저 생각해 봅시다. 그의 강점은 무엇입니까? 그의 고통 포인트는 무엇입니까?

1. 원리

이것은 전체 그림이 아닙니다. 의사 소통, 기억 및 지속성에 대해 이야기하십시오.

의사소통

es 클러스터의 최소 단위는 3개의 노드입니다. 고가용성을 보장하기 위해 두 개의 슬레이브 노드를 결합하는 것도 클러스터링의 기본입니다. 그렇다면 노드 간의 RPC 통신에 사용되는 것은 무엇입니까? netty여야 합니다. es는 netty를 기반으로 Netty4Transport의 통신 패키지를 실현합니다. Transport를 초기화한 후 Bootstrap을 생성하고 MessageChannelHandler를 통해 수신 및 전달을 완료합니다. es에서 서버와 클라이언트는 그림 1과 같이 구별됩니다. 직렬화에 사용되는 json입니다. ES는 rpc 설계에서 사용하기 쉽고 범용적이며 이해하기 쉬운 경향이 있습니다. 성능만을 추구하기보다.

그림 1

netty의 에스코트로 es는 json 직렬화를 사용할 수 있습니다.

메모리

그림 2

es 메모리는 [on heap]과 [off heap]의 두 부분으로 나뉩니다. 온 힙 부분은 es의 jvm에 의해 관리됩니다. 오프 힙은 lucene에 의해 관리됩니다. 온 힙은 두 부분으로 나뉘는데 한 부분은 재활용할 수 있고 다른 부분은 재활용할 수 없습니다.

재활용할 수 있는 인덱스 버퍼의 일부는 새 인덱스 문서를 저장합니다. 채워지면 버퍼의 문서가 디스크 세그먼트에 기록됩니다. 모든 샤드는 노드에서 공유됩니다.

노드 쿼리 캐시, 샤드 요청 캐시, 파일 데이터 캐시, 세그먼트 캐시는 재활용할 수 없습니다.

노드 쿼리 캐시는 점수 매기기를 해제하기 위해 bitset 데이터 구조(Bloom 최적화 버전)를 사용하여 모든 샤드에서 공유하는 각 노드에서 필터링 및 저장되는 노드 수준 캐시입니다. 사용할 LRU 제거 전략입니다. GC는 재활용할 수 없습니다.

샤드 요청 캐시는 샤드 수준 캐시이며 각 샤드에는 캐시가 있습니다. 기본적으로 캐시는 요청 결과 크기가 0인 쿼리만 저장합니다. 따라서 캐시는 적중이 아니지만 hit.total, 집계, 제안은 캐시됩니다. 캐시 지우기 API를 통해 지울 수 있습니다. 사용할 LRU 제거 전략입니다. GC는 재활용할 수 없습니다.

파일 데이터 캐시는 집계 및 정렬된 데이터를 캐시하는 것입니다. 초기에는 es에 doc 값이 없었기 때문에 집계 및 정렬 후 디스크 IO를 피하기 위해 캐싱을 위한 파일 데이터가 필요했습니다. 파일 데이터를 저장할 메모리가 충분하지 않으면 es는 지속적으로 디스크에서 메모리로 데이터를 로드하고 오래된 데이터를 삭제합니다. 이로 인해 디스크 IO가 발생하고 GC가 트리거됩니다. 따라서 2.x 이후 버전에서는 doc values ​​기능을 도입하고, indextime에 문서를 빌드하고, 디스크에 저장하고, 메모리 매핑된 파일을 통해 액세스합니다. hits.total에만 관심이 있더라도 doc id만 반환하고 doc 값을 끕니다. 문서 값은 키워드 및 숫자 유형을 지원합니다. 텍스트 유형은 여전히 ​​파일 데이터를 생성합니다.

세그먼트 캐시는 쿼리 속도를 높이는 데 사용되며 FST는 힙 메모리에 영원히 상주합니다. FST는 쿼리 속도를 높이는 접두사 트리로 이해할 수 있습니다. 하지만! ! es 7.3 버전은 노드가 더 많은 데이터를 지원할 수 있도록 FST를 오프 힙 메모리로 넘기기 시작했습니다. FST는 또한 디스크에 해당 영구 파일이 있습니다.

오프 힙은 세그먼트 메모리이고 오프 힙 메모리는 Lucene에서 사용합니다. 따라서 메모리의 절반 이상을 Lucene용으로 남겨두는 것이 좋습니다.

es 7.3 버전은 mmp를 통해 tip(terms index)을 로드하기 시작하고 관리를 위해 시스템의 pagecache에 넘겨줍니다. tip을 제외하고 nvd(norms), dvd(doc 값), tim(용어 사전), cfs(복합) 파일은 모두 mmp로 로드 및 전송되며 나머지는 모두 nio입니다. 팁 오프 힙의 효과는 jvm 사용량이 약 78% 감소한다는 것입니다. _cat/segments API를 사용하여 segment.memory의 메모리 사용량을 볼 수 있습니다.

외부 메모리는 운영 체제 페이지 캐시에서 관리하기 때문입니다. 재활용이 발생하면 FST 쿼리에 디스크 IO가 포함되어 쿼리 효율성에 큰 영향을 미칩니다. 이중 체인 전략을 사용하려면 Linux 페이지 캐시의 재활용 전략을 참조할 수 있습니다.

지구화

es의 지속성은 두 부분으로 나뉘는데, 한 부분은 스냅샷과 유사하고 파일 캐시의 세그먼트는 디스크로 새로 고쳐집니다(fsync). 다른 부분은 초당 작업 로그를 추가하고 기본적으로 30분마다 디스크에 플러시하는 translog 로그입니다. es 지속성은 redis의 RDB+AOF 모드와 매우 유사합니다. 아래 그림과 같이

이미지 3

위의 그림은 전체 작성 과정입니다. 디스크는 또한 세그먼트에 데이터를 기록합니다. 여기에서는 redis와 매우 유사합니다. 그러나 내부 메커니즘은 COW(copy-on-write)를 사용하지 않습니다. 병렬로 쿼리하고 쓸 때 부하가 꽉 차는 이유이기도 합니다.

요약

es 메모리와 디스크의 디자인은 매우 영리합니다. 제로 카피에서는 mmap 방식을 사용하고, 디스크 데이터는 오프 힙, 즉 루씬에 매핑한다. 데이터 액세스 속도를 높이기 위해 es의 각 세그먼트에는 오프 힙에 상주하는 일부 인덱스 데이터가 있으므로 세그먼트가 많을수록 더 많은 오프 힙이 분할되며 GC에서 재활용할 수 없습니다!

위의 두 가지 사항을 종합하면 es가 왜 그렇게 많은 메모리를 소비하는지 명확하게 알 수 있습니다.

2. 신청

사용자 초상화 시스템에서 다음과 같은 어려움을 해결해야 합니다.

1. 군중 추정: 전자 상거래 소셜 네트워킹을 좋아하는 20-25세 남성과 같이 태그를 기반으로 사람들의 그룹을 선택합니다. 20~25세 ∩ 전자상거래 SNS ∩ 남성. AND 또는 NOT 연산을 통해 특성을 충족하는 clientId의 수를 선택합니다. 이것은 그룹입니다.

그룹과 그룹 전에 교집합 및 차이 연산을 수행할 수도 있습니다. 예를 들어, 그는 전자상거래와 사교를 좋아하는 20-25세의 남성이고 철을 좋아하는 베이징의 남성입니다. (20-25세 ∩ 전자상거래 SNS ∩ 남성) ∩ (20-25세 ∩ 철 ∩ 남성). 이러한 재귀를 위해서는 17억 개가 넘는 초상화 라이브러리에서 몇 초 만에 예상 인물 수를 반환해야 합니다.

2. 크라우드 패키지 서클 선택: 위 서클에서 선택한 크라우드 패키지. 분 빌드가 필요합니다.

3. 사람 패키지 판단: clientId가 여러 군중 패키지에 존재하는지 판단합니다. 결과를 반환하는 데 10밀리초가 필요합니다.

먼저 위의 모든 문제를 해결하기 위해 es를 사용하려고 합니다.

크라우드 추정의 경우 생각할 수 있는 가장 쉬운 솔루션은 서버의 메모리에서 논리 연산을 수행하는 것입니다. 하지만 수천만 명을 선택해 몇 초 만에 돌려준다면 서버 측에서 하기엔 비용이 많이 든다. 이때 데이터베이스를 쿼리하는 것처럼 es 스토리지 측에 컴퓨팅 압력을 가할 수 있습니다. 문을 사용하여 원하는 데이터를 찾으십시오.

예를 들어 mysql

select a.age from a where a.tel in (select b.age from b);

es의 해당 dsl은 다음과 유사합니다.

{"query":{"bool":{"must":[{"bool":{"must":[{"term":{"a9aa8uk0":{"value":"age18-24","boost":1.0}}},{"term":{"a9ajq480":{"value":"male","boost":1.0}}}],"adjust_pure_negative":true,"boost":1.0}},{"bool":{"adjust_pure_negative":true,"boost":1.0}}],"adjust_pure_negative":true,"boost":1.0}}}

이러한 방식으로 es의 높은 검색 성능은 비즈니스 요구를 충족하는 데 사용됩니다. 그룹 수와 그룹의 레이블 수에 관계없이. 모두 dsl 문에 입력되었습니다. 결과가 몇 초 안에 반환되도록 합니다.

공식적으로 권장되는 RestHighLevelClient를 사용하면 세 가지 구현 방법이 있습니다. 하나는 json 문자열을 철자하는 것이고 두 번째는 api를 호출하여 문자열을 철자하는 것입니다. 나는 더 우아하게 달성하기 위해 세 번째 방법인 BoolQueryBuilder를 사용합니다. 필터, must, should 및 mustNot 메서드를 제공합니다. 좋다

     /**
     * Adds a query that <b>must not</b> appear in the matching documents.
     * No {@code null} value allowed.
     */
    public BoolQueryBuilder mustNot(QueryBuilder queryBuilder) {
        if (queryBuilder == null) {
            throw new IllegalArgumentException("inner bool query clause cannot be null");
        }
        mustNotClauses.add(queryBuilder);
        return this;
    }

    /**
     * Gets the queries that <b>must not</b> appear in the matching documents.
     */
    public List<QueryBuilder> mustNot() {
        return this.mustNotClauses;
    }

API를 사용하면 코드를 컴파일하는 능력을 크게 보여줄 수 있습니다.

군중 팩을 만드십시오. 현재 우리가 둘러본 가장 큰 패키지에는 7천만 개 이상의 clientId가 있습니다. 분 단위로 구축을 완료하려면(조건부 제한 하에서 35분 안에 7천만 데이터 완료) 두 가지 사항에 주의해야 합니다. 하나는 es 심층 쿼리이고 다른 하나는 일괄 작성입니다.

espagination에는 3가지 방식이 있고 deep paging에는 2가지 방식이 있으며 후자의 2가지 방식은 커서로 스크롤(scroll, search_after)하여 검색한다.

스크롤은 커서 상태를 유지해야 하며 각 스레드는 32비트 고유 스크롤 ID를 생성하고 각 쿼리는 고유 스크롤 ID를 가져와야 합니다. 여러 스레드가 여러 커서 상태를 유지해야 하는 경우. search_after는 스크롤과 유사합니다. 그러나 해당 매개변수는 상태 비저장이며 항상 최신 버전의 검색기에 대해 구문 분석됩니다. 정렬 순서는 스크롤 시 변경됩니다. 스크롤의 원칙은 코디네이팅 노드의 컨텍스트에서 설정된 doc id 결과를 유지하고 스크롤할 때마다 배치로 가져오는 것입니다. 크기에 따라 각 샤드 내에서 순서대로 결과를 검색하기만 하면 됩니다.

작성 시 스레드 풀을 사용하고, 사용된 차단 대기열의 크기에 주의를 기울이고, 적절한 거부 전략을 선택합니다(여기서는 예외를 던지는 전략이 필요하지 않음). 배치가 여전히 es에 기록되는 경우(예: 읽기-쓰기 분리가 수행됨) 다중 스레딩 외에도 쓰기를 최적화하기 위한 새로 고침 정책도 있습니다.

휴먼 패킷 결정 인터페이스의 경우 전체 비즈니스 링크가 매우 길기 때문에 이 검색을 위해 업스트림 서비스에서 설정한 융합 시간은 10ms입니다. 따라서 es(또는 redis)를 최적화하기 위한 쿼리는 결국 논리 처리를 담당하지 않습니다. 스레드 풀을 사용하여 IO 집약적 최적화를 해결한 후 1ms에 도달할 수 있습니다. tp99는 4ms에서 피크입니다.

3. 최적화, 병목 현상 및 솔루션

위는 비즈니스 요구에 es를 사용하는 문제 해결 방법입니다. 응답 최적화도 필요합니다. 동시에 es의 병목 현상도 만났습니다.

1. 첫 번째는 매핑의 최적화입니다. 이미지 맵핑의 필드에 있는 유형은 키워드이며 색인은 꺼야 합니다. 휴먼 패키지의 필드에 있는 doc 값이 꺼져 있습니다. 초상화는 정확한 일치를 위한 것입니다. 휴먼 패키지 판단에는 결과만 필요하고 값은 필요하지 않습니다. es api에서 휴먼 패키지 계산은 필터를 사용하여 점수를 제거하고 bitset의 Bloom 데이터 구조는 필터 내부에서 사용되지만 데이터는 예열되어야 합니다. 작성할 때 너무 많은 스레드를 갖는 것은 쉽지 않으며 코어 수와 동일하게 유지하고 새로 고침 정책 수준을 조정하십시오. 수동 디스크 새로 고침, index.refresh_interval은 구성 중에 -1로 조정되며, 디스크 새로 고침을 중지하면 힙 메모리가 증가하므로 디스크 새로 고침 빈도는 업무에 따라 조정해야 합니다. 대규모 크라우드 패키지를 구축하려면 인덱스를 여러 개로 분할할 수 있습니다. 분산형 스토리지는 응답성을 향상시킬 수 있습니다. 현재 수십 개의 군중 팩을 계속 지원할 수 있습니다. 앞으로 수백개로 늘어난다면. 군중 패키지를 빌드하고 저장하려면 비트맵을 사용해야 합니다. es는 검색 성능이 우수합니다. 그러나 쓰기 작업과 쿼리 작업을 병렬로 접할 때 그가 잘하는 것은 아닙니다. 예를 들어 군중 패키지의 데이터는 매일 변경됩니다. 이때 es의 메모리와 디스크 io는 매우 높을 것입니다. Redis를 사용하여 수백 개의 패키지를 저장할 수 있습니다. MongoDB를 사용하여 패키지 데이터를 저장하도록 선택할 수도 있습니다.

4. 요약

위 내용은 Elasticsearch를 사용하여 비즈니스의 어려움을 해결하는 것입니다. 동시에 그의 집념은 COW(copy-on-write) 방식을 사용하지 않은 것으로 밝혀졌다. 결과적으로 실시간 쓰기 중에 검색 성능이 저하됩니다.

메모리 내 시스템을 데이터 소스로 사용하는 것은 블록을 검색하는 것만으로도 다소 명백합니다! 특히 실시간 시나리오에서는 날카로운 무기라고 할 수 있습니다. 동시에, 페인 포인트도 분명합니다.실시간 쓰기는 검색 성능을 저하시킵니다. 물론 읽기-쓰기 분리, 인덱스 분할 및 기타 솔루션을 사용할 수 있습니다.

Elasticsearch 외에도 ClickHouse도 선택할 수 있으며 ck도 비트맵 데이터 구조를 지원합니다. BitMap Database인 Pilosa로 이동할 수도 있습니다.

참고

Keike DMP 플랫폼 구축 실습

매핑 매개변수 | Elasticsearch 참조 [7.10] | 탄력있는

Elasticsearch 7.3의 오프힙 원칙

{{오.이름}}
{{이름}}

추천

출처my.oschina.net/u/4090830/blog/8704613