본문 바로가기
ElasticSearch

엘라스틱서치 데이터 다루기 part 3 검색

by Salt-Fn 2024. 7. 18.

3. 검색 API

이번 포스트에서는 주요 검색 쿼리, 쿼리 문맥과 필터 문맥, 검색 결과 정렬, 페이지네이션에 대해 작성한다.

3.1 검색 대상 지정

GET [인덱스 이름]/_search
POST [인덱스 이름]/_search
GET _search
POST _search

GET과 POST 중 무엇을 사용해도 동작은 동일하다. 인덱스 이름을 지정하지 않으면 전체 인덱스에 대해 검색한다. 인덱스 이름을 지정할 때는 와일드카드 문자(*) 를 사용할 수 있따. 콤마를 이용해 검색 대상을 여럿 지정 가능하다.

ex) GET my_index*,mapping_test/_search

3.2 쿼리 DSL 검색과 쿼리 문자열 검색

요청 본문에 엘라스틱서치 전용 쿼리 DSL을 기술하여 검색하는 방법과 요청 주소줄에 q라는 매개변수를 넣고 그곳에 루씬 쿼리 문자열을 지정해 검색하는 방법이 있따. 두 방법을 혼용할 수는 없다. 만약 두 방법이 혼용되어 있다면 q매개변수가 우선 작동한다.

  • 쿼리 DSL 검색
GET my-index-01/_search
{
  "query": {
    "match": {
      "title": "hello"
    }
  }
}

요청 본문에 query 필드를 넣어 그 안에 원하는 쿼리와 질의어를 기술한다.

  • 쿼리 문자열 검색
GET my_index-01/_search?q=title:hello

q 매개변수에 루씬 쿼리 문자열을 넣었다. 주소줄에 쿼리를 기술하는 요청 방식 특성상 복잡한 쿼리를 지정하기 어렵고 긴 쿼리를 전달하는 것도 부담스럽다. 루씬 쿼리 문자열 지정 검색은 간단한 요청을 이용하는 경우 유용하다.

문법 설명 예시
질의어만 기술 전체 필드를 대상으로 검색 hello
필드이름:질의어 지정한 필드를 대상으로 검색 title:hello
title:(hello OR world)
_exists_:필드이름 특정 필드가 존재하는 문서를 검색 _exists_:title
필드이름:[시작 to 끝]
필드이름:{시작 to 끝}
지정한 필드가 시작값부터 끝값 사이에 있는 문서를 검색. []는 경계값 포함, {}는 미포함.
경계값이 없는 경우 *사용
date:[2021-05-03 TO 2021-09-03]
count:{10 TO 20]
date:{* TO 2021-09-03}
count:[20 TO *]
질의어에 * 또는 ? 포함 질의어로 와일드카드 문자* 와 ? 를 사용할 수 있다. 와일드카드를 포함한 쿼리는 매우 느리다. title:hello*
AND
OR
NOT
()
쿼리에 AND, OR, NOT 연산을 적용할 수 있으며 ()로 연산 순서 지정 가능 (title:(hello OR world)) AND
(contents: NOT bye) OR count: [1 TO 3]

indices.query.quer_string.allowLeadingWildcard 설정을 false로 지정하면 와일드카드 문자가 앞에 오는 쿼리를 막을 수 있다.

search.allow_expensive_queries 설정을 false로 지정하면 와일드카드 검색을 포함한 몇몇 무거운 쿼리를 사용한 검색 자체를 아예 막을 수 있다.

3.3 match_all 쿼리

match_all 쿼리는 모든 문서를 매치하는 쿼리다. query 부분을 비워두면 기본값으로 지정되는 쿼리이다.

GET my-index-01/_search
{
  "query": {
    "match_all": {}
  }
}

3.4 match 쿼리

match쿼리는 지정한 필드의 내용이 질의어와 매치되는 문서를 찾는 쿼리다. 필드가 text 타입이라면 필드의 값도 질의어도 모두 애널라이저로 분석된다.

GET my-index-01/_search
{
  "query": {
    "match": {
      "fieldName": {
        "query": "test query sentence"
      }
    }
  }
}

fieldName이 text타입이고 standard 애널라이저를 사용한다면 test query sentence는 test, query, sentence 총 3개의 토큰으로 분석된다. match쿼리는 fieldName의 값을 분석해서 만든 역색인에서 이 3개의 텀을 찾아 매치되는 문서를 반환한다. 이 때 match 쿼리는 OR 조건으로 동작하는데 fieldName이 sentence structrue인 문서가 있다면 이 문서는 sentence 텀으로 매치되고 test 텀이나 query텀으로는 매치되지 않지만 최종결과에 남는다. operator를 and로 지정하면 모든 텀이 매치된 문서만 검색되도록 변경할 수 있다.

GET my-index-01/_search
{
  "query": {
    "match": {
      "fieldName": {
        "query": "test query sentence",
        "operator": "and"
      }
    }
  }
}

3.5 term 쿼리

term쿼리는 지정한 필드의 값이 질의어와 정확히 일치하는 문서를 찾는 쿼리다. 대상 필드에 노멀라이저가 지정돼 있다면 질어의도 노멀라이저 처리를 거친다.

GET my-index-01/_search
{
  "query": {
    "term": {
      "fieldName": {
        "value": "hello"
      }
    }
  }
}

term 쿼리를 문자열 필드를 대상으로 사용할 때는 keyword 타입과 잘 맞는다. 질의어도 문서도 같은 노멀라이저 처리를 거치므로 직관적으로 사용할 수 있다.

 

text필드에 term쿼리를 사용할 경우 질의어는 노멀라이저 처리를 거치지만 필드의 값은 애널라이저로 분석한 뒤 생성된 역색인을 이용하게 된다. 분석 결과 단일텀이 생성됐고 그 값이 노멀라이저를 거친 질의어와 완전히 같은 경우에만 검색에 걸린다.

구분 term 쿼리 match 쿼리
사용 목적 정확한 값 매칭 애널라이저 분석을 통한 단어 단위 매칭
질의어 필드 value query
검색 방식 노멀라이저로 필드의 값 분석 후 검색 애널라이저로 토큰화하여 검색
주요 사용 사례 키워드, ID, 상태값 등의 정확한 매칭이 필요한 경우 텍스트 필드 (예: 문서 본문, 제목)에서 의미 있는 단어 검색
분석기 사용 여부 노멀라이저 애널라이저
예시 {
  "query": {
    "term": {
      "fieldName": {
        "value": "elasticsearch"
      }
    }
  }
}
{
  "query": {
    "match": {
      "fieldName": {
        "query": "test query sentence",
        "operator": "and"
      }
    }
  }
}
필드 타입 분석되지 않은 키워드, 숫자 ,날짜 등 텍스트 필드

3.6 terms 쿼리

term 쿼리와 유사하다. 질의어를 여러 개 지정할 수 있으며, 하나 이상의 질의어가 일치하면 검색 결과에 포함된다.

{
  "query": {
    "terms": {
      "fieldName": ["hello", "world"]
    }
  }
}

3.7 range 쿼리

지정한 필드의 값이 특정 범위 내에 있는 문서를 찾는 쿼리다.

GET [인덱스 이름]/_search
{
  "query": {
    "range": {
      "fieldName": {
        "gte": 10,
        "lte": 20
      }
    }
  }
}

범위는 gt, lt, gte, lte를 이용하여 지정한다.

gt (greater than) 초과
lt (less than) 미만
gte (greater than or equal to) 이상
lte (less than or equal to) 이하

문자열 필드를 대상으로 한 range 쿼리는 부하가 큰 쿼리로 분류된다. 와일드카드 쿼리와 마찬가지로, search.allow_expensive_queries 설정을 false로 지정하면 이러한 부하가 큰 쿼리(예: 문자열 필드에 대한 range 쿼리, 와일드카드 쿼리)를 제한할 수 있다.

 

range 쿼리의 대상 필드가 date 타입이면 간단한 날짜 시간 계산식도 사용할 수 있다.

GET [인덱스 이름]/_search
{
  "query": {
    "range": {
      "dateField" :{
        "gte": "2024-07-01T00:00:00.000Z || +36h/d",
        "lte": "now-3h/d"
      }
    }
  }
}
  • now : 현재 시각
  • || : 날짜 시간 문자열의 마지막에 붙인다. 이 뒤 붙는 문자열은 시간 계산식으로 파싱된다.
  • + - :  지정된 시간만큼 더하거나 빼는 연산 수행한다.
  • / : 버림 수행. 예를 들어 /d는 날짜 단위 이하의 시간을 버림한다.
    ex) now-3h/d (now는 2024-07-17T15:00:00.000Z 기준):
    2024-07-17T15:00:00.000Z -> 2024-07-17T00:00:00.000Z

3.8 prefix 쿼리

필드의 값이 지정한 질의어로 시작하는 문서를 찾는 쿼리다.

GET [인덱스 이름]/_search
{
  "query": {
    "prefix": {
      "title": {
        "value": "python"
      }
    }
  }
}

prefix도 무거운 쿼리로 분류된다. 만약 prefix 쿼리를 서비스 호출 용도로 사용하려 한다면 매핑에 index_prefixes 설정을 넣는 방법도 있다.

PUT prefix_mapping_test
{
  "mappings": {
    "properties": {
      "prefixField": {
        "type": "text",
        "index_prefixes": {
          "min_chars": 3,
          "max_chars": 5
        }
      }
    }
  }
}

index_prefixes를 지정하면 문서를 색인할 때 min_chars와 max_chars 사이의 prefix를 미리 별도 색인힌다. 색인 크기와 색인 속도에서 손해를 보는 대신 prefix 쿼리의 성능을 높인다. min_chars의 기본값은 2, max_chars의 기본값은 5다.

search.allow_expensive_queries 설정을 false로 지정하면 index_prefixes가 적용되지 않은 prefix 쿼리는 사용할 수 없다.

3.9 exists 쿼리

지정한 필드를 포함한 문서를 검색한다.

GET [인덱스 이름]/_search
{
  "query": {
   "exists": {
      "field": "fieldName"
    }
  }
}

3.10 bool 쿼리

여러 쿼리를 조합하여 검색하는 쿼리다. must, must_not, filter, should 4가지 종류의 조건절에 다른 쿼리를 조합하여 사용한다.

GET lecture/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "FIELD1": { "value": "VALUE"}}},
        {"term": { "FIELD2": { "value": "VALUE"}}}
      ],
      "must_not": [
        {"term": { "FIELD4": {"value": "VALUE"}}}
      ],
      "filter": [
        {"term": { "FIELD5": { "VALUE": true}}}
      ],
      "should": [
        {"match": {"FIELD": {"query": "TEXT"}}},
        {"match": {"FIELD": {"query": "TEXT"}}}
      ],
      "minimum_should_match": 1
    }
  }
}

must와 filter에 들어간 하위 쿼리는 모두 AND 조건으로 만족해야 최종 검색결과에 포함된다. must_not 조건절에 들어간 쿼리를 만족하는 문서는 최종 검색 결과에서 제외된다. should는 minimum_should_match 에 지정한 개수 이상의 하위 쿼리를 만족하는 문서가 최종 검색 결과에 포함된다. minimum_should_match의 기본값은 1이며, 이 값이 1이라면 OR 조건으로 검색하는 것과 같다.

 

4가지 조건절 중 필요한 것만 사용하면 된다.

  • 쿼리 문맥과 필터 문백

must와 filter는 모두 AND 조건으로 검색을 수행하지만 점수 계산 여부는 다르다. filter 조건에 들어간 쿼리는 단순히 문서의 매치 여부만을 판단하고 랭킹에 사용할 점수를 매기지 않는다. must_not도 점수를 매기지 않는다. 이렇게 점수를 매기지 않고 단순히 조건을 만족하는지 여부만 따지는 검색 과정을 필터 문맥 (filter context) 이라고 한다. 문서가 주어진 검색 조건을 얼마나 더 잘 만족하는지 유사도 점수를 매기는 검색 과정은 쿼리 문맥 (query context)이라 한다.

  쿼리 문맥 필터 문맥
질의 개념 문서가 질의어와 얼마나 잘 대치되는가 질의 조건을 만족하는가
점수 계산 계산X
성능 상대적으로 느림 상대적으로 빠름
캐시 쿼리 캐시 활용 불가 쿼리 캐시 활용 가능
종류 must, should, match, term 등 filter, must_not, exists, range, constant_score 등
  • 쿼리 수행 순서

엘라스틱서치에서 bool 쿼리의 4가지 절(must, must_not, filter, should) 중 어떤 절이 먼저 수행된다는 규칙은 없다. 요청의 위쪽에 기술된 쿼리가 먼저 수행되는 것도 아니다. 엘라스틱서치는 검색 요청을 받으면 내부적으로 쿼리를 루씬의 여러 쿼리로 쪼갠 뒤 조합하여 재작성한다. 이후, 쪼개진 각 쿼리를 수행할 때 비용이 얼마나 소모되는지 내부적으로 추정한다.

 

하부 쿼리 하나를 통째로 수행한 뒤 다음 하부 쿼리 하나를 수행하는 것도 아니다. 여러 쿼리를 만족하는 문서 후보들을 뽑아 놓고, 문서에 대한 실제 조건 만족 여부를 하나씩 체크하는 과정에서 여러 하부 쿼리를 병렬적으로 수행하기도 한다.

3.11 constant_score 쿼리

하위 filter 부분에 지정한 쿼리를 필터 문맥에서 검색하는 쿼리다.이 쿼리에 매치된 문서의 유사도 점수는 일괄적으로 1로 지정된다.

 

filter 자체가 점수를 매기지 않지만, constant_score 쿼리는 특정 조건을 가진 문서에 일정한 점수를 부여함으로써 검색 결과의 우선순위를 조정하거나 쿼리 구조를 명확히 하는 데 유용할 수 있다. 이는 복잡한 검색 요구사항을 처리할 때 매우 유용하게 사용될 수 있다.

GET [인덱스 이름]/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "constant_score": {
            "filter": {
              "term": {
                "status": "active"
              }
            },
            "boost": 2
          }
        },
        {
          "match": {
            "title": "search term"
          }
        }
      ]
    }
  }
}

3.12 그 외 주요 매개변수

쿼리 종류에 상관없이 검색 API에 공통적으로 적용할 수 있는 주요 매개변수를 알아보자

  • 라우팅 : 검색 API도 색인API나 조회 API와 마찬가지로 라우팅을 제대로 지정해 주는 것이 좋다. 라우팅을 지정하지 않으면 전체 샤드에 검색 요청이 들어간다.
    ex) GET [인덱스 이름]/_search?routing=[라우팅]
    {
      "query": {}
    }
  • explain : 검색을 수행하는 동안 쿼리의 각 하위 부분에서 점수가 어떻게 계산됐는지 설명해준다. 디버깅 용도로 사용할 수 있다.
    ex) GET [인덱스 이름]/_search?explain=true
    {
      "query": {}
    }
  • search_type : 유사도 점수를 계산할 때 각 샤드 레벨에서 계산을 끝낼지 여부를 선택할 수 있다.
    ex) GET [인덱스 이름]/_search?search_type=dfs_query_then_fetch
    {
      "query": {}
    }
    지정 가능한 값은 dfs_query_then_fetch와 query_then_fetch 두 가지다.
    query_then_fetch : 기본 설정. 각 샤드 레벨에서 유사도 점수 계산을 끝낸다. 점수 계산이 부정확할 수 있지만 검색 성능은 올라간다.
    dfs_query_then_fetch : 모든 샤드로부터 정보를 모아 유사도 점수를 글로벌하게 계산한다. 점수 계산 정확도는 올라가지만 검색 성능이 떨어진다.

3.13 검색 결과 정렬

검색 API 호출 시 요청 본문에 sort를 지정하면 검색 결과를 정렬할 수 있다. 정렬에 사용할 필드 이름과 오름차순 또는 내림차순 종류를 지정하면 된다.

GET [인덱스 이름]/_search
{
  "query": {
    "match": {
      "title": {
        "query": "hello"
      }
    }
  },
  "sort": [
    {
      "fieldName": {
        "order": "desc"
      }
    }
  ]
}

order 없이 필드만 명시하면 내림차순 정렬한다.

 

숫자, date, boolean, keyword 타입은 정렬 대상이 될 수 있지만 text타입은 정렬 대상으로 지정할 수 없다. fielddata를 true로 지정하면 text 타입도 정렬에 사용할 수 있지만 fielddata 이용은 성능상 심각한 문제를 야기할 수 있어 특수한 경우가아니면 사용하지 않는 것이 좋다.

 

필드 이름 외에도 _score나 _doc을 지정할 수 있다. _score는 검색을 통해 계산된 유사도 점수, _doc은 문서 번호 순서로 정렬한다.

 

sort 옵션을 지정하지 않으면 _score 내림차순 정렬이다.

 

정렬 수행 중에는 필드의 값이 메모리에 올라간다. 서비스에서 지속적으로 정렬된 검색을 사용한다면 정렬 대상이 될 필드는 메모리를 적게 차지하는 Integer, short, float 등의 타입으로 설계하는 것도 좋다.

3.14 페이지네이션

from, size, scroll, search_after가 있다.

 

  • from과 size
    size는 검색 API의 결과로 몇 개의 문서를 반환할 것인지 지정하고 from은 몇 번째 문서부터 결과를 반환할지 오프셋을 지정한다.
GET [인덱스 이름]/_search
{
"from": 10, 
"size": 5, 
  "query": {
    ...
  }
}

11번째 문서부터 15번째 문서까지 5개의 문서를 반환한다.

 

페이지네이션을 위한 사용으로는 부적절하다. from10, size5로 지정해 검색할 경우 내부적으로 상위 10개의 문서를 수집 후 결과의 일부를 잘라내서 반환하는 방식으로 작동한다. 그 결과 from 값이 커질수록 무거운 검색을 하게 되고 이전 페이지를 검색할 때와 다음 검색을 수행할 때 인덱스 상태가 동일하지 않는다는 문제가 있다. 두 검색 사이에 새로운 문서가 색인되거나 삭제될 수 있기 때문에 특점 시점의 데이터를 중복이나 누럭 없이 페이지네이션하여 제공해야 한다면 from과 size는 사용하지 못한다.

 

from과 size의 합이 1만을 넘어설 경우 수행이 거부된다. index.max_result_window 값을 조정해서 윈도우 크기를 늘릴 수 있다.

  • scroll

검색 조건에 매칭되는 전체 문서를 모두 순회해야 할 때 적합한 방법이다. 스크롤은 순회하는 동안 최고 검색 시의 문맥이 유지된다. 중복이나 누락도 발생하지 않는다.

GET lecture/_search?scroll=1m
{
  "size": 100,
  "query": {
    "match": {
      "title": {
        "query": "java"
      }
    }
  }
}
GET _search/scroll
{
"scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFm1Ic09maWM2U1ZDVWhUZmh5RjYwZ2cAAAAAAACC6BZKS1lMMENSNFFGYS1kdmMxRktCWjl3",
"scroll": "1m"
}


위와 같이 검색을 수행하면 _scroll_id를 반환받을 수 있다. 두 번째 검색부터는 _scroll_id를 이용하여 검색을 수행하면 된다. 더 이상 문서가 반환되지 않을 때까지, 즉 빈 hits가 돌아올 때까지 scroll 검색을 반복한다.

 

scroll 검색을 한 번 수행할 때마다 검색 문맥이 연장된다. scroll 매개변수에 지정한 검색 문맥의 유지 시간은 배치와 배치 사이를 유지할 정도의 시간으로 지정하면 된다. 모든 검색이 끝날 때까지 필요한 시간을 지정하는 것이 아니다.

 

세그먼트 병합이 끝나서 더 이상 필요 없는 세그먼트도 검색 문맥이 유지되는 동안은 삭제되지 않고 유지된다. 보존된 검색 문맥은 유지 시간이 종료되면 자동으로 삭제되지만 좀 더 빠른 자원의 반납을 위해 명시적으로 제거할 수도 있다.

DELETE _search/scroll
{
"scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFm1Ic09maWM2U1ZDVWhUZmh5RjYwZ2cAAAAAAACC6BZKS1lMMENSNFFGYS1kdmMxRktCWjl3"
}
  • search_after

서비스에서 사용자가 검색 결과를 요청하고 결과에 페이지네이션을 제공하는 용도로 사용할 수 있다. search_after에는 sort를 지정해야 한다. 이때 동일한 정렬 값이 등장할 수 없도록 최소한 1개 이상의 동점 제거용 필드를 지정해야 한다.

GET [인덱스 이름]/_search
{
  "size": 5,
  "query": {
    "term": {
      "tag": {
        "value": "웹 개발"
      }
    }
  },
  "sort": [
    {
      "ordinaryPrice": "desc"
    },
    {
      "salePrice": "asc"
    }
  ]
}

검색 후 가장 마지막 부분

첫 번째 검색 후 가장 마지막 문서에 표시된 sort 기준값을 가져와 search_after 부분에 넣어 그다음 검색을 요청한다.

GET lecture/_search
{
  "size": 5,
  "query": {
    "term": {
      "tag": {
        "value": "웹 개발"
      }
    }
  },
  "search_after": [264000, 264000], 
  "sort": [
    {
      "ordinaryPrice": "desc"
    },
    {
      "salePrice": "asc"
    }
  ]
}

_id 값을 동점 제거용 기준 필드로 사용하는 것은 좋지 않다. _id 필드는 doc_value가 꺼져 있기 떄문에 이를 기준으로 하는 정렬은 많은 메모리를 사용하게 된다. _id 필드값과 동일한 값을 별도의 필드에 저장해 뒀다가 동점 제거용으로 사용하는 편이 낫다.

 

동점 제거용 필드를 제대로 지정하더라도 인덱스 상태가 변하는 도중이라면 페이지네이션 과정 중 누락되는 문서가 발생하는 등 일관적이지 않은 변동 사항이 발생할 수 있다. search_after를 사용할 때 인덱스 상태를 특정 시점으로 고정하려면 point in time API를 함께 조합해서 사용하면 된다.

 

Point in time API

point in time API는 검색 대상의 상태를 고정할 때 사용한다. keep_alive 매개변수에 상태를 유지할 시간을 지정한다.

POST lecture/_pit?keep_alive=1m
{ "id": "gbuKBAEHbGVjdHVyZRZnd0ZreHdINlJ5LXVxZUxRTFVvQTBBABZKS1lMMENSNFFGYS1kdmMxRktCWjl3AAAAAAAAAIvgFm1Ic09maWM2U1ZDVWhUZmh5RjYwZ2cAARZnd0ZreHdINlJ5LXVxZUxRTFVvQTBBAAA=" }

쿼리를 실행할 시 id 값을 얻을 수 있는데 이렇게 얻은 id를 search_after와 같은 곳에 활용할 수 있다.

GET _search
{
  "size": 5,
  "query": {
    "term": {
      "tag": {
        "value": "웹 개발"
      }
    }
  },
  "pit": {
    "id": "gbuKBAEHbGVjdHVyZRZnd0ZreHdINlJ5LXVxZUxRTFVvQTBBABZKS1lMMENSNFFGYS1kdmMxRktCWjl3AAAAAAAAAIvgFm1Ic09maWM2U1ZDVWhUZmh5RjYwZ2cAARZnd0ZreHdINlJ5LXVxZUxRTFVvQTBBAAA=",
    "keep_alive": "1m"
  }, 
  "sort": [
    {
      "ordinaryPrice": "desc"
    },
    {
      "salePrice": "asc"
    }
  ]
}

pit 부분에 얻어온 pit id를 지정했다. 검색 대상이 될 인덱스를 지정하지 않았는데 pit을 지정하는 것 자체가 검색 대상을 지정하는 것이기 때문이다. 또한 pit을 지정하면 동점 제거용 필드를 별도로 지정할 필요가 없다. 정렬 기준 필드를 하나라도 지정했다면 _shard_doc 이라는 동점 제거용 필드에 대한 오름차순 정렬이 맨 마지막에 자동으로 추가된다. 정렬 기준을 아예 지정하지 않으면 _shard_doc이 추가 되지 않는다. 두 번째 검색에 search_after를 사용하고 싶다면 정렬 기준 필드를 최소한 하나는 지정해야 한다.

GET _search
{
  "size": 5,
  "query": {
    "term": {
      "tag": {
        "value": "웹 개발"
      }
    }
  },
  "pit": {
    "id": "gbuKBAEHbGVjdHVyZRZnd0ZreHdINlJ5LXVxZUxRTFVvQTBBABZKS1lMMENSNFFGYS1kdmMxRktCWjl3AAAAAAAAAIvgFm1Ic09maWM2U1ZDVWhUZmh5RjYwZ2cAARZnd0ZreHdINlJ5LXVxZUxRTFVvQTBBAAA=",
    "keep_alive": "1m"
  }, 
  "search_after": [264000, 264000, 558],
  "sort": [
    {
      "ordinaryPrice": "desc"
    },
    {
      "salePrice": "asc"
    }
  ]
}

pit 또한 다 사용한 뒤 명시적 삭제가 가능하다.

DELETE _pit
{
 "id": "gbuKBAEHbGVjdHVyZRZnd0ZreHdINlJ5LXVxZUxRTFVvQTBBABZKS1lMMENSNFFGYS1kdmMxRktCWjl3AAAAAAAAAIvgFm1Ic09maWM2U1ZDVWhUZmh5RjYwZ2cAARZnd0ZreHdINlJ5LXVxZUxRTFVvQTBBAAA="
}