쿼리로 삭제 API

쿼리로 삭제 API (Delete by Query API)

지정한 쿼리에 맞는 모든 문서를 한 번에 삭제하고 싶으시죠? 쿼리로 삭제 API가 지정된 쿼리와 일치하는 인덱스의 모든 문서를 제거해요. 문서를 하나씩 삭제하는 대신, 검색 조건에 기반해 단일 요청으로 여러 문서를 삭제할 수 있어요.

출처: 문서

본문

1.0에서 도입

쿼리로 삭제 API는 지정된 쿼리와 일치하는 인덱스의 모든 문서를 제거해요. 문서를 하나씩 삭제하는 대신 검색 기준에 따라 단일 요청으로 여러 문서를 삭제할 수 있어요.

이 API를 다음 시나리오에서 사용해요.

  • 날짜 범위나 다른 기준으로 시계열 인덱스에서 오래된 데이터를 제거할 때
  • 특정 패턴과 일치하는 테스트 데이터나 잘못된 문서를 정리할 때
  • 특정 수명을 초과한 문서를 삭제해 데이터 보존 정책을 구현할 때
  • 민감한 정보를 포함한 문서를 삭제할 때

쿼리로 삭제 요청을 제출하면 OpenSearch는 연산 시작 시 인덱스의 scroll 컨텍스트를 만들고 내부 버전 관리(시퀀스 번호와 primary term)를 사용해 일치하는 문서를 삭제해요. 연산은 모든 일치 문서를 찾기 위해 여러 검색 요청을 순차적으로 수행한 다음, 각 배치에 대해 벌크 삭제 요청을 실행해요. 스냅샷이 촬영된 시점과 삭제 연산이 문서를 처리하는 시점 사이에 문서가 변경되면 버전 충돌이 발생하고, conflicts 파라미터를 proceed로 설정하지 않는 한 해당 문서의 삭제는 실패해요. 배치의 이후 연산이 실패해도 성공적으로 삭제된 문서는 롤백되지 않아요.

OpenSearch는 거부된 검색 또는 벌크 요청을 지수 백오프로 최대 10번 재시도해요. 최대 재시도 한도에 도달하면 연산이 중단되고 응답에 모든 실패 요청을 반환해요.

참고: OpenSearch는 이 API로 버전 0인 문서를 삭제할 수 없어요. 내부 버전 관리 시스템은 삭제 연산을 추적하고 처리하려면 버전 번호가 0보다 커야 해요.

엔드포인트

POST /{index}/_delete_by_query

경로 파라미터

다음 표는 사용 가능한 경로 파라미터예요.

Parameter Required Data type Description
index Required List or String 검색할 데이터 스트림, 인덱스, alias의 쉼표로 구분된 목록이에요. 와일드카드(*)를 지원해요. 모든 데이터 스트림이나 인덱스를 검색하려면 이 파라미터를 생략하거나 * 또는 _all을 사용해요.

쿼리 파라미터

다음 표는 사용 가능한 쿼리 파라미터예요. 모든 쿼리 파라미터는 선택적이에요.

Parameter Data type Description Default
_source Boolean or List or String _source 필드를 반환할지(true/false) 또는 반환할 필드 목록을 지정해요. N/A
_source_excludes List 반환되는 _source 필드에서 제외할 필드 목록이에요. N/A
_source_includes List _source 필드에서 추출해 반환할 필드 목록이에요. N/A
allow_no_indices Boolean false면 와일드카드 표현식, 인덱스 alias, 또는 _all 값이 없거나 닫힌 인덱스만 대상으로 하면 요청이 오류를 반환해요. 이 동작은 요청이 다른 열린 인덱스도 대상으로 해도 적용돼요. 예를 들어 foo*,bar*를 대상으로 하는 요청은 foo로 시작하는 인덱스가 있지만 bar로 시작하는 인덱스가 없으면 오류를 반환해요. N/A
analyze_wildcard Boolean true면 와일드카드와 prefix 쿼리가 분석돼요. false
analyzer String 쿼리 문자열에 사용할 analyzer예요. N/A
conflicts String 버전 충돌 발생 시 삭제가 어떻게 처리할지 지정해요: abort 또는 proceed. 유효한 값은 다음과 같아요. - abort: 버전 충돌 시 연산을 중단해요. - proceed: 버전 충돌 시 연산을 계속해요. N/A
default_operator String 쿼리 문자열 쿼리의 기본 연산자예요: AND 또는 OR. 유효한 값은 and, AND, or, OR예요. N/A
df String 쿼리 문자열에 필드 접두사가 없을 때 기본으로 사용할 필드예요. N/A
expand_wildcards List or String 와일드카드 패턴이 매칭할 수 있는 인덱스 유형이에요. 요청이 데이터 스트림을 대상으로 할 수 있다면 이 인자는 와일드카드 표현식이 숨겨진 데이터 스트림을 매칭하는지 결정해요. 쉼표로 구분된 값(예: open,hidden)을 지원해요. 유효한 값은 all, open, closed, hidden, none이에요. N/A
from Integer 시작 오프셋이에요. 0
ignore_unavailable Boolean false면 요청이 없거나 닫힌 인덱스를 대상으로 하면 오류를 반환해요. N/A
lenient Boolean true면 쿼리 문자열의 형식 기반 쿼리 실패(예: 숫자 필드에 텍스트 제공)가 무시돼요. N/A
max_docs Integer 처리할 최대 문서 수예요. 기본값은 모든 문서예요. N/A
preference String 연산을 수행할 노드나 샤드를 지정해요. 기본값은 무작위예요. random
q String Lucene 쿼리 문자열 문법의 쿼리예요. N/A
refresh Boolean or String true면 요청 완료 후 delete by query에 관여한 모든 샤드를 refresh해요. 유효한 값은 다음과 같아요. - false: 영향받은 샤드를 refresh하지 않아요. - true: 영향받은 샤드를 즉시 refresh해요. - wait_for: 응답하기 전에 변경 사항이 표시될 때까지 기다려요. N/A
request_cache Boolean true면 이 요청에 요청 캐시가 사용돼요. 기본값은 인덱스 수준 설정이에요. N/A
requests_per_second Float 초당 하위 요청 수로 표현한 이 요청의 스로틀(제한)이에요. 0
routing List or String 작업을 특정 샤드로 라우팅하는 데 사용되는 사용자 정의 값이에요. N/A
scroll String 스크롤을 위해 검색 컨텍스트를 유지하는 기간이에요. N/A
scroll_size Integer 연산을 구동하는 스크롤 요청의 크기예요. 100
search_timeout String 각 검색 요청의 명시적 타임아웃이에요. 기본값은 타임아웃 없음이에요. N/A
search_type String 검색 연산의 유형이에요. 사용 가능한 옵션: query_then_fetch, dfs_query_then_fetch. 유효한 값은 다음과 같아요. - dfs_query_then_fetch: 모든 샤드에 걸친 전역 용어 및 문서 빈도를 사용해 문서에 점수를 매겨요. 보통 더 느리지만 더 정확해요. - query_then_fetch: 샤드의 로컬 용어 및 문서 빈도를 사용해 문서에 점수를 매겨요. 보통 더 빠르지만 덜 정확해요. N/A
size Integer Deprecated, 대신 max_docs를 사용해요. N/A
slices Integer or String 이 작업을 나눌 슬라이스 수예요. 유효한 값은 다음과 같아요. - auto: 슬라이스 수를 자동으로 결정해요. N/A
sort List : 쌍의 쉼표로 구분된 목록이에요. N/A
stats List 로깅 및 통계 목적의 요청별 태그예요. N/A
terminate_after Integer 각 샤드에 대해 수집할 최대 문서 수예요. 쿼리가 이 한도에 도달하면 OpenSearch는 쿼리를 조기에 종료해요. OpenSearch는 정렬 전에 문서를 수집해요. 주의해서 사용하세요. OpenSearch는 이 파라미터를 요청을 처리하는 각 샤드에 적용해요. 가능하면 OpenSearch가 자동으로 조기 종료하게 두세요. 여러 데이터 계층에 걸친 백킹 인덱스가 있는 데이터 스트림을 대상으로 하는 요청에는 이 파라미터를 지정하지 마세요. N/A
timeout String 각 삭제 요청이 활성 샤드를 기다리는 기간이에요. N/A
version Boolean true면 문서 버전을 hit의 일부로 반환해요. N/A
wait_for_active_shards Integer or String or NULL or String 연산을 진행하기 전에 활성 상태여야 하는 샤드 복사본 수예요. all 또는 인덱스의 총 샤드 수까지의 양의 정수(number_of_replicas+1)로 설정해요. 유효한 값은 다음과 같아요. - all: 모든 샤드가 활성화될 때까지 기다려요. N/A
wait_for_completion Boolean true면 연산이 완료될 때까지 요청이 블록돼요. true

요청 본문 필드

요청 본문은 선택적이지만 일반적으로 삭제할 문서를 지정하는 쿼리를 포함해요.

Field Data type Description
query Object 삭제할 문서를 선택하는 데 사용되는 쿼리예요. 지정하지 않으면 연산이 대상 인덱스의 모든 문서를 삭제해요. 쿼리 유형에 대한 자세한 내용은 Query DSL을 참고하세요.
slice Object 병렬 처리를 위해 슬라이스 ID와 최대 슬라이스 수를 수동으로 지정해요. id(정수, 슬라이스 번호)와 max(정수, 총 슬라이스 수)를 포함해요. 선택적.
max_docs Integer 처리할 최대 문서 수예요. 선택적.

샤드 refresh (Refreshing shards)

refresh 파라미터를 지정하면 요청 완료 후 delete by query 연산에 관여한 모든 샤드를 refresh해요. 이 동작은 삭제 요청을 받은 샤드만 refresh하는 Delete Document API의 refresh 파라미터와 달라요. Delete by Query API는 refresh 파라미터에 대해 wait_for 값을 지원하지 않아요.

delete by query 비동기 실행 (Running delete by query asynchronously)

delete by query 연산을 비동기로 실행하려면 wait_for_completion 쿼리 파라미터를 false로 설정해요. OpenSearch는 사전 검사(preflight checks)를 수행하고 요청을 시작한 다음, 진행 상황을 모니터링하거나 연산을 취소하는 데 사용할 수 있는 작업 ID를 반환해요. 비동기로 실행하면 OpenSearch가 .tasks/task/${taskId} 위치에 작업 레코드를 문서로 만들어요. 작업이 완료된 후 작업 문서를 삭제해 OpenSearch가 공간을 회수하게 해야 해요.

활성 샤드 대기 (Waiting for active shards)

wait_for_active_shards 파라미터는 요청을 처리하기 전에 활성 상태여야 하는 샤드 복사본 수를 제어해요. timeout 파라미터는 각 쓰기 요청이 사용할 수 없는 샤드가 사용 가능해질 때까지 기다리는 시간을 제어해요. 이 파라미터들은 Bulk API와 같은 방식으로 작동해요. Delete by Query는 스크롤 검색을 사용하므로 scroll 파라미터로 검색 컨텍스트가 활성 상태를 유지하는 시간을 제어할 수 있어요. 기본 스크롤 시간은 5분이에요.

삭제 요청 스로틀링 (Throttling delete requests)

delete by query가 삭제 연산 배치를 발행하는 속도를 제어하려면 requests_per_second를 양의 십진수로 설정해요. 이는 각 배치에 대기 시간을 추가해 속도를 제한해요. 스로틀을 비활성화하려면 requests_per_second를 -1로 설정해요.

스로틀링은 배치 사이의 대기 시간을 사용해 요청 패딩을 고려한 시간 초과를 내부 스크롤 요청에 줄 수 있게 해요. 패딩 시간은 배치 크기를 requests_per_second로 나눈 값과 쓰기 시간의 차이예요. 기본적으로 배치 크기는 1,000이므로 requests_per_second가 500으로 설정되면:

target_time = 1,000 / 500 per second = 2 seconds
wait_time = target_time - write_time = 2 seconds - 0.5 seconds = 1.5 seconds

각 배치가 단일 벌크 요청으로 발행되므로 배치 크기가 크면 OpenSearch가 많은 요청을 만든 다음 다음 배치를 시작하기 전에 기다리게 돼요. 이는 높은 활동 기간과 유휴 대기가 이어지는 고르지 않은 처리 패턴을 만든다.

병렬 처리를 위한 슬라이싱 (Slicing for parallel processing)

슬라이싱을 사용해 여러 스레드에서 삭제 연산을 병렬로 실행할 수 있어요. 이 접근 방식은 삭제 연산을 독립적인 세그먼트로 나눠 대규모 삭제의 성능을 향상시켜요.

slices를 auto로 설정하면 대부분의 인덱스에 대해 OpenSearch가 합리적인 값을 선택하게 해요. 자동 슬라이싱을 사용하거나 수동으로 조정할 때 다음 요소를 고려하세요.

  • 슬라이스 수를 샤드 수와 맞추면 최적의 쿼리 성능이 발생해요. 그러나 샤드가 많은 인덱스(500개 이상)에서는 과도한 병렬화 오버헤드로 인한 성능 저하를 피하기 위해 더 적은 슬라이스를 사용해요. 슬라이스를 샤드 수보다 높게 설정하면 일반적으로 효율성이 향상되지 않고 오버헤드만 추가돼요.
  • 삭제 성능은 슬라이스 수에 따라 사용 가능한 리소스에 걸쳐 선형적으로 확장돼요.
  • 쿼리 성능이 지배하는지 삭제 성능이 지배하는지는 삭제되는 문서와 사용 가능한 클러스터 리소스에 따라 달라져요.

예시: 쿼리와 일치하는 문서 삭제하기

다음 예시 요청은 year 필드가 2000보다 작은 movies 인덱스의 모든 문서를 삭제해요.

POST /movies/_delete_by_query
{
  "query": {
    "range": {
      "year": {
        "lt": 2000
      }
    }
  }
}

예시: conflicts를 proceed로 설정해 삭제하기

다음 예시 요청은 쿼리와 일치하는 문서를 삭제하고, 버전 충돌이 발생해도 계속 처리해요.

POST /movies/_delete_by_query?conflicts=proceed
{
  "query": {
    "match": {
      "status": "archived"
    }
  }
}

예시: 여러 인덱스에서 삭제하기

다음 예시 요청은 쿼리와 일치하는 여러 인덱스의 문서를 삭제해요.

POST /movies,tv-shows/_delete_by_query
{
  "query": {
    "match_all": {}
  }
}

예시: 라우팅으로 대상 지정 삭제하기

다음 예시 요청은 특정 라우팅 값을 가진 샤드로 삭제 연산을 제한해요.

POST /movies/_delete_by_query?routing=user123
{
  "query": {
    "term": {
      "user_id": "user123"
    }
  }
}

예시: scroll_size로 배치 크기 제어하기

다음 예시 요청은 5,000개 문서의 커스텀 스크롤 배치 크기를 사용해요.

POST /movies/_delete_by_query?scroll_size=5000
{
  "query": {
    "term": {
      "genre": "documentary"
    }
  }
}

예시: 병렬 처리를 위한 수동 슬라이싱

다음 예시 요청은 삭제 연산을 병렬 처리를 위해 두 개의 슬라이스로 수동 나눠요.

POST /movies/_delete_by_query
{
  "slice": {
    "id": 0,
    "max": 2
  },
  "query": {
    "range": {
      "rating": {
        "lt": 5
      }
    }
  }
}

별도의 요청에서 두 번째 슬라이스를 처리해요.

POST /movies/_delete_by_query
{
  "slice": {
    "id": 1,
    "max": 2
  },
  "query": {
    "range": {
      "rating": {
        "lt": 5
      }
    }
  }
}

예시: 자동 슬라이싱

다음 예시 요청은 자동 슬라이싱을 사용해 삭제 연산을 5개 슬라이스로 병렬화해요.

POST /movies/_delete_by_query?slices=5&refresh=true
{
  "query": {
    "range": {
      "views": {
        "lt": 100
      }
    }
  }
}

OpenSearch가 최적의 슬라이스 수를 자동 결정하게 하려면 slices=auto를 사용해요.

POST /movies/_delete_by_query?slices=auto
{
  "query": {
    "match": {
      "category": "test"
    }
  }
}

예시 응답

다음 예시 응답은 8개 문서를 삭제한 성공적인 delete by query 연산을 보여줘요.

{
  "took": 88,
  "timed_out": false,
  "total": 8,
  "deleted": 8,
  "batches": 1,
  "version_conflicts": 0,
  "noops": 0,
  "retries": {
    "bulk": 0,
    "search": 0
  },
  "throttled_millis": 0,
  "requests_per_second": -1.0,
  "throttled_until_millis": 0,
  "failures": []
}

수동 슬라이싱을 사용하면 응답에 어떤 슬라이스가 처리됐는지 나타내는 slice_id 필드가 포함돼요.

{
  "took": 13,
  "timed_out": false,
  "slice_id": 0,
  "total": 9,
  "deleted": 9,
  "batches": 1,
  "version_conflicts": 0,
  "noops": 0,
  "retries": {
    "bulk": 0,
    "search": 0
  },
  "throttled_millis": 0,
  "requests_per_second": -1.0,
  "throttled_until_millis": 0,
  "failures": []
}

특정 수의 슬라이스로 자동 슬라이싱을 사용하면 응답에 각 슬라이스의 결과를 보여주는 slices 배열이 포함돼요.

{
  "took": 52,
  "timed_out": false,
  "total": 9,
  "deleted": 9,
  "batches": 4,
  "version_conflicts": 0,
  "noops": 0,
  "retries": {
    "bulk": 0,
    "search": 0
  },
  "throttled_millis": 0,
  "requests_per_second": -1.0,
  "throttled_until_millis": 0,
  "slices": [
    {
      "slice_id": 0,
      "total": 3,
      "deleted": 3,
      "batches": 1,
      "version_conflicts": 0,
      "noops": 0,
      "retries": {
        "bulk": 0,
        "search": 0
      },
      "throttled_millis": 0,
      "requests_per_second": -1.0,
      "throttled_until_millis": 0
    },
    {
      "slice_id": 1,
      "total": 2,
      "deleted": 2,
      "batches": 1,
      "version_conflicts": 0,
      "noops": 0,
      "retries": {
        "bulk": 0,
        "search": 0
      },
      "throttled_millis": 0,
      "requests_per_second": -1.0,
      "throttled_until_millis": 0
    },
    {
      "slice_id": 2,
      "total": 0,
      "deleted": 0,
      "batches": 0,
      "version_conflicts": 0,
      "noops": 0,
      "retries": {
        "bulk": 0,
        "search": 0
      },
      "throttled_millis": 0,
      "requests_per_second": -1.0,
      "throttled_until_millis": 0
    },
    {
      "slice_id": 3,
      "total": 1,
      "deleted": 1,
      "batches": 1,
      "version_conflicts": 0,
      "noops": 0,
      "retries": {
        "bulk": 0,
        "search": 0
      },
      "throttled_millis": 0,
      "requests_per_second": -1.0,
      "throttled_until_millis": 0
    },
    {
      "slice_id": 4,
      "total": 3,
      "deleted": 3,
      "batches": 1,
      "version_conflicts": 0,
      "noops": 0,
      "retries": {
        "bulk": 0,
        "search": 0
      },
      "throttled_millis": 0,
      "requests_per_second": -1.0,
      "throttled_until_millis": 0
    }
  ],
  "failures": []
}

응답 본문 필드

다음 표는 모든 응답 본문 필드를 나열해요.

Field Data type Description
took Integer 전체 연산의 시작부터 끝까지 걸린 시간(밀리초)이에요.
timed_out Boolean delete by query 연산 동안 실행된 요청 중 하나라도 타임아웃됐는지 여부예요. true일 때 성공적으로 완료된 삭제는 여전히 유지되며 롤백되지 않아요.
total Integer 성공적으로 처리된 총 문서 수예요.
deleted Integer 성공적으로 삭제된 문서 수예요.
batches Integer delete by query 연산이 처리한 스크롤 배치 수예요.
version_conflicts Integer delete by query 연산이 만난 버전 충돌 수예요. 스냅샷이 촬영된 시점과 삭제 연산이 처리되는 시점 사이에 문서가 변경될 때 발생해요.
noops Integer 무작동(no-operation) 요청 수예요. 이 필드는 delete by query에서 항상 0을 반환해요. Update by Query 및 Reindex API와 응답 구조 일관성을 유지하기 위해 존재해요.
retries Object delete by query 연산이 시도한 재시도 수예요. bulk(벌크 작업 재시도 수)와 search(검색 작업 재시도 수)를 포함해요.
throttled_millis Integer requests_per_second를 준수하기 위해 요청이 스로틀링된 시간(밀리초)이에요.
requests_per_second Float delete by query 연산 동안 효과적으로 실행된 초당 요청 수예요.
throttled_until_millis Integer 다음 스로틀된 요청이 실행될 때까지의 시간(밀리초)이에요. 완료된 delete by query 응답에서 항상 0과 같아요. 이 필드는 Tasks API로 진행 중인 연산을 모니터링할 때만 의미가 있으며, 다음 스로틀된 요청이 실행될 시간을 나타내요.
slice_id Integer 이 응답의 슬라이스 번호예요. 수동 슬라이싱을 사용할 때만 존재해요. 이 응답이 나타내는 연산의 슬라이스가 무엇인지를 나타내요.
slices Array 특정 수로 자동 슬라이싱을 사용할 때의 슬라이스 결과 배열이에요. 각 요소는 개별 슬라이스의 결과를 보여주며 기본 응답과 동일한 응답 필드를 포함해요.
failures Array 연산 중 복구할 수 없는 오류가 발생하면 실패 배열이에요. 이 배열이 비어 있지 않으면 요청이 해당 실패로 인해 중단됐어요. Delete by query는 배치를 사용해 구현되며 어떤 실패든 전체 프로세스를 중단시키지만, 현재 배치의 모든 실패는 이 배열에 수집돼요. 버전 충돌 시 연산이 중단되지 않도록 conflicts 파라미터를 proceed로 설정할 수 있어요.

delete by query 작업 관리 (Managing delete by query tasks)

wait_for_completion=false로 delete by query 연산을 비동기로 실행하면 OpenSearch가 연산을 모니터링, 수정, 취소하는 데 사용할 수 있는 작업 ID를 반환해요.

delete by query 연산 상태 검색하기 (Retrieving the status of a delete by query operation)

delete by query 연산의 상태를 검색하려면 Tasks API를 사용해요.

GET _tasks?detailed=true&actions=*/delete/byquery

응답에는 실행 중인 모든 delete by query 연산의 상태가 포함돼요. 특정 작업의 상태를 검색하려면 작업 ID를 사용해요.

GET _tasks/{task_id}

응답에는 연산 진행 상황에 대한 상세 정보가 포함돼요.

{
  "nodes": {
    "node_id": {
      "tasks": {
        "task_id": {
          "status": {
            "total": 1000,
            "updated": 0,
            "created": 0,
            "deleted": 450,
            "batches": 5,
            "version_conflicts": 0,
            "noops": 0,
            "retries": 0,
            "throttled_millis": 0
          }
        }
      }
    }
  }
}

total 필드는 delete by query 연산이 수행할 것으로 예상하는 총 연산 수를 나타내요. deleted 필드를 total 필드와 비교해 진행 상황을 추정할 수 있어요. deleted가 total과 같아지면 연산이 완료된 거예요.

실행 중인 연산의 스로틀링 변경하기 (Changing throttling for a running operation)

실행 중인 delete by query 연산의 스로틀링을 변경하려면 작업 ID로 Rethrottle API를 사용해요.

POST _delete_by_query/{task_id}/_rethrottle?requests_per_second=100

requests_per_second를 양의 십진수 값 또는 -1로 설정해 스로틀링을 비활성화해요. 연산을 빠르게 하는 재스로틀링은 즉시 적용돼요. 연산을 느리게 하는 재스로틀링은 스크롤 타임아웃을 방지하기 위해 현재 배치를 완료한 후 적용돼요.

delete by query 연산 취소하기 (Canceling a delete by query operation)

실행 중인 delete by query 연산을 취소하려면 작업 취소 API를 사용해요.

POST _tasks/{task_id}/_cancel

취소는 빠르게 발생해야 하지만 몇 초 걸릴 수 있어요. Tasks API는 delete by query 작업이 취소됐는지 확인하고 스스로 종료할 때까지 계속 그 작업을 나열해요. 슬라이스가 있는 delete by query 연산을 취소하면 OpenSearch가 각 하위 요청을 취소해요.

보안

Security 플러그인을 사용한다면 적절한 권한이 있는지 확인해야 해요: indices:data/write/delete/byquery.

더 알아보기 (Learn more)

  • 대규모 삭제는 slices로 병렬화하고 requests_per_second로 부하를 조절할 수 있어요.
  • conflicts=proceed를 쓰면 버전 충돌에도 삭제를 계속 진행해요.