Document APIs之update API与update by Query API

本文详细介绍了Elasticsearch的Update API和Update By Query API的使用方法,包括更新文档、脚本更新、部分文档更新、冲突处理、 Upserts、条件更新以及查询更新等功能。此外,还讨论了版本控制、并发更新、滚动更新和节流控制等相关概念。
摘要由CSDN通过智能技术生成

Update API

update API允许基于提供的脚本更新文档。该操作从索引中获取文档(并行于分片上),运行脚本(使用可选的脚本语言和参数),索引返回结果(还允许删除或忽略该操作)。它使用版本控制来确保在“get”和“reindex”期间没有发生更新。

注意,这个操作仍然意味着文档的完全重索引,它只是删除了一些网络往返,并减少了get和索引之间版本冲突的机会。需要启用_source字段才能使此功能正常工作。

例如,让我们索引一个简单的文档:

PUT test/_doc/1

{

    "counter" : 1,

    "tags" : ["red"]

}

脚本更新

现在,我们可以执行一个增加计数器的脚本:

POST test/_update/1

{

    "script" : {

        "source": "ctx._source.counter += params.count",

        "lang": "painless",

        "params" : {

            "count" : 4

        }

    }

}

我们可以添加标签来进行标记(注意,如果标签存在,仍然会添加,因为是一个列表)

POST test/_update/1

{

    "script" : {

        "inline": "ctx._source.tags.add(params.tag)",

        "lang": "painless",

        "params" : {

            "tag" : "blue"

        }

    }

}

我们可以从标签列表中删除一个标签。注意,用于删除标记的Painless function将希望删除的元素的数组索引作为参数,因此需要一个更大的逻辑来定位它避免运行时发生错误。注意,如果标记在列表中出现多次,则只会删除一次

POST test/_update/1

{

    "script" : {

        "source": "if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }",

        "lang": "painless",

        "params" : {

            "tag" : "blue"

        }

    }

}

除了_source,下列变量通过 ctx域也都是可用的:_index,_type,_id,_version,_routing,_parent,和_now(当前的时间戳)。

我们也可以将新字段添加到文档:

POST test/_update/1

{

    "script" : "ctx._source.new_field = 'value_of_new_field'"

}

或者从文件中删除字段:

POST test/_update/1

{

    "script" : "ctx._source.remove('new_field')"

}

而且,我们甚至可以改变已执行的操作。这个例子就是删除文档,如果 tags包含 green,否则就什么也不做(noop):

POST test/_update/1
{
    "script" : {
        "source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }",
        "lang": "painless",
        "params" : {
            "tag" : "green"
        }
    }
}

Updates with a partial document

更新API还支持传递部分文档,该部分文档将合并到现有文档中(简单的递归合并,对象的内部合并,替换核心“键/值”和数组)。要完全替换现有文档,应使用indexAPI。以下部分更新会向现有文档添加新字段:

POST test/_update/1
{
    "doc" : {
        "name" : "new_name"
    }
}

如果同时 doc 和 script 被指定,那么 doc将被忽略。最好是把部分文件对应脚本本身。

检测noop更新

如果doc指定,则其值与现有值合并_source。默认情况下,不更改任何内容的更新将会被检测并返回"result": "noop"如下所示:

POST test/_update/1
{
    "doc" : {
        "name" : "new_name"
    }
}

如果 name是 new_name未发生改变,那么被发送之前整个更新请求将被忽略。如果请求被忽略了则在 result响应中的返回 noop。

{
   "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
   },
   "_index": "test",
   "_type": "_doc",
   "_id": "1",
   "_version": 7,
   "result": "noop"
}

你可以通过设置 “detect_noop” 禁用此行为:

POST test/_update/1
{
    "doc" : {
        "name" : "new_name"
    },
    "detect_noop": false
}

Upserts

如果文档尚不存在,则upsert元素的内容将作为新文档插入。如果文档确实存在,那么 script将执行以下操作:

POST test/_update/1
{
    "script" : {
        "source": "ctx._source.counter += params.count",
        "lang": "painless",
        "params" : {
            "count" : 4
        }
    },
    "upsert" : {
        "counter" : 1
    }
}

scripted_upsert

如果您希望脚本运行,无论文档是否存在即脚本处理初始化文档而不是 upsert元那么设置scripted_upsert为true:

POST sessions/_update/dh3sgudg8gsrgl
{
    "scripted_upsert":true,
    "script" : {
        "id": "my_web_session_summariser",
        "params" : {
            "pageViewEvent" : {
                "url":"foo.com/bar",
                "response":404,
                "time":"2014-01-01 12:32"
            }
        }
    },
    "upsert" : {}
}

doc_as_upsert

设置doc_as_upsert为ture会将一个新文档作为部分内容新增到一个已有的文档中去:

POST test/_update/1
{
    "doc" : {
        "name" : "new_name"
    },
    "doc_as_upsert" : true
}

参数

retry_on_conflict

在更新的get和indexing阶段之间,另一个进程可能已经更新了同一文档。默认情况下,更新将因版本冲突异常而失败。该retry_on_conflict 参数控制在最终抛出异常之前重试更新的次数。

routing 

路由用于将更新请求路由到正确的分片,并在更新的文档不存在时为upsert请求设置路由。不能用于更新现有文档的路由。

timeout

等待碎片变为可用的超时时间

wait_for_active_shards

在继续更新操作之前需要处于活动状态的分片副本数。

refresh 

控制何时此请求所做的更改对搜索可见

_source 

允许控制是否以及如何在响应中返回更新的源。默认情况下,不会返回更新的源

Version 

更新API在内部使用Elasticsearch版本控制支持,以确保在更新期间文档不会更改。您可以使用该version 参数指定仅在文档的版本与指定版本匹配时才更新文档。

注: 更新API不支持内部版本以外的版本控制

更新API不支持外部(版本类型external和external_gte)或强制(版本类型force)版本控制,因为它会导致Elasticsearch版本号与外部系统不同步。请改用 indexAPI

if_seq_no 和 if_primary_term

更新操作可以是有条件的,只有在为文档的最后一次修改分配序列号和if_seq_no和if_primary_term参数指定的主要术语时才能执行。如果检测到不匹配,则操作将导致a VersionConflictException 和状态代码为409.

Update By Query API

_update_by_query最简单的用法就是不更改源的情况下对索引中的每个文档执行更新。这对于获取新属性或其他在线映射更改非常有用。这是API:

POST twitter/_update_by_query?conflicts=proceed

response

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

_update_by_query获取索引的快照,当它开始和进行索引时发现使用的是 internal的版本。这意味着如果在获取快照时或者进行索引请求时文档发生改变,那么将会发生版本冲突。当索引请求被处理的时间之间变化。当版本匹配文档被更新时候,版本号递增。

注意:因为 internal版本不支持的值 0 作为一个有效的版本号,因此无法使用版本等于零的文档进行更新,_update_by_query并且将使请求失败。

所有的更新和查询失败导致 _update_by_query中止,并在返回错误的响应。已执行的更新仍然存在。换句话说,该方法不支持回滚,仅中止。虽然第一次失败将导致中止,由失败的请求返回所有故障将在 failures元素体现。因此,有可能存在一些失败的实体。

如果只想简单地计算版本冲突,而不想导致_update_by_query中止,可以在url上设置conflict =proceed,或者在请求主体中设置“conflicts”:“proceed”。 第一个示例之所以这么做,是因为它只是试图获取一个在线映射更改,而一个版本冲突简单意味着_update_by_query的开始时间和它试图更新文档的时间之间更新了冲突文档。这样做具有一定的优点,因为该更新将获得在线映射更新。

回到API格式,这将更新twitter索引中的推文:

POST twitter/tweet/_update_by_query?conflicts=proceed

还可以使用查询DSL限制_update_by_query。这将为用户kimchy更新twitter索引中的所有文档:

POST twitter/_update_by_query?conflicts=proceed
{
  "query": {   -------1
    "term": {
      "user": "kimchy"
    }
  }
}

1: 查询必须作为值传递给查询键,方法与搜索API相同。您还可以像使用搜索API一样使用q参数

到目前为止,我们只更新了文档,而没有更改它们的源。这确实对构建一个新内容很有用,但这只是乐趣的一半。_update_by_query支持更新文档的脚本。这将增加kimchy所有tweet上的like字段:

POST twitter/_update_by_query
{
  "script": {
    "source": "ctx._source.likes++",
    "lang": "painless"
  },
  "query": {
    "term": {
      "user": "kimchy"
    }
  }
}

正如在 Update API 中可以设置 ctx.op来改变所执行的操作:

noop: 设置ctx.op = "noop",如果脚本确定不必进行任何更改。这将导致_update_by_query从其更新中省略该文档。这将在响应的 noop 中被展示。

delete设置ctx.op = "delete",如果脚本确定文档必须被删除。这将在响应的 deleted 中被展示。

设置 ctx.op到别的地方是错误的。设置任何其它领域中 ctx 也是错误的。

注意,当我们不指定 conflicts=proceed 时,在这种情况下,我们希望版本冲突中止该过程,以便我们可以处理失败。

此 API 不允许移动文件本身,只需修改其源。这是有意而为之,我们并没有获得从其原始位置删除该文件的权限。

也可以同时在多个索引上完成这一切,就像搜索API一样:

POST twitter,blog/_update_by_query

如果您提供,routing则路由将复制到滚动查询,将进程限制为与该路由值匹配的分片:

POST twitter/_update_by_query?routing=1

默认情况下,_update_by_query使用滚动批次为1000。您可以使用scroll_size URL参数更改大小:

POST twitter/_update_by_query?scroll_size=100

update_by_query还可以通过如下方式指定管道来使用Ingest节点特性:

PUT _ingest/pipeline/set-foo
{
  "description" : "sets foo",
  "processors" : [ {
      "set" : {
        "field": "foo",
        "value": "bar"
      }
  } ]
}
POST twitter/_update_by_query?pipeline=set-foo

URL 参数

除了标准参数如pretty之外,Query API的Update By还支持refresh、wait_for_completion、wait_for_active_shards、timeout和scroll

wait_for_active_shards控制在继续请求之前必须有多少个分片必须处于活动状态。timeout控制每个写入请求等待不可用分片变成可用的时间。两者都能正确地在Bulk API中工作。

requests_per_second可以设置为任何正数(1.4,6,1000等),来作为“delete-by-query”每秒请求数的节流阀数字,或者将其设置为-1以禁用限制。节流是在批量批次之间等待,以便它可以操纵滚动超时。等待时间是批次完成的时间除以requests_per_second和写入时间之间的差额。默认的批处理大小是1000,所以如果requests_per_second设置为500:

target_time = 1000 / 500 per second = 2 seconds

wait_time = target_time - write_time = 2 seconds - .5 seconds = 1.5 seconds

由于分批处理没有被分解成多个批量请求,所以会导致Elasticsearch创建许多请求,然后等待一段时间再开始下一组。这是“突发”而不是“平滑”。默认值为-1。

响应

Json 响应如下:

{

  "took" : 147,

  "timed_out": false,

  "total": 5,

  "updated": 5,

  "deleted": 0,

  "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" : [ ]

}

took

从整个操作的开始到结束的毫秒数。

timeout 如果在通过查询执行删除期间执行的任何请求超时,则将此标志设置为ture。

Total

已成功处理的文档数。

deleted

成功删除的文档数。

batches

通过查询删除的滚动响应数量。

version_conflicts 按查询更新命中的版本冲突数。

noops

对于按查询删除,此字段始终等于零。它只存在,以便通过查询删除,按查询更新和重新索引API返回具有相同结构的响应。

retries

根据查询删除的重试次数。bulk是重试的批量操作的数量,search是重试的搜索操作的数量。

throttled_millis

请求休眠的毫秒数,与`requests_per_second`一致。

requests_per_second查询更新期间每秒有效执行的请求数。

throttled_until_millis

在_update_by_query响应中,该字段应该始终等于零。它只有在使用任务API时才有意义,在任务API中,它指示下一次(纪元以毫秒为单位)再次执行节流请求,以符合requests_per_second

failures

如果在此过程中存在任何不可恢复的错误,则会出现故障数组。如果这不是空的,那么请求因为那些失败而中止。使用批处理实现查询删除,任何故障都会导致整个进程中止,但当前批处理中的所有故障都将收集到阵列中。您可以使用该conflicts选项来防止reindex在版本冲突中中止

Works with the Task API

你可以使用任务API通过查询请求获取所有运行更新的状态:

GET _tasks?detailed=true&actions=*byquery

Response:

{
  "nodes" : {
    "r1A2WoRbTwKZ516z6NEs5A" : {
      "name" : "r1A2WoR",
      "transport_address" : "127.0.0.1:9300",
      "host" : "127.0.0.1",
      "ip" : "127.0.0.1:9300",
      "attributes" : {
        "testattr" : "test",
        "portsfile" : "true"
      },
      "tasks" : {
        "r1A2WoRbTwKZ516z6NEs5A:36619" : {
          "node" : "r1A2WoRbTwKZ516z6NEs5A",
          "id" : 36619,
          "type" : "transport",
          "action" : "indices:data/write/update/byquery",
          "status" : {           -------------1   
            "total" : 6154,
            "updated" : 3500,
            "created" : 0,
            "deleted" : 0,
            "batches" : 4,
            "version_conflicts" : 0,
            "noops" : 0,
            "retries": {
              "bulk": 0,
              "search": 0
            },
            "throttled_millis": 0
          },
          "description" : ""
        }
      }
    }
  }
}

1: 此对象包含实际状态。它就像是响应json,添加了重要的total字段。 total是重建索引希望执行的操作总数。您可以通过添加的updated、created和deleted的字段来估计进度。当它们的总和等于total字段时,请求将完成。

使用任务id,您可以直接查找任务。下面的示例检索关于任务r1A2WoRbTwKZ516z6NEs5A:36619的信息:

GET /_tasks/r1A2WoRbTwKZ516z6NEs5A:36619

这个API的优点是它与wait_for_completion=false集成,以透明地返回已完成任务的状态。如果任务完成并且wait_for_completion=false被设置,那么它将返回results或error字段。此功能的成本是wait_for_completion=false在.tasks/task/${taskId}创建的文档,是否删除该文档取决于你。

配合取消任务API使用

所有根据查询删除都能使用Task Cancel API取消:

POST _tasks/r1A2WoRbTwKZ516z6NEs5A:36619/_cancel

可以使用上面的任务API找到task_id。 取消正常会很快发生,但可能需要几秒钟。上面的任务状态API将继续列出delete by query任务,直到此任务检查它已被取消并终止自身。

重置节流阀

request_per_second的值可以在通过查询删除时使用_rethrottle API更改:

POST _update_by_query/r1A2WoRbTwKZ516z6NEs5A:36619/_rethrottle?requests_per_second=-1

可以使用上面的任务API找到task_id。

就像在_update_by_query API中设置它一样,request_per_second可以是-1来禁用限制,或者任何十进制数字,如1.7或12,以节制到该级别。加快查询速度的Rethrottling将立即生效,但是减慢查询速度的Rethrottling将在完成当前批处理后生效。这可以防止滚动超时。

Slicing

Update by query支持滚动切分以并行化更新过程。这种并行化可以提高效率,并提供一种方便的方法将请求分解为更小的部分。

手动Slicing

通过为每个请求提供Slicing id和切分总数,手动切分查询更新::

POST twitter/_update_by_query
{
  "slice": {
    "id": 0,
    "max": 2
  },
  "script": {
    "source": "ctx._source['extra'] = 'test'"
  }
}
POST twitter/_update_by_query
{
  "slice": {
    "id": 1,
    "max": 2
  },
  "script": {
    "source": "ctx._source['extra'] = 'test'"
  }
}

您可以通过以下方式验证:

GET _refresh
POST twitter/_search?size=0&filter_path=hits.total

其结果一个合理的total像这样:

{
  "hits": {
    "total": {
        "value": 120,
        "relation": "eq"
    }
  }
}

自动切分

你还可以让根据delete-by-query自动并行使用sliced scroll去切分在_id上 。使用slices指定使用的数字来确定切分数量。

POST twitter/_update_by_query?refresh&slices=5

{

  "script": {

    "source": "ctx._source['extra'] = 'test'"

  }

}

您可以通过以下方式验证:

POST twitter/_search?size=0&q=extra:test&filter_path=hits.total

其结果一个合理的total像这样:

{
  "hits": {
    "total": {
        "value": 120,
        "relation": "eq"
    }
  }
}

将切片设置为auto将让Elasticsearch选择要使用的切片数量。此设置将在一定限度内使用每个分片一个切分。如果有多个源索引,它将根据碎片数量最少的索引选择切片的数量

将slices添加到_update_by_query中可以自动执行上述部分中使用的手动过程,创建子请求,这意味着它有一些怪癖:

  • 您可以在Task API中看到这些请求。这些子请求是具有slices请求任务的“子”任务。
  • 获取slices请求任务的状态只包含已完成切分的状态。
  • 这些子请求可以单独寻址,例如取消和重置节流阀。
  • slices的重置节流阀请求将按相应的重新计算未完成的子请求。
  • slices的取消请求将取消每个子请求。
  • 由于slices的性质,每个子请求将不会获得完全均匀的文档部分。所有文件都将被处理,但有些片可能比其他片大。预期更大的切分可以有更均匀的分布。
  • 带有slices请求的request_per_second和size的参数相应的分配给每个子请求。结合上述关于分布的不均匀性,您应该得出结论,使用切分大小可能不会导致正确的大小文档为_delete_by_query。
  • 每个子请求都会获得源索引的略有不同的快照,尽管这些都是大致相同的时间。

挑选切分数量

如果自动切分,设置slices为auto将为大多数索引选择合理的数字。如果您手动切片或以其他方式调整自动切片,请使用这些指南。

当数量slices等于索引中的分片数时,查询性能最有效。如果该数字很大(例如,500),请选择较小的数字,因为太多slices会损害性能。设置 slices高于分片数通常不会提高效率并增加开销。

随着切分的数量的增加,更新性能在可用资源之间呈线性扩展。

查询或更新性能是否主导运行时取决于重新编制索引的文档和群集资源。

选择一个新的属性

假设您创建了一个没有动态映射的索引,用数据填充它,然后添加一个映射值来从数据中获取更多字段:

PUT test
{
  "mappings": {
    "dynamic": false,      -------1
    "properties": {
      "text": {"type": "text"}
    }
  }
}

POST test/_doc?refresh
{
  "text": "words words",
  "flag": "bar"
}
POST test/_doc?refresh
{
  "text": "words words",
  "flag": "foo"
}
PUT test/_mapping   ---------2
{
  "properties": {
    "text": {"type": "text"},
    "flag": {"type": "text", "analyzer": "keyword"}
  }
}

1: 这意味着新字段不会被索引,只存储在_source中。

2: 这将更新映射以添加新的标志字段。要获取新字段,必须用它重新索引所有文档。

搜索数据将不会发现任何东西:

POST test/_search?filter_path=hits.total
{
  "query": {
    "match": {
      "flag": "foo"
    }
  }
}
{
  "hits" : {
    "total": {
        "value": 0,
        "relation": "eq"
    }
  }
}

但是您可以发出_update_by_query请求来获取新的映射:

POST test/_update_by_query?refresh&conflicts=proceed
POST test/_search?filter_path=hits.total
{
  "query": {
    "match": {
      "flag": "foo"
    }
  }
}
{
  "hits" : {
    "total": {
        "value": 1,
        "relation": "eq"
    }
  }
}

在向多字段添加字段时,您可以执行完全相同的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值