욱'S 노트

Elasticsearch - Modeling your data:Handling Relationships 본문

Programming/Elasticsearch

Elasticsearch - Modeling your data:Handling Relationships

devsun 2016. 1. 18. 10:01

엘라스틱서치는 다른 종류의 종이다. 특히 당신이 SQL의 세상으로부터 왔다면. 엘라스틱서치는 많은 이점이 있다. : 성능, 확장성, NRT 서치, 대용량 데이터 분석 등. 또한 쉽게 이러한 일들을 수행할 수 있다. 


그러나 마법같이 모든 것을 해결해줄수는 없다. 이에 우리는 어떻게 동작하고 당신의 요구사항을 어떻게 동작하게 만들수 있는지를 이해해야 한다.


엔티티간의 관계를 처리하는 것은 관계를 지정할 수 있는 스토어처럼 명백하진 않다. 데이터 정규화를 엘라스틱서치에 적용할 수 없다. Handling Relationships, Nested Objects 그리고  Parent-Child Relationship을 통해 우리는 가능한 접근법들의 특성과 제약에 대해서 알아볼 것이다. 


Handling Relationships


실제 세상에서 관계란 : 블로그 포스트는 코멘트를 가지고, 은행 계좌는 거래를 포함하고, 고객은 은행 계좌를 가지고, 디렉토리는 파일과 서브 디렉토리를 가진다.


관계형 데이터베이스는 이러한 관계를 명확하게 관리하기 위해 설계되었다.

  • 각 엔티티는 주키에 의해 식별할 수 있다.
  • 엔티티는 정규화된다. 유니크한 엔티티는 하나만 저장되고 그들의 저장된 주키로 엔티티의 관계를 연결한다. 엔티티 데이터의 변경은 한 곳에서만 일어난다.
  • 엔티티는 쿼리시 조인될 수 있다.
  • 하나의 엔티티의 변경은 ACID하다.
  • 대부분의 관계형 데이터베이스는 다수 엔티티의 ACID 트랜잭션을 지원한다.


각 관계형 데이터베이스는 한계점을 가지고 있다. 그리고 그들은 full-text 서치를 지원하지 않는다. 쿼리 타임에 엔티티를 조인하는 것은 비용이 매우 비싸다. 엔티티간 조인을 수행하는 것은 일반적으로 하드웨어 간에 수행된다면 더욱 비싸지면 이러한 이유로 데이터를 하나의 서버에 저장한다.


엘라스틱서치는 대부분의 NoSQL 데이터베이스와 마찬가지로 실데이터를 flat하게 처리한다. 인덱스는 독립적인 도큐먼트의 플랫한 컬렉션이다. 하나의 도큐먼트는 서치 요청에 대응되는 모든 정보를 포함해야 한다


하나의 도큐먼트의 변경에 대해서 ACID 하지만 다수의 도큐먼트에 대해서는 그렇지 않다. 이렇기 때문에 트랜잭션 실패가 발생했을 경우 전체를 롤백할 수 없으므로, 부분 실패가 발생할 수 있다.


이러한 FlatWorld는 다음과 같은 이전이 있다.

  • 인덱싱은 빠르고 락에서 자유롭다.
  • 서칭은 빠르고 락에서 자유롭다.
  • 대용량 데이터를 다수의 노드에 분산시킬수 있다. 각 도큐먼트는 독립적이기 때문이다.


그러나 관계 측면에서 문제가 발생할 수 있는데 엘라스틱 서치에서 관계형 데이터를 관리하는 방법은 다음과 같다.

  • Application-side joins
  • Data denormalization
  • Nested objects
  • Parent/child relationships


종종 최종 해결책은 이러한 기술을 섞어서 사용하는 것이다.


Application-side joins


우리는 조인을 구현함으로써 관계형 데이터베이스의 조인을 에뮬레이트할 수 있다. 예를 들어 우리가 user와 blog post를 인덱싱하고 있다가 가정하다. 실제 예를 보면 다음과 같을 것이다.


PUT /my_index/user/1 

{

  "name":     "John Smith",

  "email":    "john@smith.com",

  "dob":      "1970/10/24"

}


PUT /my_index/blogpost/2 

{

  "title":    "Relationships",

  "body":     "It's complicated...",

  "user":     1 

}


인덱스, 타입 및  각 도큐먼트는 주키에 대한 함수로서 각 도큐먼트에 저장된다.


블로그 포스트는 저장된 user id에 의해 user와 연결된다. 인덱스와 타입은 어플리케이션에서 하드코딩될 필요가 없다.


ID가 1인 블로그 포스트를 찾는 일은 쉽다.


GET /my_index/blogpost/_search

{

  "query": {

    "filtered": {

      "filter": {

        "term": { "user": 1 }

      }

    }

  }

}


John이라고 불리는 유저의 블로그 포스트를 찾기 위해선 두가지 쿼리을 수행해야 한다. 첫번째는 존이라고 불리는 유저를 찾는 것이고, 두번째는 그들의 ID를 추출해 해당하는 블로그포스트를 찾는 것이다.


GET /my_index/user/_search

{

  "query": {

    "match": {

      "name": "John"

    }

  }

}


GET /my_index/blogpost/_search

{

  "query": {

    "filtered": {

      "filter": {

        "terms": { "user": [1] }  

      }

    }

  }

}


어플리케이션 사이드 조인의 주 이점은 데이터가 정규화 될 수 있다는 것이다. 한쪽에서 유저의 이름을 변경하면 된다는 것이다. 반면 안좋은 점은 서치 타임에 도큐먼트를 조인하기 위해 추가적인 쿼리를 발생해야 한다는 점이다.


이 예제는 하나의 유저가 첫번째 쿼리에 매치되었지만 실제 세계에서는 쉽게 존이라는 수백만의 유저가 나타날 수 있다. 이럴 경우 두번째 쿼리는 수백만의 단어를 찾게 될 수 있다.


이러한 접근법은 첫번째 엔티티가 작은 수의 도큐먼트를 가지고 있고, 되도록이면 변경되지 않을떄 적합하다. 이럴 경우 어플리케이션에서 결과를 캐싱하여 첫번째 쿼리가 자주 실행되는 것을 피할 수 있다.


Denormalizing Your Data


엘라스틱 서치에서 최고의 성능을 내는 방법은 인덱스 타입에 데이터를 비정규화하는 것이다. 각 데이터의 중복을 허용하는 함으로써 조인할 필요가 없게 된다.


만약 글쓴이의 이름으로 블로그 포스트를 찾기를 원한다면 블로그 포스트 도큐먼트에 글쓴이의 이름을 포함시키는 것이다.


PUT /my_index/user/1

{

  "name":     "John Smith",

  "email":    "john@smith.com",

  "dob":      "1970/10/24"

}


PUT /my_index/blogpost/2

{

  "title":    "Relationships",

  "body":     "It's complicated...",

  "user":     {

    "id":       1,

    "name":     "John Smith" 

  }

}


유저 데이터의 일부분은 비정규화되어 블로그포스트 도큐먼트에 포함되었다.


이제 우리는 한번의 쿼리에서 John이라고 불리는 유저의 블로그 포스트를 찾을 수 있다.


GET /my_index/blogpost/_search

{

  "query": {

    "bool": {

      "must": [

        { "match": { "title":     "relationships" }},

        { "match": { "user.name": "John"          }}

      ]

    }

  }

}


데이터 비정규화의 이점은 속독이다. 각 도큐먼트가 쿼리에 필요한 정보를 모두 포함하기 때문에 비싼 조인을 수행할 필요가 없다.


Field Collapsing


공통적인 요구사항중 하나는 특정한 필드로 그룹핑하여 서치 결과를 표현하는 것이다. 우리는 user명으로 그룹핑된 블로그 포스트를 리턴받기를 원할 수 있다. 이름으로 그룹핑한다는 것은 단어를 aggregation할 필요가 있다는 것을 내포한다. 유저의 전체 이름을 그룹핑하기 위해서 이름 필드는 not_analyzed 형식이여야 하며 자세한 내용은 Aggregations and Analysis를 살펴보자.


PUT /my_index/_mapping/blogpost

{

  "properties": {

    "user": {

      "properties": {

        "name": { 

          "type": "string",

          "fields": {

            "raw": { 

              "type":  "string",

              "index": "not_analyzed"

            }

          }

        }

      }

    }

  }

}


user.name 필드는 full-text 서치를 위해 사용될 수 있다.


The user.name.raw 필드는 terms aggregation을 위해 사용될 것이다.


몇가지 데이터를 추가해보자.


PUT /my_index/user/1

{

  "name": "John Smith",

  "email": "john@smith.com",

  "dob": "1970/10/24"

}


PUT /my_index/blogpost/2

{

  "title": "Relationships",

  "body": "It's complicated...",

  "user": {

    "id": 1,

    "name": "John Smith"

  }

}


PUT /my_index/user/3

{

  "name": "Alice John",

  "email": "alice@john.com",

  "dob": "1979/01/04"

}


PUT /my_index/blogpost/4

{

  "title": "Relationships are cool",

  "body": "It's not complicated at all...",

  "user": {

    "id": 3,

    "name": "Alice John"

  }

}


이제 존이라고 불리는 유저와 관계된 블로그 포스트를 찾고 유저 이름에 따른 결과를 그룹핑해보자.


GET /my_index/blogpost/_search

{

  "size" : 0, 

  "query": { 

    "bool": {

      "must": [

        { "match": { "title":     "relationships" }},

        { "match": { "user.name": "John"          }}

      ]

    }

  },

  "aggs": {

    "users": {

      "terms": {

        "field":   "user.name.raw",      

        "order": { "top_score": "desc" } 

      },

      "aggs": {

        "top_score": { "max":      { "script":  "_score"           }}, 

        "blogposts": { "top_hits": { "_source": "title", "size": 5 }}  

      }

    }

  }

}


블로그 포스트 어그리개이션이 반환되는 우리가 관심 있는 블로그 포스트는 size를 0로 세팅함으로써 일반적인 서치 히트를 비활성화 할 수 있다.


쿼리는 존이라는 이름을 가진 유저와 관계가 있는 블로그 포스트를 리턴한다.


terms aggregation은 user.name.raw라는 값을 위한 bucket를 생성한다.


top_score aggregation읜 순서는 각 버켓의 top-scoring document의 유저 어그리개이션의 단어 순서이다.


top_hits aggregation은 각 유저의 다섯개의 가장 관계 있는 블로그 포스트의 title 필드가 리턴한다.


요약된 결과는 아래와 같다.


...

"hits": {

  "total":     2,

  "max_score": 0,

  "hits":      [] 

},

"aggregations": {

  "users": {

     "buckets": [

        {

           "key":       "John Smith", 

           "doc_count": 1,

           "blogposts": {

              "hits": { 

                 "total":     1,

                 "max_score": 0.35258877,

                 "hits": [

                    {

                       "_index": "my_index",

                       "_type":  "blogpost",

                       "_id":    "2",

                       "_score": 0.35258877,

                       "_source": {

                          "title": "Relationships"

                       }

                    }

                 ]

              }

           },

           "top_score": { 

              "value": 0.3525887727737427

           }

        },

...


hits 배열은 비어있다. 이유는 size 0으로 세팅했기 때문이다. 


각 유저를 위한 top results 버켓이 있다.


각 유저의 blogspots.hits 배열은 유저를 위한 top results를 포함한다.


유저 버킷은 가장 연관된 블로그 포스트에 의해 정렬된다. 


top_hits aggregation의 사용은 가장 관계있는 블로그 포스트의 유저명을 리턴받는 쿼리를 수행하는 것과 같으며, 이는 각 유저를 위해 그들의 베스트 블로그 포스트를 가지고오기 위한 쿼리를 수행하는 것과 같다. 


각 버킷의 top hits 결과는 원래 메인 쿼리의 기반한 작은 가볍고 작은 쿼리를 수행 결과 이다. 이러한 기능은 페이징이나 하이라이팅에 유용하다.


Denormalization and Concurrency


물론 데이터 비정규화는 약점을 가지고 있다. 첫번째 약점은 인덱스 자체의 용량이 커진다는 것이다. 그러나 이것은 큰 문제는 아니다. 디스크에 데이터는 압축될 것이며, 디스크 용량은 싸다. 엘라스틱서치는 추가적인 데이터에 잘 대처한다.


더 중요한 이슈는 유저의 이름이 변경되었을때, 그의 블로그 포스트를 모두 업데이트해야 된다는 점이다. 다행이도 이름은 잘 바뀌지 않는다. 만약 변경된다면 수천의 블로그 포스트가 변경되어야 하지만 이것 또한 bulk API로 1초내에 수행할 수 있다. 


하지만 더 복잡한 시나리오를 고려해보자. 여기서는 변경은 일반적이며, 멀리 떨어져 있으며, 더욱 중요하고, 동시에 수행된다.


이런 예제를 위해 엘라스틱 서치에서 파일시스템의 디렉토리 트리를 에뮬레이트해보겠다.


우리가 특정 디렉토리에 파일들을 조회 한다면 다음과 같이 수행될 될것이다.


grep "some text" /clinton/projects/elasticsearch/*


이러한 요구사항에 맞는 정보를 등록해보자.


PUT /fs/file/1

{

  "name":     "README.txt", 

  "path":     "/clinton/projects/elasticsearch", 

  "contents": "Starting a new Elasticsearch project is easy..."

}


  • 파일명
  • 파일을 저장하고 있는 디렉토리의 전체 경로

우리는 또한 특정 디렉토리 하위에 디렉토리에 파일이 존재하는지를 검사하기도 원한다.


grep -r "some text" /clinton


이것을 지원하기 위해 path 계층구조를 인덱스할 필요도 있다. 


/clinton

/clinton/projects

/clinton/projects/elasticsearch


이러한 hierarchy를 주어진 path 필드로 자동적으로 생성될 수 있는데 이는 아래와 같다.


PUT /fs

{

  "settings": {

    "analysis": {

      "analyzer": {

        "paths": { 

          "tokenizer": "path_hierarchy"

        }

      }

    }

  }

}


커스텀 패스 analyzer는 기존 세팅으로 path_hierarchy tokenizer를 사용한다. 


파일 타입을 위한 매핑은 다음과 같다.


PUT /fs/_mapping/file

{

  "properties": {

    "name": { 

      "type":  "string",

      "index": "not_analyzed"

    },

    "path": { 

      "type":  "string",

      "index": "not_analyzed",

      "fields": {

        "tree": { 

          "type":     "string",

          "analyzer": "paths"

        }

      }

    }

  }

}


name 필드는 정확한 이름을 포함한다.


path 필드는 정확한 디렉토리 명이 포함될 것이며 path.tree 필드에 path hierarchy가 포함될 것이다.


인덱스에 세팅되고 파일이 인덱싱되면, 우리는 elasticsearch가 포함된 파일을 /clinton/projects/elasticsearch 디렉토리에서 다음과 같이 서치할 수 있다.


GET /fs/file/_search

{

  "query": {

    "filtered": {

      "query": {

        "match": {

          "contents": "elasticsearch"

        }

      },

      "filter": {

        "term": { 

          "path": "/clinton/projects/elasticsearch"

        }

      }

    }

  }

}


Find files in this directory only.


/clinton 디렉토리 하위의 모든 파일에 대한 검사를 수행할려면 다음과 같다.


GET /fs/file/_search

{

  "query": {

    "filtered": {

      "query": {

        "match": {

          "contents": "elasticsearch"

        }

      },

      "filter": {

        "term": { 

          "path.tree": "/clinton"

        }

      }

    }

  }

}


Renaming Files and Directories


파일명을 수정하는 것은 쉽다. 


PUT /fs/file/1?version=2 

{

  "name":     "README.asciidoc",

  "path":     "/clinton/projects/elasticsearch",

  "contents": "Starting a new Elasticsearch project is easy..."

}


버젼 넘버는 변경이 적용되었다는 것을 확실히 할뿐 아니라 인덱스의 도큐먼트는 이 같은 버젼넘버를 가지게 된다.


디렉토리명을 변경하는 것도 가능하다. 하지만 이는 패스 계층에 포함된 모든 디렉토리 및 파일을 변경해야 한다. 이는 빠를수도 느릴수도 있는데 이는 변경되어야 하는 파일수에 의존적이다. 


Solving Concurrent Issues


위의 문제에서 가장 큰 문제는 동시에 여러명의 사람이 파일이나 디렉토리 이름을 변경할 때 온다. 디렉토리 하위에는 수천개의 파일이 존재할 수 있으며, 여러명이 동시에 변경을 한다면 정보의 파편화가 발생할 수 있다.


다음과 같은 경우중 하나가 발생할 수 있다.


버젼 넘버를 사용하기로 했다면 버젼 충돌이 발생하면서 변경에 실패할 수 있다..

버젼을 사용하지 않는다면 당신의 변경이 다른 유저의 변경으로 업데이트 될 수 있다.


엘라스틱 서치의 문제는 ACID 트랜잭션을 지원하지 않는다는 것이다. 개별적인 문서의 변경은 ACID하나 다수의 도큐먼트의 변경은 지원하지 않는다.


만약 메인 데이터 스토어는 관계형 데이터베이스이고, 엘라스틱 서치이 검색엔진으로 사용되고 있다면 데이터베이스에서의 변경이 엘라스틱 서치에 복제된다면 별다른 문제가 없을 것이다. 이러한 방법은 database의 ACID 트랜잭션의 이점을 사용할 수 있기때문에 바른 순서로 엘라스틱서치에 변경이 발생할 것이다. 동시성은 관계형 데이터베이스에서 처리된다. 


만약 관계형 데이터베이스를 사용하지 않는다면, 동시성 이슈를 엘라스틱서치 레벨에서 사용해야 한다. 다음과 같은 세가지 실제적인 솔루션이 있다.


  • Global Locking
  • Document Locking
  • Tree Locking


Global Locking


우리는 특정 시간에 하나의 프로세스만 변경을 할 수 있도록 함으로써 동시성 이슈를 완벽하게 해결할 수 있다. 대부분의 변경은 몇개의 파일일 것이며, 매우 빨리 완료될 것이다. 


도큐먼트 단위의 ACID만을 지원하기 떄문에 글로벌 락으로써 도큐먼트의 존재 및 삭제를 사용할 수 있다.


PUT /fs/lock/global/_create

{}


만약 conflict가 발생한다면 다른 프로세스가 글로벌 락을 획득한 것이며, 다음에 다시 시도를 하면 된다. 만약 성공한다면 글로벌 락의 오너가 되며 변경을 계속 수행하면 된다. 만약 변경이 끝나면 글로벌 락 도큐먼트를 삭제함으로써 락을 릴리이즈 해야 한다.


DELETE /fs/lock/global


변경이 얼마나 자주일어나고 얼마나 오래 걸리냐에 따라서 시스템의 성능은 현저히 떨어짛수가 있다. 병렬처리를 증대하기위해 락킹을 더욱 정교하게 만들 필요가 있다.


Document Locking


모든 파일 시스템에 락킹을 하는 대신에 같은 기술을 이용해 개별적인 도큐먼트에 락키을 수행할 수 있다. 변경되고 생성될 파일에 대한 scrolled search를 수행할 수 있다.


PUT /fs/lock/_bulk

{ "create": { "_id": 1}} 

{ "process_id": 123    } 

{ "create": { "_id": 2}}

{ "process_id": 123    }


락 도큐먼트의 아이디는 락킹될 파일의 아이디와 같다.


process_id는 유니크하며 변경이 수행될 프로세스를 표현한다.


만약 몇몇의 파일이 이미 락킹이 되어 있다면 bulk 요청은 실패할 것이며 다음에 다시 수행하면 된다.


만약 모든 파일에 대한 락을 다시 요청한다면 명백하게 실패할 것이다. 이는 우리에 의해 락이 잡힌 파일이 존재하기 때문이다. 이에 우리는 단순한 create 명령 대신에 upsert 파라미터와 아래 스크립트와 함께 업데이트 명령을 날려야 한다.


if ( ctx._source.process_id != process_id ) { 

  assert false;  

}

ctx.op = 'noop'; 



process_id는 파라미터로 스크립트에 전달되어야 한다.


assert false은 exception을 발생시키고 업데이트를 실패하게 만든다


Changing the op from update to noop prevents the update request from making any changes, but still returns success.


전체 업데이트 리퀘스트는 다음과 같다.


POST /fs/lock/1/_update

{

  "upsert": { "process_id": 123 },

  "script": "if ( ctx._source.process_id != process_id )

  { assert false }; ctx.op = 'noop';"

  "params": {

    "process_id": 123

  }

}


만약 도큐먼트가 이미 존재하지 않는다면 upsert 도큐먼트는 insert될 것이다. 하지만 도큐먼트가 존재한다면 저장된 도큐먼트의 process_id를 확인한 후 자기 자신이라면 아무런 일을 하지 않고 다르다면 오류가 발생하면서 락을 획득하는데 실패할 것이다.


만약 모든 락이 성공적으로 생성되면 변경을 수행하면 된다.


더 중요한점은 락을 꼭 해제해주어야 한다는 것인데 bulk delete를 수행함으로써 락을 해제할 수 있다.


POST /fs/_refresh 


GET /fs/lock/_search?scroll=1m 

{

    "sort" : ["_doc"],

    "query": {

        "match" : {

            "process_id" : 123

        }

    }

}


PUT /fs/lock/_bulk

{ "delete": { "_id": 1}}

{ "delete": { "_id": 2}}


refresh 요청은 모든 락 도큐먼트가 서치 요청에 보이도록 확실하게 해준다.


하나의 검색 요청에 많은 수의 결과를 조회하기를 원한다면 scroll 쿼리를 사용할 수 있다.


도큐먼트 레벨 락킹은 잘 정제된 접근 제어를 가능하게 한다. 그러나 수백만의 락파일을 생성하는 것은 비용이 많이 든다. 몇몇의 경우 tree-scenario가 더 좋은 락킹의 메커니즘으로 나타날때도 있다.


Tree Locking


우리는 파일이나 디렉토리에 배타적인 접근이 필요하다. 이럴 경우 exclusive lock 도큐먼트를 획득함으로써 가능한다.


{ "lock_type": "exclusive" }


어떤 부모 디렉토리에 shared 락이 필요하다면 다음과 같이 생각할 수 있다.


{

  "lock_type":  "shared",

  "lock_count": 1 

}


lock_count 레코드는 shared lock을 획득하고 있고 프로세스의 수이다.


/clinton/projects/elasticsearch/README.txt의 파일명을 변경하고 싶은 프로세스는 파일에 대한 배타적인 락을 획득해야 되고, /clinton, clinton/projects, clinton/projects/elasticsearch의 디렉토리에 공유 락을 획득해야 된다.


배타적인 락을 획득할 요청은 다음과 같으며, shared lock에 대한 카운트를 올리는 것은 다음과 같은 로직으로 가능하다.


if (ctx._source.lock_type == 'exclusive') {

  assert false; 

}

ctx._source.lock_count++ 


만약 락 타입이 배타적이면 assert 문장은 예외를 발생할 것이며 update 문장은 실패할 것이다.


그렇지 않을 경우 lock_count를 증가시킬 것이다.


또한 락 도큐먼트가 존재하지 않을때 처리를 위한 upsert 문장도 표현되어야 한다. 전체 update 요청 스크립트는 다음과 같다.


POST /fs/lock/%2Fclinton/_update 

{

  "upsert": { 

    "lock_type":  "shared",

    "lock_count": 1

  },

  "script": "if (ctx._source.lock_type == 'exclusive')

  { assert false }; ctx._source.lock_count++"

}


도큐먼트의 ID는 /clinton이며, URL 인코딩을 하면 %2fclinton과 같다.


upsert 도큐먼트는 도큐먼트가 존재하지 않으면 인서트 된다.


parent 디렉토리에 공유 락을 획득하면 파일 자체게 exclusive 락을 생성해야 한다.O


PUT /fs/lock/%2Fclinton%2fprojects%2felasticsearch%2fREADME.txt/_create

{ "lock_type": "exclusive" }


이제 누군가가 /clinton 디렉토리의 이름을 변경하기 원한다면 그들은 path 상에 배타적인 락을 획득해야 한다.


PUT /fs/lock/%2Fclinton/_create

{ "lock_type": "exclusive" }


이 요청은 실패할 것이다. 이유는 같은 ID의 락 도큐먼트가 존재하기 때문이다. 다른 유저는 우리의 오퍼레이션이 끝날때 까지 기다리고 우리가 락을 릴리이즈하면 락을 획득할 수 있다. 배타적인 락은 단순히 삭제를 하면 된다.


DELETE /fs/lock/%2Fclinton%2fprojects%2felasticsearch%2fREADME.txt


공유락은 lock_count를 감소해야 되며 카운트가 0가되면 락 도큐먼트를 삭제해야 한다.


if (--ctx._source.lock_count == 0) {

  ctx.op = 'delete' 

}


lock_count가 0이 되면 ctx.op는 update에서 delete로 바뀐다. 


이 업데이트 요청은 각 부모 디렉토리로 긴것에서 짧은순으로 역순으로 수행되어야 한다


POST /fs/lock/%2Fclinton%2fprojects%2felasticsearch/_update

{

  "script": "if (--ctx._source.lock_count == 0) { ctx.op = 'delete' } "

}


트리 락킹은 우리에게 작은 노력으로 잘 정의된 동시성 제어를 제공한다. 물론 모든 솔루션에 적합하지는 않다. 


프로세스가 비정상적으로 종료한다면 두가지 문제가 발생할 것이다.


죽은 프로세스에 대한 락은 누가 어떻게 릴리이즈를 할 것인가?

죽은 프로세스를 어떻게 클린징할 것인가?


이러한 범위는 이 책에서 벗어난다. 그러나 만약 락킹을 사용하기로 생각했다면 이러한 해답을 만들 필요가 있다.


비정규화는 많은 프로젝트에서 좋은 결정이다. 하지만 락킹에 대한 처리를 위한 복잡한 스키마와 구현이 추가될 수 있다. 이에 엘라스틱 서치는 연관 엔티티를 처리하기 위한 두가지 모델을 제시한다.

Comments