Reference/How To ...

5. Tune for search speed

drscg 2018. 10. 5. 18:03

Give memory to the filesystem cache

Elasticsearch heavily relies on the filesystem cache in order to make search fast. In general, you should make sure that at least half the available memory goes to the filesystem cache so that Elasticsearch can keep hot regions of the index in physical memory.

Elasticsearch는 search 속도를 빠르게 하기 위해, filesystem cache에 많이 의존한다. 일반적으로, Elasticsearch가 index 중 많이 사용하는 영역을 물리적 memory에 유지할 수 있도록, 이용할 수 있는 memory의 최소한 절반을 filesystem cache에 할당해야 한다.

Use faster hardware

If your search is I/O bound, you should investigate giving more memory to the filesystem cache (see above) or buying faster drives. In particular SSD drives are known to perform better than spinning disks. Always use local storage, remote filesystems such as NFS or SMB should be avoided. Also beware of virtualized storage such as Amazon’s Elastic Block Storage. Virtualized storage works very well with Elasticsearch, and it is appealing since it is so fast and simple to set up, but it is also unfortunately inherently slower on an ongoing basis when compared to dedicated local storage. If you put an index on EBS, be sure to use provisioned IOPS otherwise operations could be quickly throttled.

search에 I/O 문제가 있다면 filesysterm cache에 더 많은 memory를 할당(위 참조)하거나 더 빠른 driver를 구매하는 것을 고려해야 한다. 특히 SSD driver는 일반 회전 disk보다 성능이 뛰어나다고 알려져 있다. 항상 local storage를 사용하자. NFS나 SMB 같은 remote filesystem은 피해야 한다. 또한 Amazon의 Elastic Block Storage 같은 가상 storage도 주의하자. Elasticsearch는 가상 storage에서 매우 잘 동작하며, 매우 빠르고 설치가 간단해 매력적이만, 전용 local storage와 비교해보면, 불행하게도 본질적으로 더 느리다. index를 EBS 에 둔다면, provisioned IOPS를 사용햐여 한다. 그렇지 않으면 연산이 느려질 수 있다.

If your search is CPU-bound, you should investigate buying faster CPUs.

search에 CPU 문제가 있다면, 더 빠른 CPU의 구매를 고려해야 한다.

Document modeling

Documents should be modeled so that search-time operations are as cheap as possible.

search 연산이 보다 저렴하도록, document는 modeling되어야 한다.

In particular, joins should be avoided. nested can make queries several times slower and parent-child relations can make queries hundreds of times slower. So if the same questions can be answered without joins by denormalizing documents, significant speedups can be expected.

특히, join은 피해야 한다. nested 는 query를 수배 더 느리게 하고, parent-child 관계는 수백배 더 느리게 한다. 따라서 document를 비정규화하여 join 없이 동일한 질문에 답할 수 있다면, 상당한 속도 향상을 기대할 수 있다.

Search as few fields as possible

The more fields a query_string or multi_match query targets, the slower it is. A common technique to improve search speed over multiple fields is to copy their values into a single field at index time, and then use this field at search time. This can be automated with the copy-to directive of mappings without having to change the source of documents. Here is an example of an index containing movies that optimizes queries that search over both the name and the plot of the movie by indexing both values into the name_and_plot field.

query_string 또는 multi_match query는 대상으로 하는 field가 많을수록 더 느려진다. 다수의 field에 대한 search 속도를 향상시키는 일반적인 기술은 index시에 그 값을 단일 field에 복사하고, 이 field를 search시에 사용하는 것이다. 이는 document의 source를 변경하지 않고 mapping의 copy-to 지시어로 자동화될 수 있다. 아래에 movie를 포함하는 index의 예가 있는데, 이는 name과 plot의 두 값을 name_and_plot field에 index하여 movie의 name과 plot 모두를 search하는 query를 최적화한다.

PUT movies
{
  "mappings": {
    "_doc": {
      "properties": {
        "name_and_plot": {
          "type": "text"
        },
        "name": {
          "type": "text",
          "copy_to": "name_and_plot"
        },
        "plot": {
          "type": "text",
          "copy_to": "name_and_plot"
        }
      }
    }
  }
}

Pre-index data

You should leverage patterns in your queries to optimize the way data is indexed. For instance, if all your documents have a price field and most queries run range aggregations on a fixed list of ranges, you could make this aggregation faster by pre-indexing the ranges into the index and using a terms aggregations.

query의 pattern을 이용하여, data가 index되는 방법을 최적화해야 한다. 모든 document가 price field를 가지고 있고, 대부분의 query가 고정된 범위에서 range aggregation을 사용한다면, index에 range를 미리 index하고, term aggregation을 사용하여, 이 aggregation을 더 빠르게 할 수 있다.

For instance, if documents look like:

예를 들어, document가 아래와 같다면,

PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13
}

and search requests look like:

search request는 다음과 같은 것이다.

GET index/_search
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 10 },
          { "from": 10, "to": 100 },
          { "from": 100 }
        ]
      }
    }
  }
}

Then documents could be enriched by a price_range field at index time, which should be mapped as a keyword:

그렇다면, index시에 price_range field로 document를 보완하면, 이 field는 keyword 로 mapping된다.

PUT index
{
  "mappings": {
    "_doc": {
      "properties": {
        "price_range": {
          "type": "keyword"
        }
      }
    }
  }
}

PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13,
  "price_range": "10-100"
}

And then search requests could aggregate this new field rather than running a range aggregation on the price field.

그런 다음, search request는 price field의 range aggregation으로 실행하는 대신 이 새로운 field로 aggregation할 수 있다.

GET index/_search
{
  "aggs": {
    "price_ranges": {
      "terms": {
        "field": "price_range"
      }
    }
  }
}

Consider mapping identifiers as keyword

The fact that some data is numeric does not mean it should always be mapped as a numeric field. The way that Elasticsearch indexes numbers optimizes for range queries while keyword fields are better at term queries. Typically, fields storing identifiers such as an ISBN or any number identifying a record from another database are rarely used in range queries or aggregations. This is why they might benefit from being mapped as keyword rather than as integer or long.

일부 data가 숫자라고 해서 항상 numeric field 로 mapping되어야 하는 것은 아니다. Elasticsearch가 숫자를 index하는 방법은 range query에 최적화되어 있지만, keyword field는 term query에 사용하는 것이 더 낫다. 일반적으로 ISBN 같은 식별자를 저장하거나 다른 database의 record를 구분하는 field는 range query나 aggregation에 거의 사용되지 않는다. 그렇기 때문에, integer 나 long 보다는 keyword 로 mapping되는 것이 더 낫다.

Avoid scripts

In general, scripts should be avoided. If they are absolutely needed, you should prefer the painlessand expressions engines.

일반적으로 script는 피해야 한다. 반드시 써야 한다면, painless 와 expressions engine을 사용하는 것이 더 낫다.

Search rounded dates

Queries on date fields that use now are typically not cacheable since the range that is being matched changes all the time. However switching to a rounded date is often acceptable in terms of user experience, and has the benefit of making better use of the query cache.

now 를 사용하는 date field에 대한 query는 일치하는 범위가 항상 변경되기 때문에, 일반적으로 cache될 수 없다. 그러나, 반올림되는 date로 변경하는 것은  UX측면에서 흔히 받아들여지며, query cache를 보다 효율적으로 활용하는 이점이 있다. 

For instance the below query:

아래 query를 예로 들자면:

PUT index/_doc/1
{
  "my_date": "2016-05-11T16:30:55.328Z"
}

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h",
            "lte": "now"
          }
        }
      }
    }
  }
}

could be replaced with the following query:

다음과 같은 query로 변경될 수 있다.

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h/m",
            "lte": "now/m"
          }
        }
      }
    }
  }
}

In that case we rounded to the minute, so if the current time is 16:31:29, the range query will match everything whose value of the my_date field is between 15:31:00 and 16:31:59. And if several users run a query that contains this range in the same minute, the query cache could help speed things up a bit. The longer the interval that is used for rounding, the more the query cache can help, but beware that too aggressive rounding might also hurt user experience.

이 경우, 분 단위로 반올림했으므로, 현재 시간은 16:31:29 이라면, range query는 my_date field가 15:31:00 와 16:31:59 인 모든 것에 일치할 것이다. 그리고 다수의 사용자가 이 범위를 포함하는 query를 동일한 시간(분)에 실행한다면, query cache가 속도를 높일 수 있다. 반올림에 사용되는 간격이 더 길수록 query cache가 더 많이 도움이 될것이다. 그러나 너무 공격적인 반올림은 UX에 도움이 되지 않는다.

Note

It might be tempting to split ranges into a large cacheable part and smaller not cacheable parts in order to be able to leverage the query cache, as shown below:

다음과 같이, query cache를 활용할 수 있도록, cache 활용이 가능한 큰 부분과 불가능한 더 작은 부분을 나누는 것이 좋다. 

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {
              "range": {
                "my_date": {
                  "gte": "now-1h",
                  "lte": "now-1h/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gt": "now-1h/m",
                  "lt": "now/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gte": "now/m",
                  "lte": "now"
                }
              }
            }
          ]
        }
      }
    }
  }
}

However such practice might make the query run slower in some cases since the overhead introduced by the bool query may defeat the savings from better leveraging the query cache.

그러나, bool query에 의해 생성된 overhead가 query cache를 더 잘 활용함으로서 발생한 이점을 상쇄할 수 있기 때문에, 그런 것들은 어떤 경우에는 query가 더 느리게 실행되는 경우가 있다.

Force-merge read-only indices

Indices that are read-only would benefit from being merged down to a single segment. This is typically the case with time-based indices: only the index for the current time frame is getting new documents while older indices are read-only.

읽기 전용인 index는 단일 segment로 merge 되는 것이 좋다. 이는 일반적으로 시간 기반 index의 경우이다. 현재 시간과 관련된 index만 새로운 document를 얻는 반면, 이전 index는 읽기 전용이다.

Important

Don’t force-merge indices that are still being written to — leave merging to the background merge process.

여전히 쓰고 있는 index를 강제로 merge하지 말자. background merge process가 병합도록 남겨두자.

Warm up global ordinals

Global ordinals are a data-structure that is used in order to run terms aggregations on keywordfields. They are loaded lazily in memory because Elasticsearch does not know which fields will be used in terms aggregations and which fields won’t. You can tell Elasticsearch to load global ordinals eagerly at refresh-time by configuring mappings as described below:

global ordinals는 keyword field에 대한 terms aggregation을 실행하기 위해 사용되는 data 구조이다. Elasticsearch는 terms aggregation에 사용될 field와 사용되지 않을 field를 알지 못하기 때문에, global ordinals은 나중에 memory에 load된다. 아래와 같이 mapping을 설정하여, Elasticsearch가 refresh시에 global ordinals을 미리 load할 수 있다.

PUT index
{
  "mappings": {
    "_doc": {
      "properties": {
        "foo": {
          "type": "keyword",
          "eager_global_ordinals": true
        }
      }
    }
  }
}

Warm up the filesystem cache

If the machine running Elasticsearch is restarted, the filesystem cache will be empty, so it will take some time before the operating system loads hot regions of the index into memory so that search operations are fast. You can explicitly tell the operating system which files should be loaded into memory eagerly depending on the file extension using the index.store.preload setting.

Elasticsearch를 실행하고 있는 machine을 다시 시작하면, filesystem cache는 비어 있으므로, 운영체제가 search 연산을 빠르게 할 수 있도록 많이 사용하는 index 영역을 memory에 load하는데 약간의 시간이 소요된다. index.store.preload 설정을 사용하여, 운영체제가 file 확장자에 따라 어떤 file을 memory에 미리 load해야 하는지 명시적으로 할 수 있다.

Warning

Loading data into the filesystem cache eagerly on too many indices or too many files will make search slower if the filesystem cache is not large enough to hold all the data. Use with caution.

너무 많은 index나 file에서 data를 filesystem cache에 미리 load하는 것은 filesystem cache가 data 모두를 가질만큼 충분히 크지 않다면 search는 더 느려질 것이다. 주의해서 사용하자.

Use index sorting to speed up conjunctions

Index sorting can be useful in order to make conjunctions faster at the cost of slightly slower indexing. Read more about it in the index sorting documentation.

Index sorting 은 약간 더 느린 index 로 더 빠른 결합을 만드는데 유용하다. 자세한 내용은 index sorting documentation 을 참고하자.

Use preference to optimize cache utilization

There are multiple caches that can help with search performance, such as the filesystem cache, the request cache or the query cache. Yet all these caches are maintained at the node level, meaning that if you run the same request twice in a row, have 1 replica or more and use round-robin, the default routing algorithm, then those two requests will go to different shard copies, preventing node-level caches from helping.

filesystem cacherequest cachequery cache 처럼 search 성능에 도움이 되는 여러가지 cache가 있다. 그러나 이 모든 cache는 node level에서 관리된다. 즉, 하나 이상의 replica 를 가지고 기본 routing 알고리즘인 round-robin 을 사용하는 index에, 동일한 request를 잇달아 2번 실행하면, 이들 2개의 request는 서로 다른 shard 복사본으로 가, node level cache는 도움이 되지 않는다.

Since it is common for users of a search application to run similar requests one after another, for instance in order to analyze a narrower subset of the index, using a preference value that identifies the current user or session could help optimize usage of the caches.

search 프로그램의 사용자는 유사한 request를 차례로 실행하는 것이 일반적이므로, 예를 들어, index의 더 작은 부분을 분석하도록, 현재 사용자나 session을 구분하는 preference 값을 사용하는 것이 cache 활용을 최적하는데 도움이 된다.

Replicas might help with throughput, but not always

In addition to improving resiliency, replicas can help improve throughput. For instance if you have a single-shard index and three nodes, you will need to set the number of replicas to 2 in order to have 3 copies of your shard in total so that all nodes are utilized.

복원력 향상 외에도, replica는 처리량도 향상시킬 수 있다. 예를 들어, 단일 shard index와 3개의 nodex가 있다면, 모든 node를 활용할 수 있도록, 전체적으로 3개의 shard 복사본을 가지기 위하여, replica의 수를 2로 설정해야 한다.

Now imagine that you have a 2-shards index and two nodes. In one case, the number of replicas is 0, meaning that each node holds a single shard. In the second case the number of replicas is 1, meaning that each node has two shards. Which setup is going to perform best in terms of search performance? Usually, the setup that has fewer shards per node in total will perform better. The reason for that is that it gives a greater share of the available filesystem cache to each shard, and the filesystem cache is probably Elasticsearch’s number 1 performance factor. At the same time, beware that a setup that does not have replicas is subject to failure in case of a single node failure, so there is a trade-off between throughput and availability.

이제, 2개의 shard와 2개의 node를 생각해 보자. 먼저 replica의 수가 0 이라면 각 node는 단일 shard를 가진다. 두번째로 replica의 수가 1이면 각 nodex는 2개의 shard를 가진다. search 성능 측면에서 어떤 설정이 가장 좋을까? 일반적으로 node당 shard가 더 적은 설정이 더 좋다. 그 이유는 각 shard에 이용가능한 filesystem cache의 더 많은 부분을 제공하기 때문이다. 그리고 filesystem cache는 아마도 Elasticsearch 최고의 성능 요소일 것이다. 동시에 replica를 가지지 않는 설정은 node 장애가 발생할 경우  장애가 발생할 수 있으므로 처리량과 가용성간에 균형이 맞아야 한다.

So what is the right number of replicas? If you have a cluster that has num_nodes nodes, num_primaries primary shards in total and if you want to be able to cope with max_failures node failures at once at most, then the right number of replicas for you is max(max_failures, ceil(num_nodes / num_primaries) - 1).

그렇다면, replica는 얼마로 해야 적절할까? num_nodes 를 가진 cluster가 있다면, 전체적으로 num_primaries 개의 primary shard가 있다. 그리고 한번에 최대 max_failures node 장애를 처리하려면, replica의 적절한 수는 max(max_failures, ceil(num_nodes / num_primaries) - 1) 이다.

Turn on adaptive replica selection

When multiple copies of data are present, elasticsearch can use a set of criteria called adaptive replica selection to select the best copy of the data based on response time, service time, and queue size of the node containing each copy of the shard. This can improve query throughput and reduce latency for search-heavy applications.

data의 복사본이 여러 개 존재할 경우, 각 shard의 복사본을 가지고 있는 node의 response time, service time, queue size에 근거하여 최상의 data 복사보을 선택하기 위하여, Elasticsearch는 adaptive replica selection 라 불리는 일련의 기준을 사용한다. 이렇게 하면 query 처리량이 향상되고, search가 많이 필요한 application의 대기시간이 줄어든다.

'Reference > How To ...' 카테고리의 다른 글

6. Tune for disk usage  (0) 2018.10.05
4. Tune for indexing speed  (0) 2018.10.05
3. Getting consistent scoring  (0) 2018.10.05
2. Mixing exact search with stemming  (0) 2018.10.05
1. General recommendations  (0) 2018.10.05