Blog

2018.10.18 - 번역 - Efficient Duplicate Prevention for Event-Based Data in Elasticsearch ...

drscg 2019. 1. 7. 14:35

The Elastic Stack is used for a lot of different use cases. One of the most common is to store and analyze different types of event or time-series based data, e.g. security events, logs, and metrics. These events often consist of data linked to a specific timestamp representing when the event occurred or was collected and there is often no natural key available to uniquely identify the event.

Elastic Stack은 다양한 경우에 사용된다. 가장 일반적인 경우 중 하나는 다양한 유형의 event나 시간 기반의 data(보안 event, log, metric 등)을 저장하고 분석하는 것이다. 이들 event는 흔히 event가 발생하거나 수집된 시간을 나타내는 특정 timestamp에 연결된 data로 구성된다. 그리고, event를 고유하게 식별할 수 있는 key는 없는 경우가 많다.

For some use cases, and maybe even types of data within a use case, it is important that the data in Elasticsearch is not duplicated: Duplicate documents may lead to incorrect analysis and search errors. We started looking at this last year in the introduction to duplicate handling using Logstash blog post, and in this one we will dig a bit deeper and address some common questions.

일부 사용 사례와 그 안의 event data 유형에서, Elasticsearch내의 data가 중복되지 않는 것이 중요하다. docoument가 중복되면, 올바르지 않은 분석과 search 오류가 발생한다. 작년에 introduction to duplicate handling using Logstash 게시물에서 이를 살펴보았는데, 여기에서 좀 더 깊이 살펴보고 몇 가지 공통적인 질문에 답할 것이다.

Indexing into Elasticsearch

When you index data into Elasticsearch, you need to receive the response to be sure that the data has been successfully indexed. If an error, e.g. connection error or node crash, prevents you from receiving it, you can not be sure whether any of the data has been indexed or not. When clients encounter this type of scenario, the standard way to ensure delivery is to retry, which may lead to the same document being indexed more than once.

data를 Elasticsearch에 index할 때, data가 성공적으로 index되었는지를 확인하려면 response를 받아야 한다. 오류(연결 오류, node 장애 등)로 인해 response를 받지 못하면, data가 index되었는지 여부를 알 수 없다. client가 이런 종류의 문제를 접하는 경우, 전송을 보장하는 표준적인 방법은 다시 시도하는 것인데, 이로 인해 동일한 document가 두 번 이상 index될 수 있다.

As described in the blog post on duplicate handling, it is possible to get around this by defining a unique ID for each document in the client rather than having Elasticsearch automatically assign one at indexing time. When a duplicate document is written to the same index, this will result in an update instead of the document being written a second time, which prevents duplicates.

중복을 다루는 게시물에서 설명했듯이, index시에 Elasticsearch에서 자동으로 고유한 ID를 할당하는 대신, client에서 각 document에 대해 고유한 ID를 정의하여 이 문제를 해결할 수 있다. 중복 document가 동일한 index에 쓰여지면, document가 2번째로 작성되는 대신 update가 발생하여, 중복이 방지된다.

UUIDs vs hash-based document ids

When deciding on what type of identifier to use, there are two main types to choose from.

어떤 형태의 ID를 사용할 것인가를 결정해야 할 경우, 2가지 방법이 있다.

Universally Unique Identifiers (UUIDs) are identifiers based on 128-bit numbers that can be generated across distributed systems, while for practical purposes being unique. This type of identifier generally has no dependence on the content of the event it is associated with.

Universally Unique Identifiers (UUIDs)는 분산 시스템에서 생성될 수 있는 128-bit 숫자 기반의 구분자로서, 실제로 유일하다. 이런 유형의 ID는 일반적으로 관련된 event의 내용과는 무관하다.

In order to use UUIDs to avoid duplicates, it is essential that the UUID is generated and assigned to the event before the event passes any boundary that does guarantee that the event is being delivered exactly once. This in practice often means that the UUID must be assigned at the point of origin. If the system where the event originates cannot generate a UUID, a different type of identifier may need to be used.

UUID를 사용하여 중복을 피하기 위해서는, event가 정확히 한 번 전송된다는 것을 보장하는 어떤 경계에 event가 전달되기 전에 UUID가 생성되고 event에 할당되어야 한다. 실제 상황에서 이것은 출처에서 UUID가 할당돠어야 함을 의미한다. event가 발생한 시스템에서 UUID를 생성할 수 없는 경우, 다른 형태의 ID를 사용해야 할 수도 있다.

The other main type of identifier are ones where a hash function is used to generate a numeric hash based on the content of the event. The hash function will always generate the same value for a specific piece of content, but the generated value is not guaranteed to be unique. The probability of a hash collision, which is when two different events result in the same hash value, depends on the number of events in the index as well as the type of hash function used and the length of the value it produces. A hash of at least 128 bits in length, e.g. MD5 or SHA1, generally provides a good compromise between length and low collision probability for a lot of scenarios. For even greater uniqueness guarantees, an even longer hash like SHA256 can be used.

다른 주요 유형의 ID는 hash function 을 사용하여 event의 내용을 기반으로 한 numeric hash를 생성하는 것이다. hash function은 특정 내용에 대해서는 항상 동일한 값을 생성하지만 생성된 값은 고유함을 보장하지 않는다. 2개의 다른 event가 동일한 hash 값을 가지는 hash 충돌 확률은 index의 event 수, 사용된 hash function의 유형, 생성된 값의 길이에 따라 다르다. MD5, SHA1 같은 최소 128 bit의 hash는 일반적으로 많은 경우에서 길이와 낮은 충돌 확률 사이의 좋은 절충안이다. 더 큰 유일함을 보장하려면, SHA256 처럼 훨씬 더 긴 hash를 사용할 수 있다.

As a hash-based identifier depends on the content of the event, it is possible to assign this at a later processing stage as the same value will be calculated wherever it is generated. This makes it possible to assign this type of IDs at any point before the data is indexed into Elasticsearch, which allows for flexibility when designing an ingest pipeline.

hash 기반 ID는 event의 내용에 따라 다르므로, 나중에 처리 단계에서 이를 할당하는 것이 가능하다. 어디에서 생성되더라도 동일한 값을 계산하기 때문이다. 이렇게 하면, data를 Elasticsearch에 index하기 전에, 어디에서라도, 이런 유형의 ID를 할당할 수 있어, ingest pipeline 설계시 유연성을 가질 수 있다.

Logstash has support for calculating UUIDs and a range of popular and common hash functions through the fingerprint filter plugin.

logstash는 fingerprint filter plugin 을 통해 UUID와 널리 사용되는 일반적인 hash function의 범위를 계산할 수 있다.

Choosing an efficient document id

When Elasticsearch is allowed to assign the document identifier at indexing time, it can perform optimizations as it knows the generated identifier can not already exist in the index. This improves indexing performance. For identifiers generated externally and passed in with the document, Elasticsearch must treat this as a potential update and check whether the document identifier already exists in existing index segments, which requires additional work and therefore is slower.

Elasticsearch가 index시에 document ID를 할당할 수 있는 경우, 생성된 ID가 index에 존재할 수 없다는 것을 이미 알기 때문에, 최적화를 수행할 수 있다. 이렇게 하면 index 성능이 향상된다. ID를 외부에서 생성하여 document와 함께 전달하는 경우, Elasticsearch는 이것을 잠재적 update로 간주하고, document ID가 기존의 index segment에 이미 존재하는지를 확인해야 하는데, 이는 추가 작업이 필요하고, 따라서 더 느려진다.

Not all external document identifiers are created equal. Identifiers that gradually increase over time based on sorting order generally result in better indexing performance than completely random identifiers. The reason for this is that it is possible for Elasticsearch to quickly determine whether an identifier exists in older index segments based solely on the minimum and maximum identifier value in that segment rather than having to search through it. This is described in this blog post, which is still relevant even though it is getting a bit old.

모든 외부 document ID가 동일하게 생성되는 것은 아니다. 정렬 순서를 기준으로 시간에 따라 점차 증가하는 ID는 완전히 무작위인 ID보다 더 나은 index 성능을 가진다. 그 이유는 Elasticsearch가 ID를 검색하는 대신 최소 최대 ID 값만을 기반으로, 해당 ID가 기존 index segment에 존재하는지 여부를 더 빠르게 판단할 수 있기 때문이다. 이것은 약간 오래되었지만 여전히 관련있는 이 게시물에 설명되어 있다.

Hash-based identifiers and many types of UUIDs are generally random in nature. When dealing with a flow of events where each has a defined timestamp, we can use this timestamp as a prefix to the identifier to make them sortable and thereby increase indexing performance.

hash기반의 ID와 많은 유형의 UUID는 일반적으로 무작위이다. 각 event가 정의된 timestamp를 가진 event flow를 처리하는 경우, 이 timestamp를 ID의 prefix로 사용하여, 정렬 가능하게 하여, index 성능을 증가시킬 수 있다.

Creating an identifier prefixed by a timestamp also has the benefit of reducing the hash collision probability, as the hash value only has to be unique per timestamp. This makes it possible to use shorter hash values even in high ingest volume scenarios.

또한, timestamp를 prefix로 사용한 ID를 생성하면, hash 충돌 확률을 줄일 수 있다. hash 값은 timestamp마다 고유해 지기 때문이다.

We can create these types of identifiers in Logstash by using the fingerprint filter plugin to generate a UUID or hash and a Ruby filter to create a hex-encoded string representation of the timestamp. If we assume that we have a message field that we can hash and that the timestamp in the event has already been parsed into the @timestamp field, we can create the components of the identifier and store it in metadata like this:

fingerprint filter plugin을 사용하여 UUID나 hash를 생성하고, ruby filter를 사용하여 timestamp를 16진수로 encode된 문자열을 생성함으로써, logstash에서 이런 유형의 ID를 생성할 수 있다. hash할 수 있는 message field를 가지고 있고 event의 timestamp가 이미 @timestamp field로 parse되었다도 가정하면, ID 요소를 생성하여, 다음과 같이 metadata에 저장할 수 있다.

fingerprint {
  source => "message"
  target => "[@metadata][fingerprint]"
  method => "MD5"
  key => "test"
}
ruby {
  code => "event.set('@metadata[tsprefix]', event.get('@timestamp').to_i.to_s(16))"
}

These two fields can then be used to generate a document id in the Elasticsearch output plugin:

그런 다음, Elasticsearch의 output plugin에서 이들 2개의 field를 사용하여 document id를 생성할 수 있다.

elasticsearch {
  document_id => "%{[@metadata][tsprefix]}%{[@metadata][fingerprint]}"
}

This will result in a document id that is hex encoded and 40 characters in length, e.g. 4dad050215ca59aa1e3a26a222a9bbcaced23039. A full configuration example can be found in this gist.

이렇게 하면, 16진수로 encode된 길이가 40인 document id(예: 4dad050215ca59aa1e3a26a222a9bbcaced23039)를 얻을 수 있다. 전체 설정 예제는 this gist 에서 볼 수 있다.

Indexing performance implications

The impact of using different types of identifiers will depend a lot on your data, hardware, and use-case. While we can give some general guidelines, it is important to run benchmarks to determine exactly how this affects your use-case.

서로 다른 유형의 ID를 사용하면, data, HW 그리고 사용 사례에 따라 많은 영향을 받는다. 몇 가지 일반적인 가이드라인을 제시할 수 있지만, 테스트를 통해 이것이 어떤 영향을 끼치는지 정확히 파악하는 것이 중요하다.

For optimal indexing throughput, using identifiers autogenerated by Elasticsearch is always going to be the most efficient option. As update checks are not required, indexing performance does not change much as indices and shards grow in size. It it therefore recommended to use this whenever possible.

index 처리량을 최적화하려면, Elasticsearch가 자동 생성하는 ID를 사용하는 것이 가장 효율적인 선택이 될 것이다. update 검사가 불필요하므로, index와 shard가 커지더라도 index 성능은 크게 변화가 없다. 따라서, 가능한 한 이것을 사용하는 것이 좋다.

The update checks resulting from using an external ID will require additional disk access. The impact this will have depend on how efficiently the required data can be cached by the operating system, how fast the storage is and and how well it can handle random reads. The indexing speed also often goes down as indices and shards grow and more and more segments need to be checked.

외부 ID를 사용하여 update 검사를 하려면 추가적인 disk access가 필요하다. 이로 인한 영향은 필요한 data가 얼마나 효율적으로 OS에 의해 cache되느냐, storage의 속도, random 읽기를 얼머너 절 처리하느냐에 따라 다르다. 또한 index와 shard가 증가할수록 index 속도는 떨어지는 경향이 있으며, 더 많은 segment를 확인해야 한다.

Use of the rollover API

Traditional time-based indices rely on each index covering a specific set time period. This means that index and shard sizes can end up varying a lot if data volumes fluctuate over time. Uneven shard sizes are not desirable and can lead to performance problems.

전통적인 시간 기반 index는 특정 설정 기간을 다루는 각각의 index에 의존한다. 즉, data의 양이 시간에 다라 다를 경우, index와 shard 크기는 다양해 질 수 있다. shard 크기가 일정하지 않은 것은 바람직하지 않으며 성능 문제가 발생할 수 있다.

The rollover index API was introduced to provide a flexible way to manage time-based indices based on multiple criteria, not just time. It makes it possible to roll over to a new index once the existing one reaches a specific size, document count and/or age, resulting in much more predictable shard and index sizes.

rollover index API 는 시간 뿐만 아니라 다양한 기준에 따라 시산 기반 index를 다루는 다양한 방법을 제공하기 위해 도입되었다. 기존의 index가 특정 크기, document 수 또는 기간에 도달하면, 새로운 index로 roll over할 수 있어, 훨씬 더 예측 가능한 shard와 index 크기가 될 수 있다.

This however breaks the link between the event timestamp and the index it belongs to. When indices were based strictly on time, an event would always go to the same index no matter how late it arrived. It is this principle that makes it possible to prevent duplicates using external identifiers. When using the rollover API it is therefore no longer possible to completely prevent duplicates, even though the probability is reduced. It is possible that two duplicate events arrive on either side of a rollover and therefore end up in different indices even though they have the same timestamp, which will not result in an update.

그러나 이렇게 하면, event timestamp와 그것이 포함된 index간의 link가 끊어진다. index가 엄격하게 시간에 기반한 경우, event는 아무리 늦게 도착하더라도 항상 동일한 index로 간다. 외부 ID를 사용하여 중복을 방지하는 것이 원칙이다. 따라서, rollover API를 사용하는 경우, 가능성은 줄어들겠지만, 중복을 완전히 방지하는 것은 더 이상 불가능하다. 2개의 중복 event가 rollover의 양쪽에 도착하면, 그들이 동일한 timestamp를 가지고 있지만 다른 index이므로, update되지 않을 것이다.

It is therefore not recommended to use the rollover API if duplicate prevention is a strict requirement.

따라서, 중복 방지 요구가 있는 경우, rollover API를 사용하지 않는 것이 좋다.

Adapting to unpredictable traffic volumes

Even if the rollover API cannot be used, there are still ways to adapt and adjust shard size if traffic volumes fluctuate and result in time-based indices that are too small or large.

rollover API를 사용할 수 없는 경우에도, traffic 양이나 시간 기반 index가 너쿠 크거나 작은 경우 shard 크기를 조정할 수 있다.

If shards have ended up too large due to, e.g. a surge in traffic, it is possible to use the split index API to split the index into a larger number of shards. This API requires a setting to be applied at index creation, so this needs to be added through an index template.

예를 들어, traffic이 급증하여 shard가 너무 커진 경우, split index API 를 사용하여 더 많은 수의 shard로 index를 분할할 수 있다. 이 API는 index 생성시에 적용해야 하므로 설정이 필요하다. 따라서, 이것은 index template을 통해 추가해야 한다.

If traffic volumes on the other hand have been too low, resulting in unusually small shards, the shrink index API can be used to reduce the number of shards in the index.

반면에 traffic 양이 너무 작아 비정상적으로 작은 shard가 나타나면, shrink index API 를 통해 index의 shard 수를 줄일 수 있다.

Conclusions

As you have seen in this blog post, it is possible to prevent duplicates in Elasticsearch by specifying a document identifier externally prior to indexing data into Elasticsearch. The type and structure of the identifier can have a significant impact on indexing performance. This will however vary from use case to use case so it is recommended to benchmark to identify what is optimal for you and your particular scenario.

이 게시물에서 보았듯이 data를 Elasticsearch에 index하기 전에 외부에서 document ID를 지정하여 Elasticsearch에서 중복을 방지할 수 있다. ID의 유형과 구조는 index 성능에 막대한 영향을 끼칠 수 있다. 그러나, 이는 사용사례에 따라 다르므로, 최적화된 ID를 테스트해 보는 것이 좋다. 

원문 : Efficient Duplicate Prevention for Event-Based Data in Elasticsearch