Rumah > pembangunan bahagian belakang > Golang > Membina Enjin Penyimpanan Pokok LSM daripada Gores

Membina Enjin Penyimpanan Pokok LSM daripada Gores

Barbara Streisand
Lepaskan: 2025-01-03 07:37:39
asal
572 orang telah melayarinya

Mukadimah

Artikel ini akan membimbing anda melalui pemahaman Log-Structured Merge-Tree (LSM-Tree), termasuk konsep teras dan strukturnya. Pada akhirnya, anda akan dapat membina enjin storan anda sendiri berdasarkan LSM-Tree dari awal.

Apakah LSM-Tree?

Konsep Asas

Pokok Gabungan Berstruktur Log (LSM-Tree) ialah struktur data yang dioptimumkan untuk operasi tulis throughput tinggi. Ia digunakan secara meluas dalam pangkalan data dan sistem storan, seperti Cassandra, RocksDB dan LevelDB.

Idea utama LSM-Tree ialah menulis operasi terlebih dahulu ke dalam struktur data dalam memori (biasanya struktur tersusun seperti senarai langkau atau pepohon AVL). Kemudian, tulisan ini dikumpulkan dan ditulis secara berurutan ke cakera sebagai SSTables, dengan itu meminimumkan I/O rawak.

Struktur Asas

Building an LSM-Tree Storage Engine from Scratch

LSM-Tree terbahagi kepada dua komponen utama:

  • Storan Dalam Memori
    • Struktur teras dalam ingatan ialah Memtable.
    • Semua operasi tulis (cth., tetapkan, padam) mula-mula pergi ke Memtable, yang memasukkan operasi ini ke dalam struktur data tersusun (cth., pepohon tertib dalam rajah).
    • Setelah Memtable mencapai ambang saiz tertentu, ia dibuang ke cakera sebagai SSTable (ditulis secara berurutan).
    • Operasi tulis baharu diteruskan pada Memtable baharu.
  • Storan Cakera
    • Storan cakera melibatkan fail WAL dan SSTable.
    • WAL (Write-Ahead Log) memastikan bahawa penulisan terbaru (disimpan dalam Memtable tetapi belum diteruskan ke cakera) tidak hilang sekiranya berlaku ranap pangkalan data. Setiap tulis pada Memtable dilampirkan pada WAL. Setelah memulakan semula pangkalan data, entri daripada WAL boleh dimainkan semula untuk memulihkan Memtable kepada keadaan pra ranapnya.
    • SSTable (Jadual Rentetan Diisih) ialah format storan data yang menyimpan satu siri pasangan nilai kunci tersusun.
    • Apabila Memtable mencapai ambang saiznya, ia menghasilkan SSTable baharu dan mengekalkannya ke cakera. Memandangkan Memtable bergantung pada struktur data tersusun dalam memori, tiada pengisihan tambahan diperlukan semasa membina SSTable.
    • SSTables pada cakera disusun ke dalam pelbagai peringkat. SSTable yang baru disiram disimpan dalam Tahap 0. Semasa fasa pemadatan seterusnya, SSTables dalam L0 digabungkan menjadi Tahap 1 dan tahap yang lebih tinggi.
    • Apabila saiz tahap melebihi ambang, proses pemadatan SSTable akan dicetuskan. Semasa pemadatan, SSTables dalam tahap semasa digabungkan ke tahap yang lebih tinggi, menghasilkan fail yang lebih besar dan lebih teratur. Ini mengurangkan pemecahan dan meningkatkan kecekapan pertanyaan.

Lazimnya, struktur SSTable merangkumi lebih daripada sekadar siri pasangan nilai kunci tersusun (blok data). Ia juga mengandungi blok indeks, blok metadata dan komponen lain. Butiran ini akan dibincangkan secara mendalam semasa bahagian pelaksanaan.

Menulis Data

Menulis data melibatkan penambahan pasangan nilai kunci baharu atau mengemas kini pasangan sedia ada. Kemas kini menimpa pasangan nilai kunci lama, yang kemudiannya dialih keluar semasa proses pemadatan.

Apabila data ditulis, ia mula-mula pergi ke Memtable, di mana pasangan nilai kunci ditambahkan pada struktur data tertib dalam memori. Pada masa yang sama, operasi tulis dilog dalam WAL dan diteruskan ke cakera untuk mengelakkan kehilangan data sekiranya berlaku ranap pangkalan data.

Metable mempunyai ambang yang ditentukan (biasanya berdasarkan saiz). Apabila Memtable melebihi ambang ini, ia ditukar kepada mod baca sahaja dan ditukar kepada SSTable baharu, yang kemudiannya dikekalkan ke Tahap 0 pada cakera.

Setelah Memtable disiram sebagai SSTable, fail WAL yang sepadan boleh dipadamkan dengan selamat. Operasi tulis seterusnya akan diteruskan pada Memtable baharu (dan WAL baharu).

Memadam Data

Dalam LSM-Tree, data tidak segera dialih keluar. Sebaliknya, pemadaman dikendalikan menggunakan mekanisme yang dipanggil batu nisan (serupa dengan pemadaman lembut). Apabila pasangan nilai kunci dipadamkan, entri baharu yang ditandakan dengan "batu nisan" ditulis, menunjukkan pemadaman pasangan nilai kunci yang sepadan. Penyingkiran sebenar berlaku semasa proses pemadatan.

Pemadaman berasaskan batu nisan ini memastikan sifat tambah sahaja LSM-Tree, mengelakkan I/O rawak dan mengekalkan penulisan berurutan pada cakera.

Menyoal Data

Proses menanyakan data bermula dengan carian dalam Memtable. Jika pasangan nilai kunci ditemui, ia dikembalikan kepada klien. Jika pasangan nilai kunci bertanda batu nisan ditemui, ini menunjukkan bahawa data yang diminta telah dipadamkan dan maklumat ini juga dikembalikan. Jika kunci tidak ditemui dalam Memtable, pertanyaan diteruskan untuk mencari SSTables dari Tahap 0 hingga Tahap N.

Memandangkan data pertanyaan mungkin melibatkan pencarian berbilang fail SSTable dan boleh membawa kepada I/O cakera rawak, LSM-Tree biasanya lebih sesuai untuk beban kerja berat tulis berbanding beban kerja intensif baca.

Satu pengoptimuman biasa untuk prestasi pertanyaan ialah penggunaan penapis Bloom. Penapis Bloom boleh menentukan dengan cepat sama ada pasangan nilai kunci wujud dalam SSTable tertentu, mengurangkan I/O cakera yang tidak diperlukan. Selain itu, sifat disusun SSTables membolehkan algoritma carian yang cekap, seperti carian binari, digunakan untuk carian yang lebih pantas.

Pemadatan Data

Di sini, kami memperkenalkan Strategi Pemadatan Bertingkat, yang digunakan oleh LevelDB dan RocksDB.

Satu lagi strategi biasa ialah Strategi Pemadatan Berperingkat Saiz, di mana SSTable yang lebih baharu dan lebih kecil digabungkan secara berturut-turut menjadi SSTable yang lebih lama dan lebih besar.

Seperti yang dinyatakan sebelum ini, SSTable menyimpan satu siri entri yang diisih mengikut kekunci. Dalam Strategi Pemadatan Bertingkat, SSTables disusun ke dalam berbilang peringkat (Tahap 0 hingga Tahap N).

Dalam Tahap 0, SSTables boleh mempunyai julat kekunci yang bertindih, kerana ia terus dipadamkan daripada Memtable. Walau bagaimanapun, dalam Tahap 1 hingga N, SSTables dalam tahap yang sama tidak mempunyai julat kekunci bertindih, walaupun julat kekunci bertindih dibenarkan antara SSTable dalam tahap yang berbeza.

Contoh ilustrasi (walaupun tidak sepenuhnya tepat) ditunjukkan di bawah. Dalam Tahap 0, julat utama SSTable pertama dan kedua bertindih, manakala dalam Tahap 1 dan Tahap 2, SSTables dalam setiap tahap mempunyai julat kunci terputus . Walau bagaimanapun, SSTable antara tahap yang berbeza (cth., Tahap 0 dan Tahap 1, atau Tahap 1 dan Tahap 2) mungkin mempunyai julat utama yang bertindih.

Building an LSM-Tree Storage Engine from Scratch

Sekarang mari kita terokai cara Strategi Pemadatan Bertingkat mengekalkan struktur organisasi ini.

Memandangkan Tahap 0 ialah kes khas, perbincangan strategi pemadatan dibahagikan kepada dua bahagian:

  • Tahap 0 hingga Tahap 1 Memandangkan Tahap 0 membenarkan kekunci bertindih antara SSTables, pemadatan bermula dengan memilih SSTable daripada Tahap 0, bersama-sama dengan semua SSTable lain dalam Tahap 0 yang mempunyai julat kunci yang bertindih dengannya. Seterusnya, semua SSTable dalam Tahap 1 dengan julat kunci bertindih dipilih. SSTable yang dipilih ini digabungkan dan dipadatkan menjadi satu SSTable baharu, yang kemudiannya dimasukkan ke Tahap 1. Semua SSTables lama yang terlibat dalam proses pemadatan dipadamkan.
  • Tahap N hingga Tahap N 1 (N > 0) Dari Tahap 1 dan seterusnya, SSTables dalam tahap yang sama tidak mempunyai julat kunci yang bertindih. Semasa pemadatan, SSTable dipilih daripada Tahap N, dan semua SSTable dalam Tahap N 1 dengan julat kekunci bertindih juga dipilih. SSTables ini digabungkan dan dipadatkan menjadi satu atau lebih SSTables baharu, yang dimasukkan ke dalam Level N 1, manakala SSTables lama dipadamkan.

Perbezaan utama antara pemadatan Tahap 0 hingga Tahap 1 dan pemadatan Tahap N hingga Tahap N 1 (N > 0) terletak pada pemilihan SSTables pada tahap yang lebih rendah (Tahap 0 atau Tahap N).

Proses pemadatan dan penggabungan berbilang SST boleh digambarkan di bawah. Semasa penggabungan, hanya nilai terkini untuk setiap kunci dikekalkan. Jika nilai terkini mempunyai penanda "batu nisan", kunci dipadamkan. Dalam pelaksanaan, kami menggunakan algoritma gabungan k-way untuk melaksanakan proses ini.

Building an LSM-Tree Storage Engine from Scratch

Adalah penting untuk ambil perhatian bahawa perihalan proses pemadatan di atas hanya memberikan gambaran keseluruhan peringkat tinggi. Banyak butiran perlu ditangani semasa pelaksanaan sebenar.

Sebagai contoh, dalam LevelDB, apabila membina SSTable baharu untuk Tahap N 1 semasa pemadatan, jika SSTable baharu bertindih dengan lebih daripada 10 SSTable dalam Tahap N 2, proses bertukar kepada membina SSTable lain. Ini mengehadkan saiz data yang terlibat dalam satu pemadatan.

Perlaksanaan

Berdasarkan gambaran keseluruhan LSM-Tree di atas, saya percaya anda kini mempunyai pemahaman asas tentang LSM-Tree dan beberapa idea tentang pelaksanaannya. Seterusnya, kami akan membina enjin storan berasaskan LSM-Tree dari awal. Di bawah, kami akan memperkenalkan hanya kod teras; untuk kod lengkap, sila rujuk https://github.com/B1NARY-GR0UP/originium.

Kami akan memecahkan pelaksanaan LSM-Tree kepada komponen teras berikut dan melaksanakannya satu demi satu:

  • Langkau Senarai
  • WAL
  • Memtable
  • SSTable
  • Gabungan K-Way
  • Penapis Bloom
  • Pemadatan Bertahap

Langkau Senarai

Dalam proses memperkenalkan penulisan data, kami menyebut bahawa LSM-Tree mula-mula menulis data ke dalam struktur data tertib dalam memori. Beberapa struktur data tertib biasa dan kerumitan masa operasinya adalah seperti berikut:

Data Structure Insert Delete Search Traverse
Skip List O(log⁡n) O(log⁡n) O(log⁡n) O(n)
AVL Tree O(log⁡n) O(log⁡n) O(log⁡n) O(n)
Red-Black Tree O(log⁡n) O(log⁡n) O(log⁡n) O(n)

Kami memilih Senarai Langkau untuk dua sebab utama: ia lebih mudah untuk dilaksanakan dan diselenggara (prinsip KISS), dan struktur senarai terpaut asas memudahkan traversal berjujukan, menjadikannya lebih mudah untuk mengekalkan data dalam memori ke cakera.

Struktur Teras

Pelaksanaan lengkap Senarai Langkau tersedia di https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/skiplist/skiplist.go .

Senarai Langkau terdiri daripada senarai terpaut asas dan berbilang peringkat indeks yang dibina di atasnya. Untuk set data yang besar, lapisan indeks memendekkan laluan carian dengan ketara.

Dalam pelaksanaan kami, struktur teras Senarai Langkau ditakrifkan seperti berikut:

type SkipList struct {
    maxLevel int
    p        float64
    level    int
    rand     *rand.Rand
    size     int
    head     *Element
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
  • maxLevel: Bilangan maksimum tahap dalam Senarai Langkau (senarai terpaut asas mempunyai satu tahap).
  • peringkat: Bilangan peringkat semasa dalam Senarai Langkau.
  • p: Kebarangkalian nod dinaikkan ke tahap yang lebih tinggi. Contohnya, jika p = 0.5, senarai terpaut dengan 10 nod pada tahap asas akan mempunyai kira-kira 5 nod dalam tahap indeks seterusnya.
  • rand: Penjana nombor rawak yang digunakan untuk membandingkan dengan p.
  • saiz: Bilangan pasangan nilai kunci yang disimpan dalam Senarai Langkau, digunakan untuk menentukan sama ada Memtable melebihi ambang saiznya.
  • kepala: Nod kepala, yang memegang rujukan kepada nod pertama dalam setiap peringkat.

Struktur elemen yang disimpan dalam Senarai Langkau ditakrifkan seperti berikut:

type Element struct {
    types.Entry
    next []*Element
}

// https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/types/entry.go
type Entry struct {
    Key       string
    Value     []byte
    Tombstone bool  
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
  • jenis.Entri mewakili pasangan nilai kunci dalam enjin storan, termasuk kunci, nilai dan bendera batu nisan untuk dipadamkan.

  • seterusnya: Mengandungi penunjuk ke elemen seterusnya pada setiap peringkat.

Struktur ini mungkin kelihatan abstrak, jadi mari kita gambarkan dengan contoh:

Level 3:       3 ----------- 9 ----------- 21 --------- 26
Level 2:       3 ----- 6 ---- 9 ------ 19 -- 21 ---- 25 -- 26
Level 1:       3 -- 6 -- 7 -- 9 -- 12 -- 19 -- 21 -- 25 -- 26

next of head [ ->3, ->3, ->3 ]
next of Element 3 [ ->6, ->6, ->9 ]
next of Element 6 [ ->7, ->9 ]
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dalam Senarai Langkau tiga peringkat ini, penunjuk seterusnya nod kepala merujuk nod pertama pada setiap peringkat. Elemen 3 dan 6 menyimpan elemen seterusnya untuk setiap tahapnya.

Sebagai contoh, jika kita ingin mencari nod seterusnya bagi elemen 19 pada Tahap 2, kita menggunakan e19.next[2-1].

Tetapkan

func (s *SkipList) Set(entry types.Entry)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

LSM-Tree menggunakan batu nisan untuk melakukan pemadaman, jadi kami tidak memerlukan kaedah Padam dalam pelaksanaan senarai langkau. Untuk memadamkan elemen, hanya tetapkan Batu Nisan entri kepada benar. Oleh itu, kaedah Set mengendalikan memasukkan pasangan nilai kunci baharu, mengemas kini yang sedia ada dan memadamkan elemen.

Mari kita terokai pelaksanaan kaedah Set. Dengan merentasi nod dalam setiap tahap dari yang tertinggi, elemen terakhir yang lebih kecil daripada kunci yang akan ditetapkan disimpan dalam kepingan kemas kini.

type SkipList struct {
    maxLevel int
    p        float64
    level    int
    rand     *rand.Rand
    size     int
    head     *Element
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Pada penghujung traversal ini, curr menghala ke elemen terakhir yang lebih kecil daripada kekunci yang akan ditetapkan dalam senarai terpaut peringkat bawah. Jadi, kita hanya perlu menyemak sama ada elemen curr seterusnya sama dengan kunci yang ingin kita tetapkan. Jika ia sepadan, elemen telah dimasukkan; kami mengemas kini elemen sedia ada dan mengembalikan.

type Element struct {
    types.Entry
    next []*Element
}

// https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/types/entry.go
type Entry struct {
    Key       string
    Value     []byte
    Tombstone bool  
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Jika elemen tidak ditemui, ia dimasukkan sebagai elemen baharu. Menggunakan randomLevel, kami mengira tahap indeks elemen ini. Jika ia melebihi bilangan tahap semasa dalam senarai langkau, kami menambah nod kepala pada kepingan kemas kini dan mengemas kini s.level kepada kiraan tahap baharu.

Level 3:       3 ----------- 9 ----------- 21 --------- 26
Level 2:       3 ----- 6 ---- 9 ------ 19 -- 21 ---- 25 -- 26
Level 1:       3 -- 6 -- 7 -- 9 -- 12 -- 19 -- 21 -- 25 -- 26

next of head [ ->3, ->3, ->3 ]
next of Element 3 [ ->6, ->6, ->9 ]
next of Element 6 [ ->7, ->9 ]
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Seterusnya, bina elemen yang hendak disisipkan dan penunjuk seterusnya bagi setiap peringkat dikemas kini untuk melengkapkan sisipan.

func (s *SkipList) Set(entry types.Entry)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dapatkan

Senarai langkau boleh melakukan operasi carian pantas dengan bergantung pada berbilang lapisan indeks. Gelung bersarang dalam pelaksanaan mewakili operasi carian berasaskan indeks. Jika elemen yang sepadan akhirnya ditemui dalam senarai terpaut peringkat bawah, ia akan dikembalikan.

curr := s.head
update := make([]*Element, s.maxLevel)

for i := s.maxLevel - 1; i >= 0; i-- {
    for curr.next[i] != nil && curr.next[i].Key < entry.Key {
        curr = curr.next[i]
    }
    update[i] = curr
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Semua

Salah satu sebab kami memilih senarai langkau adalah laluan berurutan yang mudah, yang dimungkinkan dengan hanya melintasi senarai terpaut peringkat bawah.

// update entry
if curr.next[0] != nil && curr.next[0].Key == entry.Key {
    s.size += len(entry.Value) - len(curr.next[0].Value)

    // update value and tombstone
    curr.next[0].Value = entry.Value
    curr.next[0].Tombstone = entry.Tombstone
    return
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

WAL

Pelaksanaan lengkap WAL boleh didapati di https://github.com/B1NARY-GR0UP/originium/blob/main/wal/wal.go.

Seperti yang dinyatakan sebelum ini, tujuan WAL (Write-Ahead Logging) adalah untuk mengelakkan kehilangan data dalam Memtable yang disebabkan oleh ranap pangkalan data. Oleh itu, WAL perlu merekodkan operasi pada Memtable dan memulihkan Memtable daripada fail WAL apabila pangkalan data dimulakan semula.

Struktur Teras

Struktur teras WAL adalah seperti berikut, di mana fd menyimpan deskriptor fail fail WAL:

// add entry
level := s.randomLevel()

if level > s.level {
    for i := s.level; i < level; i++ {
        update[i] = s.head
    }
    s.level = level
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

tulis

Memandangkan kita perlu merekodkan operasi pada Memtable, ini pada asasnya melibatkan penulisan setiap operasi (Tetapkan, Padam) sebagai Kemasukan ke dalam WAL. Takrif kaedah Tulis adalah seperti berikut:

e := &Element{
    Entry: types.Entry{
        Key:       entry.Key,
        Value:     entry.Value,
        Tombstone: entry.Tombstone,
    },
    next: make([]*Element, level),
}

for i := range level {
    e.next[i] = update[i].next[i]
    update[i].next[i] = e
}
s.size += len(entry.Key) + len(entry.Value) + int(unsafe.Sizeof(entry.Tombstone)) + len(e.next)*int(unsafe.Sizeof((*Element)(nil)))
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Apabila menulis entri ini pada fail, kami perlu menyeragamkan format fail WAL. Format yang kami gunakan di sini ialah data panjang. Mula-mula, kami menyerikan Entri, kemudian mengira panjang data bersiri, dan akhirnya menulis panjang dan data bersiri secara berurutan ke dalam fail WAL.

Kod teras adalah seperti berikut:

func (s *SkipList) Get(key types.Key) (types.Entry, bool) {
    curr := s.head

    for i := s.maxLevel - 1; i >= 0; i-- {
        for curr.next[i] != nil && curr.next[i].Key < key {
            curr = curr.next[i]
        }
    }

    curr = curr.next[0]

    if curr != nil && curr.Key == key {
        return types.Entry{
            Key:       curr.Key,
            Value:     curr.Value,
            Tombstone: curr.Tombstone,
        }, true
    }
    return types.Entry{}, false
}
Salin selepas log masuk
Salin selepas log masuk

Baca

Memandangkan kami menggunakan format fail WAL data panjang, semasa membaca, kami mula-mula membaca 8 bait (int64) untuk mendapatkan panjang data, dan kemudian membaca data berdasarkan panjang ini dan nyahserikannya untuk mendapatkan semula Entri.

Kod teras adalah seperti berikut:

type SkipList struct {
    maxLevel int
    p        float64
    level    int
    rand     *rand.Rand
    size     int
    head     *Element
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Memtable

Pelaksanaan lengkap Memtable boleh didapati di https://github.com/B1NARY-GR0UP/originium/blob/main/memtable.go.

Metable bertanggungjawab untuk menulis operasi klien ke dalam senarai langkau dan merekodkannya dalam WAL. Ia juga boleh memulihkan data daripada WAL apabila pangkalan data bermula.

Struktur Teras

Struktur teras Memtable adalah seperti berikut, yang merangkumi dua komponen utama skiplist dan wal:

type Element struct {
    types.Entry
    next []*Element
}

// https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/types/entry.go
type Entry struct {
    Key       string
    Value     []byte
    Tombstone bool  
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Tetapkan

Apabila melakukan operasi Set, kedua-dua senarai langkau dan WAL perlu dikemas kini secara serentak.

Level 3:       3 ----------- 9 ----------- 21 --------- 26
Level 2:       3 ----- 6 ---- 9 ------ 19 -- 21 ---- 25 -- 26
Level 1:       3 -- 6 -- 7 -- 9 -- 12 -- 19 -- 21 -- 25 -- 26

next of head [ ->3, ->3, ->3 ]
next of Element 3 [ ->6, ->6, ->9 ]
next of Element 6 [ ->7, ->9 ]
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dapatkan

Untuk mendapatkan semula nilai, hanya kembalikan hasil operasi Dapatkan senarai langkau.

func (s *SkipList) Set(entry types.Entry)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Pulih

Memulihkan Memtable daripada fail WAL melibatkan pembacaan fail WAL dahulu, kemudian secara berurutan menggunakan rekod Kemasukan daripada fail WAL ke Memtable, dan akhirnya memadamkan fail WAL yang dipulihkan.

Dapatkan semula senarai fail WAL:

curr := s.head
update := make([]*Element, s.maxLevel)

for i := s.maxLevel - 1; i >= 0; i-- {
    for curr.next[i] != nil && curr.next[i].Key < entry.Key {
        curr = curr.next[i]
    }
    update[i] = curr
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Baca WAL dan dapatkan semula Memtable:

// update entry
if curr.next[0] != nil && curr.next[0].Key == entry.Key {
    s.size += len(entry.Value) - len(curr.next[0].Value)

    // update value and tombstone
    curr.next[0].Value = entry.Value
    curr.next[0].Tombstone = entry.Tombstone
    return
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

SSTable

LevelDB SSTable

Dalam pengenalan sebelumnya, kami hanya menyebut bahawa "SSTable (Sorted String Table) ialah format storan data yang mengekalkan satu siri pasangan nilai kunci tersusun." Di sini, kami akan memberikan penjelasan yang lebih terperinci tentang struktur SSTable.

Dalam LevelDB, SSTable terdiri daripada berbilang blok dengan tujuan yang berbeza. Gambarajah skematik ditunjukkan di bawah:

Building an LSM-Tree Storage Engine from Scratch

  • Blok Data: Menyimpan jujukan pasangan nilai kunci tersusun.
  • Meta Block: Termasuk dua jenis: penapis dan statistik. Jenis penapis menyimpan data untuk penapis Bloom, manakala jenis statistik menyimpan maklumat statistik tentang Blok Data.
  • Blok MetaIndex: Menyimpan maklumat indeks untuk Blok Meta.
  • Blok Indeks: Menyimpan maklumat indeks untuk Blok Data.
  • Footer: Panjang tetap, ia menyimpan maklumat indeks Blok MetaIndex dan Blok Indeks, serta nombor ajaib.

Maklumat indeks pada asasnya ialah struktur penunjuk yang dipanggil BlockHandle, yang merangkumi dua atribut: offset dan saiz, digunakan untuk mencari Blok yang sepadan.

SSTable kami

Dalam pelaksanaan SSTable kami, kami telah memudahkan struktur LevelDB SSTable. Gambarajah skematik ditunjukkan di bawah:

Building an LSM-Tree Storage Engine from Scratch

  • Blok Data: Menyimpan jujukan pasangan nilai kunci tersusun.
  • Meta Block: Menyimpan beberapa metadata untuk SSTable.
  • Blok Indeks: Menyimpan maklumat indeks untuk Blok Data.
  • Footer: Panjang tetap, ia menyimpan maklumat indeks Blok Meta dan Blok Indeks.

Pelaksanaan lengkap SSTable boleh didapati di https://github.com/B1NARY-GR0UP/originium/tree/main/sstable.

Blok Data

Struktur Blok Data ditakrifkan seperti berikut, menyimpan urutan entri yang tersusun.

type SkipList struct {
    maxLevel int
    p        float64
    level    int
    rand     *rand.Rand
    size     int
    head     *Element
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kami melaksanakan tiga kaedah utama untuk Blok Data:

  • Enkod: Mengekodkan Blok Data ke dalam data binari.
type Element struct {
    types.Entry
    next []*Element
}

// https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/types/entry.go
type Entry struct {
    Key       string
    Value     []byte
    Tombstone bool  
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kami menggunakan mampatan awalan untuk mengekod jujukan nilai kunci. Dalam penimbal, kami menulis secara berurutan panjang awalan biasa, panjang akhiran, akhiran itu sendiri, panjang nilai, nilai dan penanda "batu nisan".

Level 3:       3 ----------- 9 ----------- 21 --------- 26
Level 2:       3 ----- 6 ---- 9 ------ 19 -- 21 ---- 25 -- 26
Level 1:       3 -- 6 -- 7 -- 9 -- 12 -- 19 -- 21 -- 25 -- 26

next of head [ ->3, ->3, ->3 ]
next of Element 3 [ ->6, ->6, ->9 ]
next of Element 6 [ ->7, ->9 ]
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Akhir sekali, kami memampatkan data menggunakan s2.

S2 ialah sambungan berprestasi tinggi bagi algoritma mampatan Snappy.

func (s *SkipList) Set(entry types.Entry)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
  • Nyahkod: Menyahkod data binari ke dalam Blok Data.
curr := s.head
update := make([]*Element, s.maxLevel)

for i := s.maxLevel - 1; i >= 0; i-- {
    for curr.next[i] != nil && curr.next[i].Key < entry.Key {
        curr = curr.next[i]
    }
    update[i] = curr
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Semasa penyahkodan, proses hanya diterbalikkan. Pasangan nilai kunci penuh dibina semula menggunakan awalan dan akhiran.

// update entry
if curr.next[0] != nil && curr.next[0].Key == entry.Key {
    s.size += len(entry.Value) - len(curr.next[0].Value)

    // update value and tombstone
    curr.next[0].Value = entry.Value
    curr.next[0].Tombstone = entry.Tombstone
    return
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
  • Cari: Mencari pasangan nilai kunci menggunakan carian binari.
// add entry
level := s.randomLevel()

if level > s.level {
    for i := s.level; i < level; i++ {
        update[i] = s.head
    }
    s.level = level
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Blok Indeks

Struktur Blok Indeks ditakrifkan seperti berikut. Ia menyimpan kunci pertama dan terakhir setiap Blok Data, bersama-sama dengan BlockHandle bagi Blok Data yang sepadan.

e := &Element{
    Entry: types.Entry{
        Key:       entry.Key,
        Value:     entry.Value,
        Tombstone: entry.Tombstone,
    },
    next: make([]*Element, level),
}

for i := range level {
    e.next[i] = update[i].next[i]
    update[i].next[i] = e
}
s.size += len(entry.Key) + len(entry.Value) + int(unsafe.Sizeof(entry.Tombstone)) + len(e.next)*int(unsafe.Sizeof((*Element)(nil)))
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Begitu juga, Blok Indeks melaksanakan tiga kaedah utama: Enkod, Nyahkod dan Cari. Idea pelaksanaan untuk kaedah Encode dan Decode pada asasnya adalah sama, jadi kami akan menumpukan pada kaedah Cari.

Kaedah Carian Blok Data direka bentuk untuk mencari pasangan nilai kunci tertentu dalam jujukan nilai kunci tersusun yang disimpan dalam Blok Data tunggal. Sebaliknya, kaedah Carian Blok Indeks digunakan untuk mencari Blok Data yang mengandungi kunci yang diberikan dalam keseluruhan SSTable.

func (s *SkipList) Get(key types.Key) (types.Entry, bool) {
    curr := s.head

    for i := s.maxLevel - 1; i >= 0; i-- {
        for curr.next[i] != nil && curr.next[i].Key < key {
            curr = curr.next[i]
        }
    }

    curr = curr.next[0]

    if curr != nil && curr.Key == key {
        return types.Entry{
            Key:       curr.Key,
            Value:     curr.Value,
            Tombstone: curr.Tombstone,
        }, true
    }
    return types.Entry{}, false
}
Salin selepas log masuk
Salin selepas log masuk

Meta Block Dan Footer

func (s *SkipList) All() []types.Entry {
    var all []types.Entry

    for curr := s.head.next[0]; curr != nil; curr = curr.next[0] {
        all = append(all, types.Entry{
            Key:       curr.Key,
            Value:     curr.Value,
            Tombstone: curr.Tombstone,
        })
    }

    return all
}
Salin selepas log masuk

Pelaksanaan kedua-dua Blok ini agak mudah, dengan kedua-duanya hanya memerlukan kaedah Pengekodan dan Nyahkod.

bina

Selepas memperkenalkan semua Blok dalam SSTable kami, membina SSTable hanya melibatkan membina setiap Blok langkah demi langkah berdasarkan pasangan nilai kunci. Akhirnya, indeks dalam memori dan SSTable yang dikodkan dikembalikan.

type SkipList struct {
    maxLevel int
    p        float64
    level    int
    rand     *rand.Rand
    size     int
    head     *Element
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Gabungan K-Way

Pelaksanaan lengkap K-Way Merge boleh didapati di https://github.com/B1NARY-GR0UP/originium/tree/main/pkg/kway.

Dalam bahagian konseptual, kami menggambarkan proses memampatkan dan menggabungkan berbilang SSTable melalui rajah. Proses ini dicapai menggunakan algoritma k-way merge.

Algoritma cantuman k-way ialah kaedah untuk menggabungkan k jujukan yang diisih ke dalam satu jujukan tersusun, dengan kerumitan masa O(knlogk).

Satu pelaksanaan algoritma ini menggunakan timbunan min sebagai struktur tambahan:

  • Masukkan elemen pertama setiap jujukan ke dalam timbunan.
  • Pancarkan nilai terkecil daripada timbunan dan tambahkannya pada set hasil. Jika jujukan elemen yang timbul masih mempunyai lebih banyak elemen, masukkan elemen seterusnya daripada jujukan itu ke dalam timbunan.
  • Ulang proses ini sehingga semua elemen daripada semua jujukan digabungkan.

Timbunan

Pustaka standard menyediakan pelaksanaan timbunan dalam bekas/timbunan. Dengan melaksanakan timbunan.Antaramuka, kita boleh membina timbunan min.

  • Pertama, tentukan struktur asas timbunan min. Kepingan digunakan untuk menyimpan unsur-unsur. Setiap elemen termasuk bukan sahaja Entri tetapi juga LI untuk menunjukkan urutan yang diisih unsur ini berasal.
type Element struct {
    types.Entry
    next []*Element
}

// https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/types/entry.go
type Entry struct {
    Key       string
    Value     []byte
    Tombstone bool  
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
  • Laksanakan kaedah isihan.Antaramuka untuk mengisih unsur dalam timbunan. Perhatian khusus diperlukan untuk kaedah Less: dengan membandingkan LI unsur-unsur, kami memastikan bahawa apabila unsur mempunyai kunci yang sama, urutan dari jujukan terdahulu akan dipesan terlebih dahulu. Ini memudahkan penduaan apabila menggabungkan elemen ke dalam set hasil. Keperluan ini juga bermakna bahawa urutan yang diisih hendaklah disusun mengikut tertib daripada yang paling lama kepada yang terbaru apabila menggunakan algoritma cantum k-way.
Level 3:       3 ----------- 9 ----------- 21 --------- 26
Level 2:       3 ----- 6 ---- 9 ------ 19 -- 21 ---- 25 -- 26
Level 1:       3 -- 6 -- 7 -- 9 -- 12 -- 19 -- 21 -- 25 -- 26

next of head [ ->3, ->3, ->3 ]
next of Element 3 [ ->6, ->6, ->9 ]
next of Element 6 [ ->7, ->9 ]
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
  • Akhir sekali, laksanakan kaedah Push dan Pop. Tolak menambahkan elemen pada hujung kepingan, manakala Pop mengalih keluar elemen terakhir daripada kepingan.
func (s *SkipList) Set(entry types.Entry)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Bercantum

Takrifan fungsi kaedah Gabung:

curr := s.head
update := make([]*Element, s.maxLevel)

for i := s.maxLevel - 1; i >= 0; i-- {
    for curr.next[i] != nil && curr.next[i].Key < entry.Key {
        curr = curr.next[i]
    }
    update[i] = curr
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Mengikuti proses algoritma cantuman k-way.

  • Mula-mula, masukkan elemen pertama bagi setiap urutan yang diisih ke dalam timbunan min.
type SkipList struct {
    maxLevel int
    p        float64
    level    int
    rand     *rand.Rand
    size     int
    head     *Element
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
  • Letuskan secara berulang elemen daripada timbunan min dan tambahkannya pada baris gilir hasil. Jika jujukan elemen yang muncul masih mempunyai lebih banyak elemen, masukkan elemen seterusnya daripada jujukan itu ke dalam timbunan. Di sini, peta digunakan dan bukannya urutan hasil. Peta secara automatik mengendalikan penyahduplikasian, dengan kekunci yang lebih baharu sentiasa menggantikan yang lama.
type Element struct {
    types.Entry
    next []*Element
}

// https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/types/entry.go
type Entry struct {
    Key       string
    Value     []byte
    Tombstone bool  
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Akhir sekali, rentas peta untuk menambahkan elemen pada baris gilir hasil, mengalih keluar sebarang pasangan nilai kunci yang ditandakan sebagai "batu nisan." Memandangkan peta tidak tersusun, baris gilir hasil perlu diisih sebelum dikembalikan.

Level 3:       3 ----------- 9 ----------- 21 --------- 26
Level 2:       3 ----- 6 ---- 9 ------ 19 -- 21 ---- 25 -- 26
Level 1:       3 -- 6 -- 7 -- 9 -- 12 -- 19 -- 21 -- 25 -- 26

next of head [ ->3, ->3, ->3 ]
next of Element 3 [ ->6, ->6, ->9 ]
next of Element 6 [ ->7, ->9 ]
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Penapis Bloom

Pelaksanaan lengkap Penapis Bloom boleh didapati di https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/filter/filter.go.

Penapis Bloom ialah struktur data yang memeriksa dengan cekap sama ada sesuatu elemen ialah ahli set.

  • Ia menggunakan tatasusunan bit dan berbilang fungsi cincang.
  • Apabila menambahkan elemen, elemen dicincang menggunakan berbilang fungsi cincang, yang memetakannya ke kedudukan berbeza dalam tatasusunan bit, menetapkan kedudukan tersebut kepada 1.
  • Semasa pertanyaan, jika semua kedudukan yang dipetakan oleh fungsi cincang ialah 1, elemen itu mungkin wujud. Jika mana-mana kedudukan adalah 0, elemen itu pasti tidak wujud.

Kerumitan masa untuk kedua-dua operasi sisipan dan pertanyaan ialah O(k), dengan k ialah bilangan fungsi cincang. Positif palsu mungkin berlaku (Penapis Bloom meramalkan bahawa elemen berada dalam set, tetapi tidak), tetapi negatif palsu tidak boleh berlaku (Penapis Bloom meramalkan bahawa elemen tidak dalam set, tetapi sebenarnya ada).

Positif Benar (TP): Sistem meramalkan peristiwa sebagai "positif", dan ia sememangnya positif.
Positif Palsu (FP): Sistem meramalkan peristiwa sebagai "positif", tetapi ia sebenarnya negatif.
True Negatif (TN): Sistem meramalkan peristiwa sebagai "negatif", dan ia sememangnya negatif.
Negatif Palsu (FN): Sistem meramalkan peristiwa sebagai "negatif", tetapi ia sebenarnya positif.

Struktur Teras

Struktur teras Penapis Bloom termasuk tatasusunan bit (yang boleh dioptimumkan untuk menggunakan uint8) dan berbilang fungsi cincang.

func (s *SkipList) Set(entry types.Entry)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

baru

Kaedah untuk mencipta tika Penapis Bloom menerima dua parameter: n (bilangan unsur yang dijangkakan) dan p (kadar positif palsu yang dikehendaki).

Pertama, parameter disahkan. Kemudian, saiz tatasusunan bit (m) dan bilangan fungsi cincang (k) dikira menggunakan formula khusus. Akhirnya, tatasusunan bit dan fungsi cincang dimulakan berdasarkan m dan k.

type SkipList struct {
    maxLevel int
    p        float64
    level    int
    rand     *rand.Rand
    size     int
    head     *Element
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Tambah

Apabila menambah elemen, semua fungsi cincang digunakan untuk mengira nilai cincang untuk kunci. Nilai ini kemudiannya dipetakan kepada indeks dalam tatasusunan bit dan kedudukan yang sepadan ditetapkan kepada benar.

type Element struct {
    types.Entry
    next []*Element
}

// https://github.com/B1NARY-GR0UP/originium/blob/main/pkg/types/entry.go
type Entry struct {
    Key       string
    Value     []byte
    Tombstone bool  
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Mengandungi

Untuk menyemak sama ada kunci berada dalam set, fungsi cincang mengira nilai cincang dan memetakannya kepada indeks dalam tatasusunan bit. Jika mana-mana kedudukan ini tidak benar, elemen itu tiada dalam set dan palsu dikembalikan.

Level 3:       3 ----------- 9 ----------- 21 --------- 26
Level 2:       3 ----- 6 ---- 9 ------ 19 -- 21 ---- 25 -- 26
Level 1:       3 -- 6 -- 7 -- 9 -- 12 -- 19 -- 21 -- 25 -- 26

next of head [ ->3, ->3, ->3 ]
next of Element 3 [ ->6, ->6, ->9 ]
next of Element 6 [ ->7, ->9 ]
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Pemadatan Bertingkat

Pelaksanaan lengkap Pemadatan Bertingkat boleh didapati di https://github.com/B1NARY-GR0UP/originium/blob/main/level.go.

Selepas melaksanakan komponen seperti K-Way Merge dan Bloom Filter, kami boleh melengkapkan bahagian akhir pelaksanaan kami, proses pemadatan SSTable yang paling penting dalam enjin storan LSM-Tree. Pelaksanaan ini mengikut Strategi Pemadatan Bertingkat yang diterangkan dalam bahagian "Pemadatan Data".

Dalam Strategi Pemadatan Bertingkat, SSTables disusun ke dalam pelbagai peringkat (Tahap 0 - Tahap N). Kami memerlukan struktur untuk menyimpan maklumat ini dan mengurus SSTables merentas tahap yang berbeza.

Oleh itu, kami melaksanakan struktur yang dipanggil levelManager. Kami menggunakan []*list.List untuk menyimpan maklumat SSTable bagi setiap peringkat, di mana indeks hirisan sepadan dengan tahap. Setiap elemen dalam hirisan ialah senarai. Senarai, senarai berganda yang menyimpan maklumat tentang semua SSTable dalam tahap tertentu.

func (s *SkipList) Set(entry types.Entry)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

compactLN

Kaedah compactLN bertanggungjawab untuk pemadatan Tahap N hingga Tahap N 1 (N > 0). Ia memilih SSTable tertua daripada LN dan semua SSTables daripada LN 1 yang mempunyai julat kunci bertindih dengan SSTable ini.

curr := s.head
update := make([]*Element, s.maxLevel)

for i := s.maxLevel - 1; i >= 0; i-- {
    for curr.next[i] != nil && curr.next[i].Key < entry.Key {
        curr = curr.next[i]
    }
    update[i] = curr
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

SSTables yang dipilih diproses mengikut urutan daripada yang paling lama kepada yang terbaru. Pasangan nilai kunci daripada blok data mereka ditambahkan pada kepingan dua dimensi dan kemudian digabungkan menggunakan algoritma Gabungan K-Way.

// update entry
if curr.next[0] != nil && curr.next[0].Key == entry.Key {
    s.size += len(entry.Value) - len(curr.next[0].Value)

    // update value and tombstone
    curr.next[0].Value = entry.Value
    curr.next[0].Tombstone = entry.Tombstone
    return
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dengan pasangan nilai kunci yang digabungkan, kami membina Penapis Bloom dan SSTable baharu. Maklumat berkaitan tentang SSTable baharu dilampirkan pada penghujung Tahap N 1.

// add entry
level := s.randomLevel()

if level > s.level {
    for i := s.level; i < level; i++ {
        update[i] = s.head
    }
    s.level = level
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Akhir sekali, SSTables lama dipadamkan dan SSTable yang baru dibina ditulis pada cakera.

e := &Element{
    Entry: types.Entry{
        Key:       entry.Key,
        Value:     entry.Value,
        Tombstone: entry.Tombstone,
    },
    next: make([]*Element, level),
}

for i := range level {
    e.next[i] = update[i].next[i]
    update[i].next[i] = e
}
s.size += len(entry.Key) + len(entry.Value) + int(unsafe.Sizeof(entry.Tombstone)) + len(e.next)*int(unsafe.Sizeof((*Element)(nil)))
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kaedah compactL0 mengendalikan pemadatan Tahap 0 hingga Tahap 1. Tidak seperti compactLN, ia memilih bukan sahaja satu SSTable daripada L0 tetapi juga semua SSTables bertindih dalam L0. Proses selebihnya adalah sama dengan compactLN.

cari

Kaedah carian mencari pasangan nilai kunci yang sepadan merentas semua SSTables. Ia bermula dari L0 dan berulang melalui setiap peringkat sehingga LN. Dengan memanfaatkan Penapis Bloom dan struktur tersusun SSTables, SSTables yang tidak mengandungi pasangan nilai kunci yang diingini boleh dilangkau dengan cekap.

type SkipList struct {
    maxLevel int
    p        float64
    level    int
    rand     *rand.Rand
    size     int
    head     *Element
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

DB

Dengan ini, kami telah melaksanakan semua komponen teras enjin storan berasaskan LSM-Tree. Dengan memasang komponen ini seperti yang diterangkan dalam pengenalan LSM-Tree, kami boleh memuktamadkan antara muka pangkalan data.

  • Kod lengkap: https://github.com/B1NARY-GR0UP/originium/blob/main/db.go

  • Dokumentasi: https://github.com/B1NARY-GR0UP/originium?tab=readme-ov-file#usage

Ringkasan

Kami bermula dengan memahami LSM-Tree, membiasakan diri dengan komponen terasnya dan proses mengendalikan permintaan pelanggan. Akhirnya, kami membina enjin storan LSM-Tree kami sendiri dari awal.

Sudah tentu, pelaksanaan ini hanyalah prototaip. Enjin storan gred pengeluaran memerlukan pertimbangan lebih banyak butiran. ORIGINIUM akan terus menerima pengoptimuman dan penambahbaikan pada masa hadapan. Semoga artikel dan ORIGINIUM ini membantu mendalami pemahaman anda tentang LSM-Tree.

Itu menyimpulkan semua yang dibincangkan dalam artikel ini. Jika terdapat sebarang ralat atau soalan, sila hubungi melalui mesej peribadi atau tinggalkan ulasan. Terima kasih!

Rujukan

  • https://github.com/B1NARY-GR0UP/originium
  • https://www.cnblogs.com/whuanle/p/16297025.html
  • https://vonng.gitbook.io/vonng/part-i/ch3#stables-he-lsm-shu
  • https://github.com/google/leveldb/blob/main/doc/table_format.md

Atas ialah kandungan terperinci Membina Enjin Penyimpanan Pokok LSM daripada Gores. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan