문서 업데이트 API

문서 업데이트 API (Update Document API)

1.0에서 도입되었어요. 인덱스에 있는 문서의 필드를 업데이트해야 한다면 update document API 작업을 사용할 수 있어요. 인덱스에 있길 원하는 새 데이터를 지정하거나, 요청 본문에 스크립트를 포함해서 OpenSearch가 문서를 업데이트하게 할 수 있어요. 기본적으로 update 작업은 인덱스에 이미 존재하는 문서만 업데이트해요. 문서가 존재하지 않으면 API가 오류를 반환해요. 문서를 upsert하려면(존재하면 업데이트하고, 없으면 새로 인덱싱) upsert 작업을 사용하세요.

update 요청을 제출하면 OpenSearch는 다음 작업을 수행해요.

  1. 문서가 저장된 샤드에서 현재 문서를 가져와요.
  2. 제공된 스크립트를 적용하거나 부분 문서를 기존 문서와 병합해서 업데이트를 적용해요.
  3. 업데이트된 문서를 다시 인덱싱하고 버전 번호를 증가시켜요.

문서를 다시 인덱싱해야 하지만, Update Document API를 사용하면 GET 메서드로 문서를 수동으로 가져와 수정한 뒤 Index API로 다시 인덱싱하는 것보다 네트워크 왕복과 버전 충돌을 줄여줘요.

Update Document API를 사용하려면 인덱스에 _source 필드가 활성화되어 있어야 해요.

Update Document API를 호출할 때 ingest pipeline을 명시적으로 지정할 수는 없어요. 인덱스에 default_pipeline이나 final_pipeline이 정의되어 있다면 다음 동작이 적용돼요.

  • Upsert 작업: 새 문서를 인덱싱할 때 인덱스에 정의된 default_pipeline과 final_pipeline이 지정된 대로 실행돼요.
  • Update 작업: 기존 문서를 업데이트할 때는 ingest pipeline 실행이 권장되지 않아요. 잘못된 결과를 만들 수 있기 때문이에요. update 작업 중 ingest pipeline 실행 지원은 deprecated이며 버전 3.0.0에서 제거될 예정이에요. 인덱스에 ingest pipeline이 정의되어 있다면 update document 작업은 다음 deprecation 경고를 반환해요.
the index [sample-index1] has a default ingest pipeline or a final ingest pipeline, the support of the ingest pipelines for update operation causes unexpected result and will be removed in 3.0.0

출처: 문서

본문

엔드포인트 (Endpoints)

POST /{index}/_update/{id}

경로 파라미터 (Path parameters)

다음 표는 사용 가능한 경로 파라미터를 보여줘요.

파라미터 필수 데이터 타입 설명
id 필수 String 문서 ID예요.
index 필수 String 인덱스 이름이에요. 기본적으로 인덱스가 존재하지 않으면 자동으로 만들어져요.

쿼리 파라미터 (Query parameters)

다음 표는 사용 가능한 쿼리 파라미터를 보여줘요. 모든 쿼리 파라미터는 선택 사항이에요.

파라미터 데이터 타입 설명 필수
if_seq_no Integer 문서가 지정된 시퀀스 번호를 가질 때만 업데이트 작업을 수행해요. 아니요
if_primary_term Integer 문서가 지정된 프라이머리 텀을 가질 때 업데이트 작업을 수행해요. 아니요
lang String 스크립트의 언어예요. 기본값은 painless예요. 자세한 내용은 Painless scripting language를 참고하세요. 아니요
require_alias Boolean 대상이 인덱스 별칭이어야 하는지 여부예요. 기본값은 false예요. 아니요
refresh Enum true이면 OpenSearch가 샤드를 새로고침해서 작업을 검색에 보이게 해요. 유효한 옵션은 true, false, 그리고 작업 실행 전에 새로고침을 기다리라고 OpenSearch에 알리는 wait_for예요. 기본값은 false예요. 아니요
retry_on_conflict Integer 문서 충돌이 있을 때 OpenSearch가 작업을 재시도할 횟수예요. 기본값은 0이에요. 아니요
routing String 업데이트 작업을 특정 샤드로 라우팅하는 값이에요. 아니요
_source Boolean 또는 List 응답 본문에 _source 필드를 포함할지 여부예요. 기본값은 false예요. 이 파라미터는 쿼리 응답에 여러 소스 필드를 포함하기 위한 소스 필드의 쉼표 구분 목록도 지원해요. 아니요
_source_excludes List 쿼리 응답에서 제외할 소스 필드의 쉼표 구분 목록이에요. 아니요
_source_includes List 쿼리 응답에 포함할 소스 필드의 쉼표 구분 목록이에요. 아니요
timeout Time 클러스터의 응답을 기다리는 시간이에요. 아니요
wait_for_active_shards String OpenSearch가 update 요청을 처리하기 전에 사용 가능해야 하는 활성 샤드 수예요. 기본값은 1(프라이머리 샤드만)이에요. all 또는 양의 정수로 설정해요. 1보다 큰 값은 복제본이 필요해요. 예를 들어 값 3을 지정하면 작업이 성공하려면 인덱스에 두 개의 복제본이 두 개의 추가 노드에 분산되어 있어야 해요. 아니요

요청 본문 필드 (Request body fields)

요청 본문에는 문서를 업데이트하는 데 사용할 정보가 포함되어야 해요. 다음 표는 사용 가능한 요청 본문 필드를 보여줘요.

필드 데이터 타입 설명
doc Object 기존 문서에 병합할 필드를 포함하는 부분 문서예요. 단순한 필드 업데이트에 사용해요. doc 객체로 문서 업데이트를 참고하세요.
script Object 문서를 업데이트하는 방법을 정의하는 스크립트예요. 조건부 로직이나 계산된 값이 필요한 복잡한 업데이트에 사용해요. doc과 script가 모두 지정되면 doc은 무시돼요. 스크립트로 문서 업데이트를 참고하세요.
upsert Object 대상 문서가 존재하지 않을 때 인덱싱할 문서예요. 조건부 upsert 작업을 위해 doc이나 script와 함께 사용돼요. Upsert를 참고하세요.
doc_as_upsert Boolean true이면 업데이트와 삽입 모두에 doc 콘텐츠를 사용해요. 기본값은 false예요. Doc as upsert를 참고하세요.
scripted_upsert Boolean true이면 문서가 존재하는지와 무관하게 스크립트를 실행해요. 기본값은 false예요. script와 upsert 필드가 모두 필요해요. Scripted upsert를 참고하세요.
detect_noop Boolean true이면 OpenSearch가 업데이트가 문서를 변경하는지 확인해요. 변경이 감지되지 않으면 업데이트를 건너뛰어요. 기본값은 true예요. No-op 업데이트 감지를 참고하세요.

스크립트 컨텍스트와 변수 (Script context and variables)

스크립트는 ctx 맵을 통해 문서에 접근하고 수정할 수 있어요. ctx 맵은 다음 변수에 대한 접근을 제공해요.

변수 설명
ctx._source 문서 소스예요. 이 객체를 읽고 수정해서 문서 필드를 업데이트할 수 있어요.
ctx._index 문서를 담고 있는 인덱스의 이름이에요.
ctx._id 문서 ID예요.
ctx._version 현재 문서 버전이에요.
ctx._routing 문서를 샤드로 라우팅하는 데 사용한 라우팅 값이에요(사용자 지정 라우팅을 사용한 경우).
ctx._now epoch 이후 밀리초 단위의 현재 타임스탬프예요.
ctx.op 수행할 작업이에요. 문서를 삭제하려면 delete로, 아무 작업도 수행하지 않으려면(no-op) none으로 설정해요.

이 변수들을 스크립트에서 사용해 문서의 현재 상태를 기반으로 조건부 로직을 구현할 수 있어요.

예제 설정 (Example setup)

다음 예제들은 sample-index1 인덱스의 테스트 문서를 사용해요. 따라 하려면 먼저 샘플 문서가 있는 인덱스를 만들어요.

예제 요청 (Example requests)

다음 예제들은 서로 다른 요청 본문 필드로 문서를 업데이트하는 방법을 보여줘요.

doc 객체로 문서 업데이트 (Updating a document using a doc object)

스크립트로 문서 업데이트 (Updating a document using a script)

upsert 작업 사용 (Using the upsert operation)

Upsert는 요청의 정보를 기반으로 기존 문서를 업데이트하거나 새 문서를 삽입하는 작업이에요. 문서가 이미 존재하는지 확실하지 않을 때, 어느 쪽이든 올바른 콘텐츠가 있도록 보장하고 싶을 때 유용해요.

Upsert

다음 예제에서 upsert 작업은 문서가 이미 존재하면 first_name과 last_name 필드를 업데이트해요. 문서가 존재하지 않으면 upsert 객체의 콘텐츠로 새 문서가 인덱싱돼요.

다음 문서를 포함한 인덱스를 생각해보세요.

{
  "_index": "sample-index1",
  "_id": "1",
  "_score": 1,
  "_source": {
    "first_name": "Bruce",
    "last_name": "Wayne"
  }
}

upsert 작업 후 문서의 first_name과 last_name 필드가 업데이트돼요.

{
  "_index": "sample-index1",
  "_id": "1",
  "_score": 1,
  "_source": {
    "first_name": "Martha",
    "last_name": "Rivera"
  }
}

문서가 인덱스에 존재하지 않으면 upsert 객체에 지정된 필드로 새 문서가 인덱싱돼요.

{
  "_index": "sample-index1",
  "_id": "1",
  "_score": 1,
  "_source": {
    "last_name": "Oliveira",
    "age": "31"
  }
}
Doc as upsert

요청에 doc_as_upsert를 추가하고 true로 설정하면 upsert 작업을 수행할 때 doc 필드의 정보를 사용할 수도 있어요.

다음 문서를 포함한 인덱스를 생각해보세요.

{
  "_index": "sample-index1",
  "_id": "1",
  "_score": 1,
  "_source": {
    "first_name": "Bruce",
    "last_name": "Wayne"
  }
}

upsert 작업 후 문서의 first_name과 last_name 필드가 업데이트되고 age 필드가 추가돼요. 문서가 인덱스에 존재하지 않으면 doc 객체의 필드로 새 문서가 만들어져요.

{
  "_index": "sample-index1",
  "_id": "1",
  "_score": 1,
  "_source": {
    "first_name": "Martha",
    "last_name": "Oliveira",
    "age": "31"
  }
}
Scripted upsert

스크립트로 문서가 업데이트되는 방식을 제어할 수도 있어요. scripted_upsert 파라미터를 true로 설정하면 문서가 아직 존재하지 않아도 OpenSearch가 스크립트를 사용하게 만들어요. 이렇게 하면 전체 upsert 로직을 스크립트에서 정의할 수 있어요.

다음 예제에서 스크립트는 문서가 이전에 존재했는지와 무관하게 문서가 특정 필드를 포함하도록 설정해요.

ID 2의 문서가 아직 존재하지 않으면 이 작업이 스크립트로 문서를 만들어요. 문서가 존재하면 스크립트가 지정된 필드를 업데이트해요. 두 경우 모두 결과는 다음과 같아요.

{
  "_index": "sample-index1",
  "_id": "2",
  "_score": 1,
  "_source": {
    "first_name": "Selina",
    "last_name": "Kyle",
    "age": 28
  }
}

표준 doc 기반 작업이 충분히 유연하지 않을 때 scripted_upsert를 사용하면 문서 생성과 업데이트를 완전히 제어할 수 있어요.

No-op 업데이트 감지 (Detecting no-op updates)

기본적으로 OpenSearch는 업데이트 작업이 실제로 문서를 변경하는지 감지해요. 업데이트가 변경을 만들지 않으면 OpenSearch는 작업을 건너뛰고 아무 작업도 수행되지 않았음을 나타내는 "result": "noop"을 반환해요. 이 최적화는 문서가 이미 설정하려는 값을 포함하고 있을 때 불필요한 재인덱싱을 피해요.

다음 예제는 이미 포함하고 있는 값으로 문서 1을 업데이트하려고 해요.

POST /sample-index1/_update/1
{
  "doc": {
    "first_name": "Bruce",
    "last_name": "Wayne",
    "age": 35
  }
}

문서가 이미 정확히 이 값들을 가지고 있기 때문에 OpenSearch는 변경을 감지하지 못하고 no-op 응답을 반환해요.

{
  "_index": "sample-index1",
  "_id": "1",
  "_version": 2,
  "result": "noop",
  "_shards": {
    "total": 0,
    "successful": 0,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}

no-op이 감지되면 _shards.total이 0인데, 이는 어떤 샤드 작업도 수행되지 않았음을 나타내요.

detect_noop을 false로 설정하면 no-op 감지를 비활성화할 수 있어요. 그러면 값이 변하지 않았더라도 OpenSearch가 문서를 다시 인덱싱하게 돼요.

POST /sample-index1/_update/1
{
  "doc": {
    "first_name": "Bruce",
    "last_name": "Wayne",
    "age": 35
  },
  "detect_noop": false
}

noop 감지를 비활성화하면 콘텐츠가 동일하더라도 OpenSearch가 문서를 다시 인덱싱하고 버전 번호를 증가시켜요.

예제 응답 (Example response)

{
  "_index": "sample-index1",
  "_id": "1",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 4,
  "_primary_term": 17
}

응답 본문 필드 (Response body fields)

다음 표는 모든 응답 본문 필드를 보여줘요.

필드 설명
_index 인덱스의 이름이에요.
_id 문서의 ID예요.
_version 문서의 버전이에요. 문서가 업데이트될 때마다 증가해요.
result 업데이트 작업의 결과예요. 문서가 성공적으로 업데이트되면 updated를, upsert 작업이 새 문서를 만들면 created를, 변경이 없으면 noop을 반환해요.
_shards 클러스터 샤드에 대한 자세한 정보예요.
_shards.total 전체 샤드(프라이머리와 복제본) 수예요.
_shards.successful 업데이트 작업을 성공적으로 처리한 샤드 수예요.
_shards.failed 업데이트 작업을 처리하지 못한 샤드 수예요.
_seq_no 문서가 업데이트될 때 할당된 시퀀스 번호예요. 낙관적 동시성 제어에 사용돼요.
_primary_term 문서가 업데이트될 때 할당된 프라이머리 텀이에요. 낙관적 동시성 제어를 위해 _seq_no와 함께 사용돼요.

고급 스크립트 예제 (Advanced script examples)

다음 예제들은 문서 업데이트를 위한 고급 스크립팅 기능을 보여줘요.

배열에 항목 추가 (Adding items to an array)

스크립트를 사용해 배열 필드에 항목을 추가할 수 있어요. 다음 예제는 gadgets 배열에 gadget을 추가해요(gadget이 목록에 이미 있더라도 추가돼요).

배열에서 항목 제거 (Removing items from an array)

스크립트를 사용해 배열에서 항목을 제거할 수 있어요. Painless의 remove 함수는 제거하려는 요소의 배열 인덱스를 받아요. 런타임 오류를 피하려면 먼저 항목이 존재하는지 확인하세요. 목록에 중복 항목이 있으면 이 스크립트는 하나의 발생 항목만 제거해요.

필드 추가와 제거 (Adding and removing fields)

스크립트를 사용해 문서에서 필드를 추가하거나 제거할 수 있어요. 다음 예제는 새 필드를 추가해요.

다음 예제는 필드를 제거해요.

작업 유형 변경 (Changing the operation type)

스크립트를 사용해 문서 콘텐츠에 따라 실행되는 작업을 변경할 수 있어요. 다음 예제는 gadgets 필드에 kryptonite가 있으면 문서를 삭제하고, 그렇지 않으면 아무 작업도 수행하지 않아요(noop).

오류 응답 (Error responses)

다음 예제들은 Update Document API를 사용할 때 만날 수 있는 일반적인 오류 응답을 보여줘요.

문서를 찾을 수 없음 (Document not found)

upsert 작업을 사용하지 않고 인덱스에 존재하지 않는 문서를 업데이트하려 하면 OpenSearch가 404 오류를 반환해요.

{
  "error": {
    "root_cause": [
      {
        "type": "document_missing_exception",
        "reason": "[1]: document missing",
        "index": "sample-index1",
        "shard": "0",
        "index_uuid": "aAsFqTI0Tc2W0LCWgPNrOA"
      }
    ],
    "type": "document_missing_exception",
    "reason": "[1]: document missing",
    "index": "sample-index1",
    "shard": "0",
    "index_uuid": "aAsFqTI0Tc2W0LCWgPNrOA"
  },
  "status": 404
}

이 오류를 피하려면 문서가 존재하지 않을 때 문서를 만들도록 upsert 작업을 사용하세요.

버전 충돌 (Version conflict)

if_seq_no와 if_primary_term 파라미터로 낙관적 동시성 제어를 사용 중이고 문서가 마지막으로 읽은 이후 수정되었다면, OpenSearch가 409 충돌 오류를 반환해요.

{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[1]: version conflict, required seqNo [3], primary term [1]. current document has seqNo [4] and primary term [1]",
        "index": "sample-index1",
        "shard": "0",
        "index_uuid": "aAsFqTI0Tc2W0LCWgPNrOA"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[1]: version conflict, required seqNo [3], primary term [1]. current document has seqNo [4] and primary term [1]",
    "index": "sample-index1",
    "shard": "0",
    "index_uuid": "aAsFqTI0Tc2W0LCWgPNrOA"
  },
  "status": 409
}

이 오류를 처리하려면 문서의 최신 버전을 가져와 올바른 if_seq_no와 if_primary_term 값으로 업데이트를 다시 시도하거나, retry_on_conflict 파라미터로 작업을 자동 재시도하세요.

스크립트 컴파일 오류 (Script compilation error)

Painless 스크립트에 오류가 있으면 OpenSearch가 컴파일 실패에 대한 세부 정보와 함께 400 오류를 반환해요.

{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "failed to execute script"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "failed to execute script",
    "caused_by": {
      "type": "script_exception",
      "reason": "compile error",
      "script_stack": [
        "ctx._source.value = params.newValue",
        "                         ^---- HERE"
      ],
      "script": "ctx._source.value = params.newValue",
      "lang": "painless",
      "position": {
        "offset": 25,
        "start": 0,
        "end": 34
      },
      "caused_by": {
        "type": "illegal_argument_exception",
        "reason": "cannot resolve symbol [params.newValue]"
      }
    }
  },
  "status": 400
}

오류 응답의 script_stack과 caused_by 필드를 검토해서 스크립트 오류를 식별하고 고치세요.

필요한 권한 (Required permissions)

Security plugin을 사용한다면 indices:data/write/update 권한이 있는지 확인하세요.

더 알아보기 (Learn more)