Lucene Segment
ElasticSearch์์ ์ค๋(Shard)๋ฅผ ๊ตฌ์ฑํ๋ ๋ฃจ์ฌ(Lucene) Index์ ์ญ์์ธ(Inverted Index) ๊ตฌ์กฐ์ ๋ฌธ์ ๊ฒ์ ๊ธฐ๋ฅ์ ๊ตฌํ์ฒด์ธ Lucene Segment์ ๋ํด ์ดํด๋ณด์. Lucene Segment๋ฅผ ์ดํดํ๋ค๋ฉด, ElasticSearch ๋์์ ํต์ฌ์ ์ดํดํ ๊ฒ์ด๋ค!
ElasticSearch Index์ ๊ตฌ์กฐ
ElasticSearch Index๋ ์ฌ๋ฌ ์ค๋(Shard)๋ก ๋๋ ์ง๋ค. ์ค๋๋ ๋ฐ์ดํฐ๋ฅผ ๋๋ ์ผ์ข ์ ํํฐ์ (Partition)์ด๋ค.
ํ๋์ ES ์ค๋๋ ํ๋์ Lucene Index๋ฅผ ๊ฐ์ง๋ค. ์ฌ์ค ES ์ค๋๋ Lucene Index๋ฅผ ํ์ฅํ ๊ฒ์ด๋ ๋ค๋ฆ ์๋ค. ๊ฑฐ์ ๋น์ทํ ์กด์ฌ๋ผ๊ณ ๋ณด๋ฉด ๋๋ค!
ํ๋์ Lucene Index๋ ์ฌ๋ฌ Lucene Segment๋ก ๊ตฌ์ฑ๋๋ค. ์ Lucene Segment ์์ ES Document์ ์ญ์์ธ ๊ตฌ์กฐ๊ฐ ์กด์ฌํ๋ ๊ฒ์ด๋ค.
Lucene Index์ Seegment
Lucene์ ์ด์์ ์ธ ์ค-์ค์๊ฐ(Near-realtime) ๊ฒ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ธฐ ์ํด Lucene Index์ Lucene Segment ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ค. Luncene Index์ Segment์์์ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์/์์ฑ/์์ /๋ณ๊ฒฝ ๋ก์ง์ ์ดํด๋ณด์.
class LuSegment:
self.documents = [Document(1), Document(2), ...]
self.inverted_index = InvertedIndex()
class LuIndex:
self.segments = [Segment(1), Segment(2), ...]
๋ฌธ์ ๊ฒ์
Lucene Index์ ๊ฒ์์ ์ธ๋ฑ์ค๊ฐ ๊ฐ์ง N๊ฐ์ Lucene Segment์์ ๊ฒ์ํด์ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์กฐํฉํ ๊ฒ์ด๋ค. ์ฝ๋๋ก ๋ํ๋ด๋ฉด ์๋์ ๊ฐ๋ค.
class LuSegment:
self.documents = [Document(1), Document(2), ...]
self.inverted_index = InvertedIndex()
def search(self, qry: str):
doc_idx = inverted_index(qry)
return documents[doc_idx]
class LuIndex:
self.segments = [Segment(1), Segment(2), ...]
def search(self, qry: str):
qry_ret = []
for segment in segments:
ret = segment.search(qry)
qry_ret.append(ret)
return qry_ret
๋ฌผ๋ก ์์ ์ฝ๋๋ ์ดํด๋ฅผ ์ํด Lucene Index์ Segment์ ๊ฒ์์ ๋จ์ํ ํ ๊ฒ์ด๋ค.
๊ฐ Segment์์์ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ทจํฉํ๋ ๊ฒ๋ ๋จ์ํ .append()
ํ์ง ์์ ๊ฒ์ด๋ค.
์ด๋ ๊ฒ Lucene Indexd์์ Segment ๋จ์๋ก ๊ฒ์ํ๊ณ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ทจํฉํ๋ ๋ฐฉ์์ โ์ธ๊ทธ๋จผํธ ๋จ์ ๊ฒ์(Per-Segment Search)โ์ด๋ผ๊ณ ํ๋ค.
๋ฌธ์ ์์ฑ
์๋ก์ด Document๋ฅผ ์ถ๊ฐํ๋ฉด Lucene Index๋ ์๋ก์ด Lucene Segment๋ฅผ ๋ง๋ค์ด ์ ์ฅํด๋๋ค.
class LuSegment:
self.documents = [Document(1), Document(2), ...]
self.inverted_index = InvertedIndex()
def __init__(self, document: Document):
self.documents = [document]
self.inverted_index = InvertedIndex(document)
class LuIndex:
self.segments = [Segment(1), Segment(2), ...]
def insert(self, document: Document):
newSegment = Segment(document)
self.segments.append(newSegment)
๊ทธ๋ฌ๋ Document๋ฅผ ์ถ๊ฐ๋ก ์ธ๊ทธ๋จผํธ๊ฐ ์์ฑ๋๊ธฐ๋ง ํ๋ค๋ฉด Segment๋ ๋ ํ๋์ Document๋ง ๊ฐ์ง๊ฒ ๋๋ค. ๊ทธ๋์ Lucene Index๋ ์ฃผ๊ธฐ์ ์ผ๋ก โMergeโ ์์ ์ ํตํด Segment๋ฅผ ๋ณํฉํ๋ค.
class LuSegment:
self.documents = [Document(1), Document(2), ...]
self.inverted_index = InvertedIndex()
def __init__(self, seg1: Segment, seg2: Segment):
self.documents = seg1.documents + seg2.documents
self.inverted_index = InvertedIndex(self.documents)
class LuIndex:
self.segments = [Segment(1), Segment(2), ...]
def merge(self):
seg1 = segments[0]
seg2 = segments[1]
newSegment = Segment(seg1, seg2)
segments.push(newSegment)
del seg[0:2]
Lucene Index์ Segment ๋์ ๊ณจ๋ผ ์๋ก์ด Segment๋ฅผ ์์ฑํ๋ค. ๋ Segment๊ฐ ํฉ์น๋ฉด, ๊ฒ์์์ Lucene Index๊ฐ ํ์ํ Segment ์๊ฐ ์ค์ด๋ ๋ค!
๋ฌผ๋ก ์์ ์ฝ๋๋ ์ดํด๋ฅผ ์ํด Merge ์์ ์ ๋จ์ํ ํ ๊ฒ์ด๋ค! ์ค์ ๋ก ๋ณํฉํ ๋ Segment๋ฅผ ์ ํํ๋ ๋ฐฉ์๋ ๋ณต์กํ๋ฉฐ, Merge ๊ณผ์ ์ค์๋ ์ญ์ ํ์๋ ๋ฌธ์์ โ๋ฌผ๋ฆฌ์ ์ญ์ โ๋ ์ด๋ค์ง๋ค!
๋ฌธ์ ์ญ์
Lucene Index์์ Document์ Lucene Segment๋ ๋ถ๋ณ์ฑ(immutability)๋ฅผ ๊ฐ์ง๋ค. ์ด๊ฒ์ ๋ฌธ์์ ๋ํ ์ญ์ ์์ฒญ์ด ๋ฐ์ํด๋ ํด๋น Document๋ฅผ ์ค์ ๋ก ๋ฌผ๋ฆฌ์ ๊ณต๊ฐ์์ ์ญ์ ํ์ง๋ ์๋๋ค๋ ๋ง์ด๋ค! ๋ค๋ง, ์ ์ (Client) ์ ์ฅ์์ ๋ฌธ์๊ฐ ์ญ์ ๋์๋ค๋ ์๋ต์ ์ ์์ ์ผ๋ก ๋ฐ๋๋ค.
๋์ ์ญ์ ์์ฒญ ์จ ๋ฌธ์์ id
๋ฅผ ๊ฒ์ํด์ ํด๋น id
๋ฅผ ๊ฐ์ง ๋ฌธ์๋ค์ "์ญ์ ๋จ"
๋ผ๋ ํ์๋ง ํด๋๋ค. ๊ทธ๋ผ ๋ฌผ๋ฆฌ์ ์ญ์ ๋ ์ธ์ ์ผ์ด๋๋๊ฐ? ๋ฌผ๋ฆฌ์ ์ญ์ ๋ Segment๊ฐ โ๋จธ์งโ๋ ๋, Segment์ ๋ฌธ์ ์ค์ "์ญ์ ๋จ"
ํ์๊ฐ ์๋ ๋ฌธ์๋ฅผ ์ญ์ ํ๋ ๊ฒ์ด๋ค.
class LuSegment:
self.documents = [Document(1), Document(2), ...]
self.inverted_index = InvertedIndex()
def delete(self, doc_id: str):
doc_idx = self.inverted_index(id)
doc = self.documents[doc_idx]
doc.is_deleted = True
def __init__(self, seg1: Segment, seg2: Segment):
self.documents = []
for document in (seg1.documents + seg2.documents):
if document.is_delete:
continue
self.documents.append(document)
self.inverted_index = InvertedIndex(self.documents)
class LuIndex:
self.segments = [Segment(1), Segment(2), ...]
def delete(self, doc_id: str):
for segment in segments:
segment.delete(doc_id)
def merge(self):
...
๋ฌธ์ ๋ณ๊ฒฝ
ES์์ ๋ฌธ์์ ๋ณ๊ฒฝ์ DB์์์ ๋ณ๊ฒฝ๊ณผ ๋ฌ๋ฆฌ Overwrite(Delete & Write)๊ฐ ์๋ ์๋ก Document๋ฅผ ๋ง๋ ํ ๋ฌธ์์ โ๋ฒ์ โ์ ์ฌ๋ฆฌ๋ ๊ฒ์ด๋ค. ์ฆ, Mark Delete๋ฅผ ํ ํ Write๋ฅผ ํ๋ ์ ์ด๋ค! ๊ทธ๋์ ๋ฌธ์๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒ์ ์ฌ์ค Lucene Index์์ ๋ฌธ์๋ฅผ ์ญ์ ํ๊ณ ์๋ก ๋ฌธ์๋ฅผ ํ๋ ์์ฑํ๋ ๊ฒ๊ณผ ๊ฐ๋ค! ๋จ, ์ฌ๊ธฐ์์ ๋ฌธ์ ์ญ์ ๋ ๋ฌผ๋ฆฌ์ ์ญ์ ๊ฐ ์๋๋ผ Mark Delete ํ๋ ๊ฒ์ ๋งํ๋ค.
class LuIndex:
self.segments = [Segment(1), Segment(2), ...]
def update(self, doc_id: str, document: Document):
self.delete(doc_id)
self.insert(doc_id, document)
์ธ๊ทธ๋จผํธ ๋ถ๋ณ์ฑ
์ Lucene์ ์ธ๊ทธ๋จผํธ ๋ถ๋ณ์ฑ(Segment Immutability)๋ฅผ ์ฑํํ ๊ฒ์ผ๊น?
๋จผ์ ์ธ๊ทธ๋จผํธ ๋ถ๋ณ์ฑ์ด ์๋ ์ํฉ์์ ๋ฌธ์๋ฅผ ์ญ์ ํ๋ค๊ณ ์๊ฐํด๋ณด์. ๊ทธ๋ฌ๋ฉด Segment์ (1) ๋ฌธ์ ๋ฆฌ์คํธ์์ ๋ฌธ์๋ฅผ ์ญ์ ํ๊ณ (2) Inverted Index๋ฅผ ๊ฐฑ์ ํ๋ ๋ ๊ณผ์ ์ ๋ค์ ์ํํด์ค์ผ ํ๋ค.
๊ทธ๋ฐ๋ฐ Segment๊ฐ ์์ ํ๋ ์ค์ธ๋ฐ, ํด๋น Segment์ ๊ฒ์ ์์ฒญ์ด ์ค๋ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด๋ณด์. ๊ทธ๋ฌ๋ฉด ๊ฒ์ ์์ ์ ์ฅ์์๋ 2๊ฐ์ง ์ ํ์ง๊ฐ ์๋๋ฐ
- ์์ ์ค์ธ Segment๋ ์คํตํ๋ค.
- Segment ์์ ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค.
1๋ฒ ๋ฐฉ์์ ์ฑํํ๋ฉด, ์์ ์ค์ธ Segment์ ์๋ Document๊ฐ ๊ฒ์ ๊ฒฐ๊ณผ์์ ์คํต ๋๊ธฐ ๋๋ฌธ์ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ๋ถ์ ํ ํด์ง๋ค. 2๋ฒ ๋ฐฉ์์ ์ฑํํ๋ฉด, ๋ฌธ์ ์ญ์ ์์ฒญ ์ดํ์ ํธ๋ฆฌ๊ฑฐ๋ ๋ชจ๋ ๊ฒ์์ด Segment ์์ ์ด ์๋ฃ๋ ๋๊น์ง pending ๋์ด๋ฒ๋ฆฐ๋ค!
๊ฒฐ๊ตญ, Segment๋ฅผ ์์ ํ๋ ์์ ์์ฒด๊ฐ ๊ฒ์ ์์ง์๊ฒ๋ ๋ถ๋ด์ค๋ฌ์ด ์์ ์ด๋ผ๋ ๋ง์ด๋ค!
๊ฒฐ๊ตญ Lucene์ ์ธ๊ทธ๋จผํธ ๋ด์ ๋ฌธ์์ ๋ณ๊ฒฝ/์ญ์ ์์
์ด ์ผ์ด๋๋ฉด ์ผ๋จ ๊ฐ์ง๊ณ ์๋ ๋ฌธ์๋ฅผ "์ญ์ ๋จ"
์ผ๋ก ํ์ํด๋๊ณ , ์๋ก์ด ๋ฒ์ ์ ๋ฌธ์๊ฐ ๋ด๊ธด Segment๋ฅผ ์์ฑํ๊ฒ ๋ ๊ฒ์ด๋ค!