Blog

2014.10.15 - 번역 - Elasticsearch from the Top Down ...

drscg 2019. 1. 6. 17:05

UPDATE: This article refers to our hosted Elasticsearch offering by an older name, Found. Please note that Found is now known as Elastic Cloud.

이 게시물은 기존에 Found라는 이름으로 제공된 Elasticsearch 서비스에 관한 것이다. Found은 이제 Elasticsearch Cloud로 알려져 있다.

The previous article in this series, Elasticsearch from the Bottom Up, covered essential data structures within a single shard. In this article, we will look at the distributed nature of Elasticsearch.

이 시리즈의  이전 게시물에서는 단일 shard의 필수 data 구조에 대해 살펴보았다. 이 게시물에서는 Elasticsearch의 분산 구조를 살펴보겠다.

Introduction

In the previous article, Elasticsearch from the Bottom Up, we started with fairly low level data structures, and ascended up the abstraction layer to put them into context. We focused on what happens within a single shard, however.

이전 게시물인 Elasticsearch from the Bottom Up 에서, 우리는 상당히 낮은 수준의 데이터 구조로 시작했고, 이를 내용에 포함시키기 위해 추상화 계층으로 올라갔다. 그러나 우리는 단일 shard에서 어떤 일이 일어나는지에 대해 집중했다.

To look at how all of this fits together in a distributed setting, it is easier to start at the other end: from the “top”, as observed from a user’s perspective.

분산 환경에서 이 모든 것이 어떻게 결합되는지 보려면, 사용자의 관점에서 관찰하는 것 처럼, 반대쪽 끝인 "맨 위(top)"에서 시작하는 것이 더 쉽다.

We will look at what happens when a user sends an index and a search request to Elasticsearch, and how the requests ripple through the network, until we reach the lower level structures covered earlier. First, we’ll look at the node accepting the request and its role as a coordinator. We’ll look at how it routes requests to the shards’ primaries for indexing requests, and load balances across replicas for search requests.

사용자가 Elasticsearch에 indexing과 검색 request를 보냈을 때 어떤 일이 발생하는지, 그리고 이전에 설명한 하위 레벨 구조에 도달할 때까지, request가 네트워크를 통해 어떻게 동작하는지 살펴보겠다. 먼저, request를 수락하는 node와 coordinator로서 그 node의 역할을 살펴보겠다. indexing request를 primary shard에 route하는 방법과 검색 request를 replica간에 로드밸런싱하는 방법을 살펴보겠다.

After having been routed to the right shards, we’ll look at what happens at the shard level — such as what constitutes a “successfull” index operation, and transformations necessary to convert documents and searches to their Lucene equivalents. For search request, we’ll also look at the scatter/gather process, which can happen in multiple rounds.

올바른 shard로 route한 후에, "성공적인" index 작업을 구성하는 것과 같은, shad level에서 어떤 일이 발생하는지와 document와 검색을 Lucene과 동등한 수준으로 변경하는데 필요한 변환을 살펴볼 것이다. 검색 request를 위해, 여러 번 발생할 수있는 scatter/gather 프로세스도 살펴볼 것이다.

A Cluster of Nodes

To describe how Elasticsearch works, we need some sort of cluster topology. The cluster in the figure describes the kind of cluster we assume. We have one Elasticsearch index with two shards, replicated across nodes in two different “zones” for availability — with master only nodes running across three zones. Also, we have two client nodes, which we will be sending requests through.

Elasticsearch의 작 방식을 설명하기 위해, 어떤 cluster가 필요하다. 그림의 cluster는 우리가 가정하는 cluster를 설명한다. 우리는 1개의 Elasticsearch index에 2개의 shard를 가지고 있으며, 가용성을 위해 두 개의 "zone"에 있는 node에 걸쳐 replicate되어 있다. master node는 세 개의 zone에서 실행된다. 또한 2개의 client node를 가지며, 이를 통해 request를 보내게 된다.

Sample Cluster Topology

Sample Cluster Topology

Different nodes in a cluster can have different roles (data and/or master – or none as a client) as well as properties (such as zone). We have data nodes, master nodes and client nodes - in different zones, i.e. with different node.zone properties. We focus on the data nodes in this article. To learn more about the other kinds of nodes, we recommend reading Elasticsearch in Production, and Java Clients for Elasticsearch.

cluster의 다른 node는 속성(예 : zone)뿐만 아니라 다른 role(data 및/또는 master 또는 client가 아님)을 가질 수 있다. data node, master node 그리고 client node가 서로 다른 zone, 즉 node.zone 속성이 서로 다르다. 이 게시물에서는 data node에 중점을 둔다. 다른 종류의 node에 대한 자세한 내용은  Elasticsearch in Production 그리고 Java Clients for Elasticsearch를 읽을 것을 추천한다.

Request Coordinators

When you send a request to a node in Elasticsearch, that node becomes the coordinator of that request. It will decide which nodes and shards to route a request to, how to merge different nodes’ responses, as well as decide when the request is “done”. While Elasticsearch handles this transparently for you, advanced partitioning schemes require knowledge about internal processing and routing. Given the cluster described above, the client node will act as the coordinator.

Elasticsearch에서 node에 request를 보내면 해당 node가 해당 requestcoordinator가 된다. 어떤 node와 shard로 request를 route할지, 다른 node의 response를 병합하는 방법, request가 언제 "완료"되었는지를 결정한다. Elasticsearch가 이것을 투명하게 처리하는 동안, advanced patitioning schema는 내부 처리 및 routing에 대한 지식이 필요하다. 위에서 설명한 cluster를 보면, client node는 coordinator로 동작할 것이다.

To be able to act as a coordinator, the node needs to know the cluster’s state. The cluster state is replicated to every node in the cluster. It has things like the shard routing table (which nodes host which indexes and shards), metadata about every node (such as where it runs and what attributes the node has), and index mappings (which can contain important routing configuration) and templates. The cluster state being replicated to all nodes is an important reason why the mappings need to be reasonably sized

coordinator의 역할을 수행하려면, node가 cluster의 상태(state)를 알아야 한다. cluster state는 cluster의 모든 node에 복제된다. shard routing table(어떤 node가 어떤 index와 shard를 가지고 있는지), 모든 node에 관한 metadata(node가 실행되는 위치, node의 속성 등), index mapping(중요한 routing 설정을 포함 할 수 있는) 및 template과 같은 것들이 cluster state에 있다. 모든 node에 복제되는 cluster state는 mapping이 적절한 크기로 조정되어야 하는 중요한 이유이다.

For a search request, coordinating means picking replicas of shards to send the request to for further processing, possibly in multiple rounds, which we’ll look more into later. Picking a replica is done either at random, or influenced by the request’s preference.

검색 request의 경우, coordinating은 더 많은 처리를 위해 shard의 replica를 선택하는 것을 의미한다. (아마도 나중에 여러 번 더 살펴볼 것이다.). replica 선택은 무작위로 수행되거나 request의 preference 에 따라 영향을 받는다.

An index request (i.e. one where you create, update or delete a document) is slightly different. In this case, the request must be routed to the primaries of the shards, and also to a certain amount of replicas - depending on the index request’s write_consistency. Write consistency can be either onequorum (the default, though equivalent to one unless you have ≥2 replicas), or all — i.e. requiring one, most or every replica to have acknowledged.

index request(즉, document를 작성, update 또는 삭제하는 request)는 약간 다르다. 이 경우, index request의 write_consistency에 따라, request를 shard의 primary뿐만 아니라 특정 양의 replica로 route해야 한다. write consistency는 onequorum(기본값, replica가 2개 이상인 경우 외에는 one과 동일함) 또는 all이다. 즉, 하나, 대부분 또는 모든 replica가 확인되어야 한다.

Index a Document to an Index of … Indexes?

The term “index” is used in a lot of contexts, with different meanings. You index documents, to an Elasticsearch index. The Elasticsearch index has shards, which are Lucene indexes. And those have inverted indexes. This can get confusing.

"index"라는 용어는 많은 문맥에서 다른 의미로 사용된다. document를 Elasticsearch index색인(index)한다. Elasticsearch index에는 Lucene index인 shard가 있다. 그리고, shard에는 inverted index가 있다. 혼란스러울 수 있다.

An important insight is that, conceptually, an Elasticsearch index with two shards is exactly the same as two Elasticsearch indexes with one shard each. Ultimately, they are two Lucene indexes. The difference is largely the convenience Elasticsearch provides via its routing feature. It is possible to achieve the same “manually” having just single shard indexes. (Not that you should!)

중요한 점은, 개념적으로, 두 개의 shard를 가진 Elasticsearch index는 각각 한 개의 shard를 가진 두 개의 Elasticsearch index와 정확히 동일하다는 것이다. 궁극적으로, 그것들은 두 개의 Lucene index이다. 차이점은 Elasticsearch가 routing 기능을 통해 제공하는 편의성이다. 단일 shard index만으로 "수동"으로 동일한 기능을 얻을 수 있다. (그렇게 할 필요는 없겠지만!)

The important part is realizing that an Elasticsearch index is an abstraction on top of a collection of Lucene indexes, through the concept of shards. This helps when you need to wrap your head around more advanced partitioning strategies.

중요한 점은, Elasticsearch, index가 shard라는 개념을 통해, Lucene index 모음 위에 추상화된 것이라는 것을 인식하는 것이다. 이는 고급 partitioning 전략에 대해 이해해야 할 때 도움이 된다.

Index Requests

An index request is one that creates or changes documents in an index. Whether it’s the creation of a new document, a deletion or an update is not very important, so we’ll refer to them all as an “index request”.

index request는 index에 document를 작성하거나 변경하는 request이다. 새로운 document의 작성, 삭제 또는 update냐 하는 것은 중요한 것이 아니므로, 그들 모두를 "index request"라 한다.

Consider the following bulk request:

다음 bulk request를 살펴보자.

{"index": {"_index": "tweets", "_type": "tweet", "_id": "502878691740090369"}} {"user": "mikepluta", "tweet": "Moore's Law for #BigData: The amount of nonsense packed into the term \"BigData\" doubles approximately every two years"} {"index": {"_index": "tweets", "_type": "tweet", "_id": "475944863175671808"}} {"user": "viktorklang", "tweet": "For resilient software, \"What could possibly go wrong?\" should be the famous first words; not last."} {"index": {"_index": "logs-2014-10-14", "_type": "log", "_consistency": "all"}} {"@timestamp": "2014-10-14T12:34:56Z", "message": "Suddenly the Dungeon collapses!! - You die..."}

There are several questions the node that processes this request needs to consider:

이 reques를 처리해야 할 node가 고려해야할 몇 가지가 있다.

  • What is the routing configuration for the tweets and the logs? This can be determined by looking at the mappings in the cluster state. For the tweets, it’s possible that user based routing is in place, so the tweet’s user attribute should determine which shard to route the documents to. We look at some examples in the next section.
    tweets과 logs의 routing configuration은 무엇인가? 이는 cluster state의 mapping을 조사하여 알 수 있다. tweets의 경우 사용자 기반 routing이 가능할 수 있으므로, tweets의  user 속성이 document를 routing할 shard를 결정해야 한다. 다음 섹션에서 몇 가지 예를 살펴 보겠다.
  • After having determined the shards for the two tweets, when can we consider the document to actually be indexed? Since nothing is specified, this defaults to the node’s default action.write_consistency, which defaults to quorum.
    두 개의 tweets에 대한 shard를 결정한 후, 언제 document를 실제로 indexing할 수 있나? 아무것도 지정되지 않았기 때문에, 기본값은 node의 기본값인 action.write_consistency로 설정되며, 기본값은 quorum이다.
  • The log message does not have an ID associated with it. All documents must have one, so the coordinator will have to assign one to it. The document ID is what the coordinator will use to route the request, if the action or mapping does not override it. (It’s a best practice to assign IDs and have idempotent requests, however)
    log message에는 그것과 관련한 ID가 없다. 모든 document에는 하나의 ID가 있어야 하므로, coordinator가 document에 ID를 지정해야 한다. action 또는 mapping이 이를 재정의하지 않는 경우, coordinator가 request를 route하는 데 사용하는 것이다. (그러나, ID를 지정하는, 멱등(idempotent) request가 가정 좋다.
  • The log action specifies a strict _consistency-setting, so the request cannot be acknowledged until every single replica has returned success.
    log action은 엄격한 _consistency 설정을 지정하므로, 모든 단일 replica가 성공을 return할 때까지, request를 확인할 수 없다.
  • When can the bulk request as a whole finish?
    bulk request는 언제 완료되는가?

Routing

Routing specifies which documents go where, and is therefore an important part of how requests flow internally in Elasticsearch, both for search and index requests. It’s integral to designing proper data flow and index partitioning. Here, we focus on the mechanics, since index and shard design is a big topic.

routing은 어떤 document가 어디로 갈지를 지정하므로, 검색 및 index request 모두에 대해 Elasticsearch에서 request가 내부적으로 전달되는 방법에 대한 중요한 부분이다. 그것은 적절한 data 전달과 index partitioning 설계에 필수적이다. index와 shard design은 큰 주제이기 때문에, 여기서 우리는 메커니즘에 중점을 둔다.

Documents are routed based on a routing key, and are placed on shard number \(\mathrm{hash}\left(key\right) \bmod n\), where \(n\) is the number of shards in the index. (For the curious, the hash function used is djb2)

document는 routing key를 기반으로 route되며, shard 번호 \(\mathrm{hash}\left(key\right) \bmod n\)에 배치된다. 여기서 \(n\)은 index의 shard 번호이다. hash function은 djb2이다.

Shard design is largely about deciding on what that key is. If we use the document IDs for the above tweets as the routing key, and assume the tweets index has two shards, we would get \(\mathrm{hash}\left(502878691740090369\right) \bmod 2 = 1\) and \(\mathrm{hash}\left(475944863175671808\right) \bmod 2 = 0\) respectively. The two tweets would be stored in different shards. However, if the mapping would rather specify routing based on the user key, we would find that \(\mathrm{hash}\left(mikepluta\right) \bmod 2 = \mathrm{hash}\left(viktorklang\right) \bmod 2 = 1\). The documents would thus be stored in the same shard. That can be helpful if we only want to search a particular user’s documents.

shard 디자인은 주로 그 key가 무엇인가에 따라 결정된다. 위의 tweets에서 document ID를 routing key로 사용하고, tweets index에 두 개의 shard가 있다고 가정하면, 각각 \(\mathrm{hash}\left(502878691740090369\right) \bmod 2 = 1\) 과 \(\mathrm{hash}\left(475944863175671808\right) \bmod 2 = 0\) 을 얻을 수 있다. 두 개의 tweet은 다른 shard에 저장된다. 그러나, mapping이 user key에 기반한 routing을 지정하는 경우, \(\mathrm{hash}\left(mikepluta\right) \bmod 2 = \mathrm{hash}\left(viktorklang\right) \bmod 2 = 1\) 이다. 따라서, document는 동일한 shard에 저장된다. 특정 사용자의 document만 검색하려는 경우 유용할 수 있다.

Primary Concerns

When an index operation has been routed, it is forwarded to the “primary” for that shard. A shard has exactly one “primary”, and zero or more replicas. In this regard, the primary can be considered the “master” for the shard, and the replicas as “slaves”.

index 작업이 routing되면, 해당 shard의 "primary"로 전달된다. shard는 단 하나의 "primary"와 0 개 이상의 replica가 있다. 이와 관련하여, primary는 shard의 "master"로, replica는 "slave"로 간주될 수 있다.

The primary will act as a coordinator for index operations for that specific shard. It will send the index operations to the relevant replicas, and wait until the required number has acknowlegded before indicating success.

primary는 해당 shard에 대한 index 작업의 coordinator 역할을 한다. 대응하는 replica에 index 작업을 보내고, 필요한 번호가 확인될 때까지 기다린 후 성공을 표시한다.

Sequence of an Index Operation

Sequence of an Index Operation

When a sufficient number of replicas have acknowledged, the primary will report success back to the originating request coordinator, in our case the client node. For the default write consistency of “quorum”, two out of three operations is sufficient, so it is not necessary to wait for the third operation before passing on success.

충분한 수의 replica가 확인되면, primary는 원본 request coordinator(이 경우에는 client node)에게 성공을 보고한다. "quorum"의 write consistency 기본값 때문에, 세 가지 작업 중 두 가지 작업만으로 충분하므로, 성공을 전달하기 전에 세 번째 작업을 기다릴 필요가 없다.

The coordinator/client node sends out operations to separate shards’ primaries in parallel. When all operations have returned, the originating bulk request can also finally be returned.

coordinator/client node는 shard의 primary별로 병렬로 전송한다. 모든 작업이 return되면, 원래의 bulk request가 최종적으로 return된다.

The definition of a “successful” operation is worth looking into.

"성공적인" 작업의 정의를 살펴보자.

In the previous article, we looked at how indexes are built, and how there is a tradeoff between search speed, index compactness, indexing speed, and the time it takes for operations to become visible. Lucene internals like immutable segments were covered, and we emphasized that building these in batches and delaying costly disk synchronization operations were important.

이전 게시물에서, index를 생성하는 방법과 검색 속도, index 압축률, indexing 속도 그리고 작업이 표시되는데 소요되는 시간 사이의 균형을 살펴 보았다. immutable segments 같은 Lucene 내부를 다루었으며, 이들을 일괄적으로 구축하고 비용이 많이 드는 disk 동기화 작업을 지연시키는 것이 중요하다고 강조했다.

All of that seems to run counter to what we want from an index operation: safe and durable, yet quickly acknowledged. To achieve that, Elasticsearch has a “transaction log” (“translog” in the documentation), or a “write-ahead log”, like almost every database system. Being written to the append-only translog is what defines success for a shard, not whether the document is actually part of a live index through a searchable segment.

이 모든 것은 index 작업에서 우리가 원하는 것(안전하고 내구성이 있지만 신속하게 확인되는)과 상반되는 것으로 보인다. 이를 위해, Elasticsearch는 거의 모든 데이터베이스 시스템과 마찬가지로 "transaction log"(문서에서 translog) 또는 “write-ahead log”를 가지고 있다. 추가 전용(append-only) translog에 기록되는 것은, shard에 대한 성공을 정의하는 것이지, document가 검색 가능한 segment를 통해 실제 index의 일부인지 여부가 아니다.

During normal operation, Elasticsearch does not replicate already built pieces of indexes (i.e. segments), but the operations necessary to reapply the same change. (During recovery or shard migration, on the other hand, segments are replicated.) Therefore, all the replicas are essentially doing the same amount of work, for the same CPU cost. A replica cannot “reuse” the work the primary has already done. This is an important reason why adding replicas decreases the overall indexing throughput — you need to wait for even more nodes to acknowledge the operations. Also, you cannot expect to have “write only” nodes and “read only” nodes.

정상 작동 중에는 Elasticsearch는 이미 작성된 index 조각(segment)을 복제(replicate)하지 않지만, 동일한 변경 사항을 다시 적용하는 데 필요한 작업이 필요하다. (복구-recovery 또는 shard migration 중에는, segment가 복제된다) 따라서, 모든 replica는 본질적으로 동일한 CPU 비용으로 동일한 양의 작업을 수행한다. replica는 이미 수행한 primary의 작업을 "재사용"할 수 없다. 이는 replica를 추가하면 전체 indexing 처리량이 감소하는 중요한 이유이다. 더 많은 node가 작업을 승인할 때까지 기다려야 하기 때문이다. 또한 "쓰기 전용(write only)" node와 "읽기 전용(read only)" node가 있을 것으로 예상할 수 없다.

All in a Shard

While putting an operation in the shard’s transaction log is a great start, ultimately the effect of the operation is supposed to end up in the index structures. In the previous article, we covered how the inverted index works, and how it’s built. We said nothing about mappings, but focused on the fact that a shard is a Lucene index.

shard의 transaction log에 작업을 넣는 것이 좋은 출발이지만, 궁극적으로 작업의 결과는 index 구조에서 끝나기로 되어 있다. 이전 게시물에서 inverted index가 동작하는 방식과 만들어진 방식에 대해 살펴 보았다. mapping에 대해서는 언급하지 않았지만, shard가 Lucene index라는 사실에 중점을 두었다.

Lucene does not have a concept of a mapping, nor does a Lucene index or document have types. In Lucene, a document is typeless and has an arbitrary number of fields. These fields have certain types (string/numeric) and properties (stored/indexed/…). The Mapping is an excellent abstraction to express how to transform a source document to a Lucene document with a bunch of fields. Mappings have concepts like types, dynamic properties, multi-fields, scripted transforms and so on. Lucene, however, knows nothing of these. Lucene is happy as long as Elasticsearch produces a document in the way Lucene expects. There is nothing special with fields like _all_source, or even _type as far as Lucene is concerned.

Lucene에는 mapping이라는 개념이 없으며, Lucene index나 document에도 type이 없다. Lucene에서, document는 type이 없고 임의의 수의 field가 있다. 이 filed에는 특정 type(string/numeric) 및 property(stored/indexed/...)이 있다. mapping은 여러 field가 있는 source document를 Lucene document로 변환하는 방법을 표현하는 멋진 추상화이다. mapping에는 type, 동적 property, 다중 field, scripted transform 등과 같은 개념이 있다. 그러나 Lucene은 이것들을 전혀 모른다. Lucene은 Elasticsearch가 Lucene이 기대하는 방식으로 document를 작성하는 것으로 만족한다. Lucene에 관한 한, _all_source, 또는 _type 과 같은 field를 가진 특별한 것이 없다.

Elasticsearch transforms a source document to a Lucene document

Elasticsearch transforms a source document to a Lucene document

Many compare an Elasticsearch index to a database, and a type to a table. Personally, I think this comparison can cause more confusion than clarity. With two different tables, you can reasonably assume that the tables have nothing in common in how or where they are stored. Conversely, you can construct mappings that will cause Elasticsearch to produce fields with the same name, but with disparate types. Since everything is a bunch of terms in the same inverted index, this can cause problems. If you have a filter that is numeric for some documents and a string for others, you will eventually get errors or possibly unexpected results. The same goes for fields with the same type (e.g. string), but with different analyzers. Dynamic mapping is great for jumpstarting development, but probably not something you want to rely entirely on.

많은 이들이 Elasticsearch index를 database와, type을 table과 비교한다. 개인적으로, 이 비교가 명확함보다 혼란을 야기할 수 있다고 생각한다. 두 개의 서로 다른 table을 사용하면, table을 저장하는 방법이나 위치에 아무런 공통점이 없다고 합리적으로 추측할 수 있다. 반대로, Elasticsearch에서 name이 같지만 type이 다른 field를 생성하게 하는 mapping을 구성할 수 있다. 동일한 inverted index에서, 모든 것은 용어(term)의 집합이기 때문에, 문제가 발생할 수 있다. 어떤 document는 숫자이고 다른 것은 string인 경우, filter를 사용하면, 결과적으로 오류가 발생하거나 예상치 못한 결과가 발생할 수 있다. 동일한 type(예: string)이지만 analyzer가 다른 field의 경우에도 마찬가지이다. dynamic mapping은 개발을 시작하기에 좋지만, 전적으로 의존하고 싶은 것은 아니다.

Index Request Summary

To summarize the flow of index requests, this is what happens:

index request의 흐름을 요약하면, 다음과 같다.

  1. The node accepting the request will be the coordinator. It consults the mappings to determine which shard to send the request to.
    request를 수락하는 node는 coordinator이다. request를 보낼 shard를 결정하기 위해 mapping을 참조한다.
  2. The request is sent to the primary of that shard.
    해당 shard의 primary로 request를 보낸다.
  3. The primary writes the operation to its translog, and relays the request to the replicas.
    primary는 translog에 작업을 기록하고 replica에 request를 전달한다.
  4. When a sufficient number of replicas have acknowledged, the primary returns success.
    충분한 수의 relica가 확인되면 primary는 성공을 return한다.
  5. The coordinator returns success when all the sub-operations (in e.g. a bulk request) have succeeded.
    coordinator는 모든 하위 작업(예 : bulk request에서)이 성공하면, 성공을 return한다.

While this happens, each shard will continuously process its queue of documents, transforming the input documents to Lucene documents. These are then added to the index buffer, eventually flushed in a new segment, and even later completely committed. When this happens is up to the node hosting the shard. The flushing is not synchronized across nodes, so it is possible for searchers to briefly see separate “timelines” as refreshes propagate.

이런 일이 발생하는 동안, 각 shard는 계속해서 document queue를 처리하여, 들어오는 document를 Lucene document로 변환한다. 그런 다음, index buffer에 추가되고,  결국에는 새로운 segment로 flush되고, 나중에 완전히 commit된다. 이런 일이 발생하는 시간은 shard를 가진 node에 따라 다르다. flushing은 node 사이에서는 동기화되지 않으므로, 검색하는 사람은 refreah가 전달됨에 따라, 다른 "timelines"을 잠시 볼 수 있다.

Search Requests

We’ve seen the amount of back and forth required to turn a request with an index operation into an actual index change. The process for search requests is similar in some ways, and different in many others.

index 작업의 request를 실제 index 변경으로 전환하는 데 필요한 전후의 작업을 확인했다. 검색 request process는 여러 면에서 유사하며, 다른 여러 가지 면에서 다르다.

Like with index requests, the search requests must be routed. A search will either hit all distinct shards, if no routing is specified — or a specific shard, if routing is specified.

index request와 마찬가지로, 검색 request도 route해야 한다. routing이 지정되지 않은 경우, 모든 개별(all distinct) shard를 검색하거나 routing이 지정된 경우 특정 shard를 검색한다.

When the relevant shards have been identified, the coordinator will choose amongst the available replicas of that shard, trying to balance load.

관련 shard가 확인되면, coordinator는 해당 shard의 사용 가능한 replica 중에서 선택하여, 로드밸런싱을 시도한다.

Let’s assume we have the following search. We have a multi_match-query on the fields title and description, searching for “Holy Grail”. We want the top ten authors, and the top ten books, preferring a match in the title.

다음과 같은 검색을 한다고 가정 해 보자. 우리는 title 과 description에 대해, multi_match-query를 사용하여,  "Holy Grail(성배)"를 검색하려 한다. title에 일치하는, 상위 10명의 author(저자)와 상위 10권의 책(book)을 원한다.

POST /books/book/_search
query:
    filtered:
        filter:
            term:
                tag: python
        query:
            multi_match:
                query: Holy Grail
                fields: [title^5, description]
aggregations:
    author_id:
        terms:
            field: author_id
            size: 10
            shard_size: 100
size: 10

We have not specified the search’s type, so the default will be used – query_then_fetch. This “search type” is not to be confused with the types discussed above! Naming is difficult.

검색 type을 지정하지 않았으므로, 기본값인 query_then_fetch가 사용된다. 이 "검색 type"을 위에서 언급된 type과 혼동하지 마십시오! 이름 지정이 어렵다.

Essentially, “query then fetch” means that there will be two rounds of searching. We will compare this search type with others later.

본질적으로 "query and fetch"는 두 차례의 검색이 수행됨을 의미한다. 나중에 이 검색 type을 다른 것과 비교할 것이다.

First, the shards will each find the top 10 hits and send back their IDs. These IDs (up to \(10 * n\) of them) will then be merged by the coordinator to find the true top 10, after which it will request the actual documents for the winners. For finding the global top 10 hits, it is sufficient to ask for the top 10 from every shard. We’ll see why this is different for aggregations later.

먼저, shard는 각각 상위 10개의 hit를 찾고 자신의 ID를 돌려 보낸다. 이들 ID (최대 \(10 * n\)까지)는 coordinator에 의해 병합되어, 진정한 상위 10 개를 찾은 다음, 실제 상위 10개의 document를 request한다. 전체 상위 10개를 찾기 위해서는, 모든 shard에서 상위 10개를 요청하는 것으로 충분하다. 나중에 aggregation에서 이것과 다른 이유를 알 수 있다.

But first, let’s look closer at the first round, and see what happens at the shard level for a search.

먼저, 첫 번째 단계를 자세히 살펴보고, shard 레벨에서 검색을 위해 어떤 일이 발생하는지 살펴보자.

Query Rewriting

Before a search request can be executed on a shard, the search needs to be rewritten and adapted to Lucene query graphs. Elasticsearch is often criticized for having a deeply nested and verbose search DSL, which to some extent assumes Lucene familiarity. Personally, I find the search DSL to be awesome — for precisely the same reasons:

shard에서 검색 request를 실행하려면, Lucene query에 맞도록 다시 작성해야 한다. Elasticsearch는 종종 너무 중첩되고 상세한 search DSL을 가지고 있다는 이야기를 듣는데, 이것은 Lucene과의 친숙함을 보여주는 것이다. 개인적으로, 정확히 같은 이유로, search DSL이 굉장하다고 생각한다.

First, the nested nature makes it easier to work with programatically. It is certainly verbose for simple things, but real search needs with heavily customized scoring and matching are not simple.

첫째, 중첩된 특성으로 인해 프로그래밍 방식으로 작업하기가 더 쉽다. 그것은 분명 단순한 작업에서는 장황하지만, 복잡한 사용자 정의 score 계산과 일치(match)를 통한 진짜 검색에서 필요한 것은 간단하지 않다.

Secondly, the queries and the filters in the DSL are quite close to their Lucene counterparts. This is important to understand what is really going on.

두번째로, DSL의 query와 filter는 Lucene과 매우 비슷하다. 이것은 실제로 무슨 일이 일어나고 있는지 이해하는데 중요하다.

There are a few exceptions, however. As mentioned above, the mapping concept is foreign to Lucene. Yet, some queries utilize the mappings. For example, the match family of queries have no Lucene counterpart. They will process the query text according to the fields’ mappings, and compose a Lucene query. Our example will be rewritten to something like the following:

그러나 몇 가지 예외가 있다. 위에서 언급했듯이, mapping 개념은 Lucene에게는 생소하다. 그러나 일부 query는 mapping을 사용한다. 예를 들어, match 계열 query는 Lucene에 없다. field mapping에 따라, query text를 처리하고 Lucene query를 작성한다. 예제는 다음과 같이 다시 작성된다.

Note that the query text “Holy Grail” has been tokenized and lowercased, according to the analyzers configured for the respective fields. A common source of frustration when not getting the results you want is to have incompatible index- and query time text processing. For example, a terms query would not have transformed Holy Grail into holy, grail, and therefore would not match.

각 field에 설정된 analyzer에 따라, query text "Holy Grail"가 token화되고 소문자로 변경된다는 점에 주목하자. 원하는 결과를 얻지 못할 경우의 일반적인 원인은 호환되지 않는 index 및 query 시의 text 처리입니다. 예를 들어, terms query는 Holy Grail 를 holy, grail로 변형시키지 않으므로, 일치하지 않을 것이다.

Searching a Shard

At this point, we have a Lucene query operator ready to be executed on every shard. We’re in search phase one — i.e. “query”, to be followed by “fetch” — so we need the following:

이 시점에서, 모든 shard에서 Lucene query 연산자를 실행할 수 있다. 검색 절("query")에 이어서 "fetch"를 수행하므로, 다음과 같은 항목이 필요하다.

  • A list of the top ten document IDs. (Not the entire document) These will be merged by the coordinator, which will do a second “fetch” phase to fetch the actual documents.
    상위 10개의 document ID 목록(전체 document가 아님). 이들은 실제 document를 가져 오기 위해, 두 번째 "fetch" 절을 수행하는 coordinator에 의해 병합된다.
  • An iterator of all hits (though we don’t need scores for them all), for aggregation purposes.
    aggregation을 위해, 모든 hit에 대한 반복자(iterator) - 그들 모두에 대한 score를 필요로 하지는 않지만
  • A way to quickly find a book’s author_id given a document ID: i.e. a field cache or document values.
    주어진 document ID(예: field cache 또는 document values.)가 있는, 책의 author_id 를 빠르게 찾을 수 있는 방법.
  • The top 100 authors. (Note the shard_size being 100)
    상위 100명의 author (shard_size 가 100인 것에 유의하자)

The section on inverted indexes and index terms in the bottom up article describes how the search is executed per segment, so we will skip repeating that here. However, it is worth repeating that a search happens across multiple independent segments and merged - much like how the sharding works. There are several caches that can boost the performance of the search, all scoped by segment:

지난 게시물의 inverted indexes and index terms section에서, segment별로 검색이 실행되는 방식을 설명했으므로, 여기에서 반복하지는 않겠다. 그러나 여러 독립적인 segment에서 검색을 수행하고 병합하는 방법은 반복할 만하다. shard가 동작하는 방법도 마찬가지이다. 검색 성능을 향상시킬 수 있는 여러 가지 cache가 있으며, segment별로 범위가 지정된다.

  • If the tag:python filter is cached, it can immediately be reused.
    tag:python filter가 cache되면, 즉시 재사용될 수 있다.
  • The author_id field must be in the field cache, if document values are not enabled. If not, all author_ids for all documents must be loaded into memory.
    document value가 비활성화되어 있는 경우, author_id field는 field cache에 있어야 한다. 그렇지 않은 경우, 모든 document의 모든 author_id memory에 load되어야 한다.
  • If document values are used for author_id, it’s possible that the disk pages holding the needed values is in the page cache. If so, great!
    document value가  author_id에 사용되는 경우, 필요한 값을 가진 disk page가 page cache에 있을 수 있다. 그렇다면 좋다.!

Queries are not cached, however. So for these, and any uncached filters and fields, we will need to hit the inverted index. For anything that needs to use the underlying index, it would be great if the relevant pages are present in the operating system’s page cache. (If you assign all the memory to the Elasticsearch process, there is nothing left for the operating system.)

그러나 query는 cache되지 않는다. 따라서, 이들과 cache되지 않은 filter와 field의 경우, inverted index를 사용해야 한다. 기본 index를 사용해야 하는 항목의 경우, 관련 page가 OS page cache에 있으면 좋을 것이다. 모든 memory를 Elasticsearch process에 할당하면, OS에는 남은 것이 없다.

Ultimately, with filters and aggregations, you are essentially manipulating bitmaps to summarize values you already have cached. This is why Elasticsearch can be so mind-boggingly fast.

궁극적으로, filter 및 aggregation을 사용하면, 이미 cache된 값을 요약하기 위해, 기본적으로 bitmap을 조작하게 된다. 이런 이유로 Elasticsearch가 그렇게 빠를 수 있다.

… Then Fetch

After every shard has provided its contribution to the results, the coordinator will merge them. Specifically, there are two things it needs to find out:

모든 shard의 결과가 return되면 coordinator가 그들을 병합한다. 특히 알아야 할 두 가지가 있다.

  • The true top 10 hits. The shards will provide the IDs of up to 10 documents, and their scores.
    진정한 상위 10개의 결과. shard는 최대 10 개의 document ID와 score를 제공한다.
  • An approximation of the top 10 authors. The shards have provided the counts for up to 100 authors each. If we did not do this, and only requested the top 10 per shard, what would happen if one of the true top 10 authors were actually the 11th in one of the shards? It would not have been submitted as a candidate, and caused that particular outer to have fallen out. This is still possible, if one of the true top 10 authors is actually the 101st on one of the shards, but it should be less likely. To have complete accuracy, Elasticsearch needs to gather the counts for all authors for every shard. This can be prohibitively expensive to do, so trading accuracy for speed is common in this case.
    상위 10명의 author의 근사치. shard는 각각 최대 100명의 author 수를 제공한다. 이렇게 하지 않고, shard 당 상위 10명만을 요청한 경우, 진정한 상위 10명의 author 중 한 명이 실제로 shard 중 하나에서 11번째라면 어떻게 될까? 그것은 후보로 제출되지 않을 것이고, 빠져 나간다. 이것은 여전히 가능해서, 진정한 상위 10명의 author 중 한 명이 실제로 shard 중 하나에서 101번째일 수도 있지만, 가능성이 적다. 완벽한 정확성을 얻으려면, Elasticsearch가 모든 shard에 대해 모든 author의 수를 수집해야 한다. 이것은 엄청나게 비용이 소모되므로, 이 경우 속도와 정확성을 거래하는 것이 일반적이다.

The merge process will determine the true top 10 hits, then reach out to the shards that host the documents and ask for the entire document. Whether this extra step is helpful or an optimization that ends up adding to the overall latency depends on your use case. If you have a big number of shards and rather big documents, it’s probably worthwhile to do it in two rounds. If you have tiny documents and few shards, you can consider the query_and_fetch search type. As always, test and verify.

병합(merge) process는 진정한 상위 10개를 결정한 다음, document를 가지고 있는 shard로 document 전체를 요청한다. 이 추가 단계가 도움이 되는지 또는 전반적인 대기 시간에 추가되는 최적화는 사용 사례에 달려 있다. shard 수가 많고 document가 크면, 두 번에 걸쳐 할 만 하다. 작은 document와 소수의 shard가 있다면, query_and_fetch search type을 고려할 수 있다. 언제나 그렇듯이, 테스트하고 확인하자.

Summary

The goals of this article was to start out with an index and a search request, as sent from a client, and reach the bits covered in the first part of this series. We’ve looked at shard routing, with the help of cluster state, mappings and possibly customized routing. With shards routed, we’ve seen how the shards’ primaries coordinate changes to its replicas, and how the transaction log balances durability and timely responses.

이 게시물의 목표는 client에서 보낸 index 및 search request로 시작하여, 이 시리즈의 첫 번째 부분에서 다루는 부분에 도달하는 것이다. cluster state, mapping 및 가능한 사용자 정의 routing을 사용하여, shard routing을 살펴 보았다. shard가 route되면 shard의 primary가 replica의 변경 사항을 조정하는 방법과 transaction log가 내구성과 적절한 response의 균형을 조정하는 방법을 살펴 보았다.

Similarily, we’ve traced the flow of search requests — through routing, balancing, scattering, query rewriting, response gathering, merging, subsequent fetching, and more.

마찬가지로, routing, balancing, scattering, query 재 작성, response 수집, 병합, 후속 fetch 등을 통해, 검색 request의 흐름을 추적했다.

Also, we’ve looked at mappings and types, how they do not exist in “Lucene land”, and what kinds of rewriting is necessary. Both when indexing, and when searching.

또한, mapping과 type, "Lucene"에 존재하지 않는 것과 어떤 것이 재 작성이 필요한지 살펴 보았다. indexing과 및 검색할 때 모두.

Hopefully, you have learned a bit more about the distributed nature of Elasticsearch, and the boundaries between Elasticsearch and Lucene!

여러분이 Elasticsearch의 분산의 특징과 Elasticsearch와 Lucene 사이의 경계에 대해 조금 더 배웠기를 바란다.!

Learning More

원문 : Elasticsearch from the Top Down