더 나은 사용자 경험을 위해 느린 Elasticsearch 쿼리 문제를 해결하는 방법

Elastic Cloud(Elasticsearch Service/ESS) 사용자를 위한 중요 참고 사항: 현재 이 문서에서 언급된 콘텐츠는 Elastic Cloud에서 사용할 수 없습니다. 그러나 저희는 소중한 사용자들의 의견을 수집하고자 합니다. 이 기능을 사용하는 데 관심이 있으시면 Elastic 지원으로 연락해 주시기 바랍니다.

Elasticsearch® 를 검색 엔진으로 사용하는 모든 사용자에게 쿼리를 식별하고 문제를 해결하는 것은 반드시 숙달해야 할 중요한 기술입니다. 전자상거래, 통합 가시성 또는 업무용 검색 솔루션 등 무엇이든 Elasticsearch가 느려지면 사용자 경험에 부정적인 영향을 미칩니다.

느린 Elasticsearch 쿼리를 정확히 찾아내려면 느린 로그를 사용할 수 있으며, 이는 특정 임계값에서 실행되는 쿼리를 캡처합니다. 느린 로그 임계값을 올바르게 설정하는 것 자체가 어려운 과제입니다. 예를 들어, 전체 부하 상태에서 500밀리초가 걸리는 쿼리는 허용될 수 있지만, 부하가 낮은 상태에서는 동일한 쿼리가 허용되지 않을 수 있습니다. 느린 로그는 이를 구분하지 않고 500밀리초 이상의 모든 항목을 기록합니다. 느린 로그는 제 역할을 매우 잘 수행하므로 임계값에 따라 다양한 수준의 세분성을 캡처할 수 있습니다. 대신 추적(Tracing) 기능을 사용하면 모든 쿼리를 살펴보고 특정 임계값 내에 있는 쿼리가 몇 개인지 식별할 수 있습니다.

애플리케이션 성능 모니터링(APM)은 더 이상 애플리케이션에만 국한되지 않습니다. Elasticsearch의 계측을 사용하여 이제 Elasticsearch를 애플리케이션 스택의 종속성이 아닌 완전한 서비스로 추가할 수 있습니다. 이러한 방식으로 슬로우 로그가 제공하는 것보다 더 세밀한 성능 보기를 얻을 수 있습니다.

다음 예제에서 데이터 말뭉치는 OpenWebText이며, 이는 약 40GB의 순수 텍스트와 약 800만 개의 개별 문서를 제공하고 32GB RAM이 탑재된 M1 Max Macbook에서 로컬로 실행됩니다.

시작하기

Elasticsearch에서 추적을 활성화하는 작업은 정적 설정(elasticsearch.yml에서 구성)과 런타임 중에 PUT _cluster/settings 명령을 사용하여 전환할 수 있는 동적 설정을 통해 수행되며, 이러한 동적 설정 중 하나가 샘플링 속도입니다. 샘플링 속도와 같은 일부 설정은 런타임 중에 전환할 수 있습니다. elasticsearch.yml 에서 다음을 설정합니다:

버전 9.x에 유효

telemetry.agent.enabled: true
telemetry.agent.server_url: "url of the APM server"

버전 7.x 및 8.x에 유효

tracing.apm.enabled: true
tracing.apm.agent.server_url: "url of the APM server"

비밀 토큰 (또는 API 키)은 Elasticsearch 키스토어에 있어야 해요. 키스토어 도구는 <your elasticsearch install directory>/bin/Elasticsearch-keystore 에서 다음 명령을 사용하여 버전 7.x 및 8.x에서 Elasticsearch-keystore add tracing.apm.secret_token 또는 tracing.apm.api_key 를 사용할 수 있어야 해요. 버전 9.x의 경우 텔레미터.비밀_토큰 또는 텔레미터.api_key 대신 사용하세요. 그 후에는 Elasticsearch를 다시 시작해야 해요.추적에 대한 자세한 내용은 추적 문서.

APM이 활성화되면 Kibana에서 APM 뷰를 확인하여 Elasticsearch가 다양한 REST API 엔드포인트를 자동으로 캡처하는 것을 볼 수 있습니다. 여기서는 주로 POST /{index}/_search 호출에 초점을 맞추고 이를 통해 무엇을 얻을 수 있는지 살펴보겠습니다.

Elasticsearch 스크린샷

GET /{index}/_search 상자에서 간단한 쿼리를 직접 검사하여 다음 워터폴 분석을 확인합니다. 여기에는 Elasticsearch가 내부적으로 수행하는 작업에 대한 더 깊은 인사이트를 제공하는 내부 스팬이 포함되어 있습니다. 또한 이 검색의 전체 소요 시간(86밀리초)을 확인할 수 있습니다.

추적 샘플

쿼리에 수반되는 메타데이터에는 HTTP 헤더, 사용자 에이전트, Elasticsearch Node 위치(클라우드 서비스 제공자 메타데이터, 호스트 이름, 컨테이너 정보), 일부 시스템 정보 및 URL 세부 정보에 관한 광범위한 정보가 포함됩니다. 기본적인 트랜잭션 정보를 사용하여 평균 트랜잭션 기간을 플로팅하는 Lens 차트를 생성하고, 상승 또는 하락 추세가 있는지 확인할 수 있습니다.

당사의 검색 애플리케이션

더 이상 느린 로그를 사용할 필요가 없어서 좋습니다! 트랜잭션 지속 시간을 확인하고 특정 임곗값 미만으로 응답된 검색 횟수를 파악할 수 있습니다. 하지만 한 가지 단점이 있습니다. Elasticsearch는 전송된 쿼리를 캡처하지 않으므로 쿼리에 오랜 시간이 걸렸다는 것은 알 수 있지만, 해당 쿼리가 무엇인지는 알 수 없습니다.

샘플 검색 애플리케이션을 계측해 보겠습니다. 이 사례에서는 두 개의 경로인 search_singlesearch_phrase를 사용하는 간단한 Flask 앱을 사용할 것이며, 이는 Elasticsearch에서 matchmatch_phrase 쿼리를 나타냅니다. 예를 들어, 다음과 같은 쿼리를 사용할 수 있습니다:

{
  "query": {
    "match": {
      "content": "support"
    }
  }
}
And
{
  "query": {
    "match_phrase": {
      "content": "support protest"
    }
  }
}

다음 Flask 코드는 search_single 경로를 구현합니다.search_phrase는 match 대신match_phrase 를 사용한다는 점을 제외하면 매우 유사합니다.

@app.route("/search_single", methods=["GET"])
def search_single():
    query = request.args.get("q", "")
    if not query.strip():
        return jsonify({"error": "No search query provided"}), 400
    try:
        result = es.search(
            index=ES_INDEX, query={"match": {"content": query}}
        )

        hits = result["hits"]["hits"]
        response = []
        for hit in hits:
            response.append(
                {
                    "score": hit["_score"],
                    "content": hit["_source"]["content"],
                }
            )
        
        return jsonify(response)

모든 준비가 완료되었으므로, 이제 curl -XGET "http://localhost:5000/search_single?q='microphone'" 를 호출하여 microphone이라는 용어를 검색할 수 있습니다.

저희는 주로 Observe를 위해 검색 애플리케이션에 APM을 추가하지만, APM 에이전트가 나가는 요청을 캡처하여 메타데이터 정보로 보강합니다. 저희의 경우, span.db.statement 에 Elasticsearch 쿼리가 포함됩니다. 그리고 아래의 이 사례에서는 누군가가 window를 검색했습니다.

스팬 세부 사항

한 곳에 모으기

Flask 서비스에서 쿼리 크기를 5,000으로 설정했습니다. 이는 Elasticsearch가 단일 JSON 응답으로 최대 5,000개의 일치하는 문서를 제공해야 함을 의미합니다. 이는 큰 수치이며, 해당 양의 문서를 디스크에서 검색하는 데 많은 시간이 소요됩니다. 상위 100개의 문서로 변경한 후, 이를 비교하여 대시보드에서 어떤 일이 발생했는지 빠르게 파악할 수 있습니다.

APM 뷰에서 트랜잭션을 확인하고 임계 경로에 대한 랩(labs) 기능을 활성화하면 오버레이가 생성되어 애플리케이션이 어디에서 시간을 소비하고 있는지 보여줍니다.

APM 보기 타임라인

그 후, transaction.duration.us, es_query_took, transaction.name 필드를 사용하여 대시보드를 생성했습니다. 일반 KQL 필터에는 service.name, processor.event: transaction, transaction.name: POST /{index}/_search가 포함됩니다.

추가 팁: Data view 관리로 이동하여 APM 데이터 스트림이 포함된 Data view를 선택하고 transaction.duration.us 필드를 선택한 다음 형식을 duration 으로 변경하십시오. 이제 마이크로초 대신 사람이 읽을 수 있는 출력으로 자동 렌더링됩니다.

Lens 주석 기능을 활용하면, 가운데 Lens에서 100개의 문서로 변경한 결과 평균 검색 트랜잭션이 크게 감소했음을 확인할 수 있습니다. 뿐만 아니라 오른쪽 상단 모서리에 있는 전체 레코드 개수를 확인해 보십시오. 더 빠르게 검색할 수 있으므로 처리량이 더 높습니다! 저는 히스토그램을 매우 좋아해서 맨 위 행 중앙에 하나를 만들었습니다. 여기서는 X축에 트랜잭션 기간을, Y축에 레코드 개수를 설정했습니다. 또한 APM은 메트릭을 제공하므로 언제든지 CPU% 사용량은 물론 JVM 힙, 비힙(non-heap) 사용량, 스레드 수 및 기타 유용한 정보를 확인할 수 있습니다.

그래프 및 차트

결론

이 블로그 게시물에서는 Elasticsearch를 계측된 애플리케이션으로 활용하여 병목 현상을 훨씬 더 쉽게 식별하는 것이 얼마나 중요한지 보여드렸습니다. 또한 트랜잭션 지속 시간을 이상 징후 탐색을 위한 지표로 사용하고, 애플리케이션에 대한 A/B 테스트를 수행할 수 있으며, 이제 그 질문에 답할 수 있는 데이터가 있으므로 Elasticsearch가 더 빠르게 느껴지는지 더 이상 고민할 필요가 없습니다. 또한 사용자 에이전트에서 쿼리에 이르기까지 수집된 모든 메타데이터는 문제 해결에 도움을 줍니다.

대시보드와 Data view는 여기에서 가져올 수 있습니다.

경고

Elasticsearch 내 트랜잭션 지속 시간에 문제가 있습니다. 이 문제는 8.9.1 릴리즈에서 수정될 예정입니다. 그때까지는 트랜잭션이 잘못된 클록을 사용하여 전체 지속 시간에 영향을 줍니다.

이 게시물에서 설명된 모든 기능이나 성능의 출시와 일정은 Elastic의 단독 재량에 따라 결정됩니다. 현재 제공되지 않는 기능이나 성능은 예정된 시간에 출시되지 않을 수도 있으며 아예 제공되지 않을 수도 있습니다.