批量更高效

    bulk的请求主体的格式稍微有些不同:

    这种格式就类似于一个用"\n"字符来连接的单行json一样。下面是两点注意事项:

    • 每一行都结尾处都必须有换行字符"\n"最后一行也要有。这些标记可以有效地分隔每行。
    • 这些行里不能包含非转义字符,以免干扰数据的分析 — — 这也意味着JSON不能是pretty-printed样式。

    在《bulk格式》一章中,我们将解释为何bulk API要使用这种格式。


    action/metadata 行指定了将要在哪个文档中执行什么操作

    其中action必须是index, create, update或者deletemetadata 需要指明需要被操作文档的_index, _type以及_id,例如删除命令就可以这样填写:

    1. { "delete": { "_index": "website", "_type": "blog", "_id": "123" }}

    在你进行index以及create操作时,request body 行必须要包含文档的_source数据——也就是文档的所有内容。

    1. { "create": { "_index": "website", "_type": "blog", "_id": "123" }}
    2. { "title": "My first blog post" }

    如果没有指定_id,那么系统就会自动生成一个ID:

    完成以上所有请求的bulk如下:

    1. POST /_bulk
    2. { "delete": { "_index": "website", "_type": "blog", "_id": "123" }} <1>
    3. { "create": { "_index": "website", "_type": "blog", "_id": "123" }}
    4. { "title": "My first blog post" }
    5. { "index": { "_index": "website", "_type": "blog" }}
    6. { "title": "My second blog post" }
    7. { "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} }
    8. { "doc" : {"title" : "My updated blog post"} } <2>
    1. 注意delete操作是如何处理request body的,你可以在它之后直接执行新的操作。

    2. 请记住最后有换行符

    Elasticsearch会返回含有items的列表、它的顺序和我们请求的顺序是相同的:

    1. "took": 4,
    2. "errors": false, <1>
    3. "items": [
    4. { "delete": {
    5. "_index": "website",
    6. "_type": "blog",
    7. "_id": "123",
    8. "_version": 2,
    9. "found": true
    10. }},
    11. { "create": {
    12. "_index": "website",
    13. "_type": "blog",
    14. "_id": "123",
    15. "_version": 3,
    16. "status": 201
    17. }},
    18. { "create": {
    19. "_index": "website",
    20. "_type": "blog",
    21. "_id": "EiwfApScQiiy7TIKFxRCTw",
    22. "_version": 1,
    23. "status": 201
    24. }},
    25. { "update": {
    26. "_index": "website",
    27. "_type": "blog",
    28. "_id": "123",
    29. "_version": 4,
    30. "status": 200
    31. }}
    32. ]
    33. }}
    1. 所有的请求都被成功执行。

    每一个子请求都会被单独执行,所以一旦有一个子请求失败了,并不会影响到其他请求的成功执行。如果一旦出现失败的请求,error就会变为true,详细的错误信息也会出现在返回内容的下方:

    1. {
    2. "errors": true, <1>
    3. "items": [
    4. { "create": {
    5. "_index": "website",
    6. "_type": "blog",
    7. "_id": "123",
    8. "status": 409, <2>
    9. "error": "DocumentAlreadyExistsException <3>
    10. [[website][4] [blog][123]:
    11. document already exists]"
    12. }},
    13. { "index": {
    14. "_index": "website",
    15. "_type": "blog",
    16. "_id": "123",
    17. "_version": 5,
    18. "status": 200 <4>
    19. }}
    20. ]
    21. }
    1. 至少有一个请求错误发生。
    2. 这条请求的状态码为409 CONFLICT
    3. 错误信息解释了导致错误的原因。
    4. 第二条请求的状态码为200 OK

    这也更好地解释了bulk请求是独立的,每一条的失败与否 都不会影响到其他的请求。

    或许你在批量导入大量的数据到相同的index以及type中。每次都去指定每个文档的metadata是完全没有必要的。在mget API中,bulk请求可以在URL中声明/_index 或者/_index/_type

    1. POST /website/_bulk
    2. { "index": { "_type": "log" }}
    3. { "event": "User logged in" }

    你依旧可以在metadata行中使用_index以及_type来重写数据,未声明的将会使用URL中的配置作为默认值:

    整个数据将会被处理它的节点载入内存中,所以如果请求量很大的话,留给其他请求的内存空间将会很少。bulk应该有一个最佳的限度。超过这个限制后,性能不但不会提升反而可能会造成宕机。

    最佳的容量并不是一个确定的数值,它取决于你的硬件,你的文档大小以及复杂性,你的索引以及搜索的负载。幸运的是,这个平衡点 很容易确定:

    试着去批量索引越来越多的文档。当性能开始下降的时候,就说明你的数据量太大了。一般比较好初始数量级是1000到5000个文档,或者你的文档很大,你就可以试着减小队列。
    有的时候看看批量请求的物理大小是很有帮助的。1000个1KB的文档和1000个1MB的文档的差距将会是天差地别的。比较好的初始批量容量是5-15MB。