minidb

package module
v0.0.0-...-21d887d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 12 Imported by: 0

README

minidb

連載「DBRE への道」第 3 部で作っている、教材用の自作データベース minidb です。本リポジトリは 第 18 回 時点のコードにあたります。

第 18 回時点では、次のところまでを実装しています。

  • ページ — 固定長 8KB(PostgreSQL に合わせた PageSize)のバイト列。
  • スロット付きページ — ページ内に可変長レコードを詰め、削除跡を compact で回収する。
  • ディスクマネージャ — ファイルをページ単位で読み書きし、ページ番号で位置を決める。
  • ヒープファイルと全件走査 — ページを並べてレコードを溜め、全ページ・全スロットを順にたどる Seq Scan 相当の走査を行う。
  • バッファプール — 限られたフレームにページを載せ、ヒットならディスクへ行かずに返す。clock で置換し、書き換えた(dirty な)ページは flush で書き戻す。
  • B+tree — キーで RecordID(行の住所、ctid に当たる)を引く索引。ノードを 1 ページとしてバッファプール越しに読み書きする。葉に(キー, RecordID)と次の葉への横ポインタ、内部ノードに仕切りキーと子ページ番号を持つ。挿入で分割(葉は先頭キーを写し上げ、内部は中央キーを押し上げ)、削除で兄弟からの借用・併合・木の縮約を行い、葉の横つながりをたどる順序付き走査もできる。
  • MVCC — トランザクション番号(XID)とスナップショットで「いつ何が見えるか」を決める多版同時実行制御。行はバージョンの列として持ち、更新は旧バージョンに削除印(xmax)を付けて新バージョンを足す。Begin 時点で進行中だったトランザクションや未確定の変更は見えず、スナップショット分離を満たす。どの現役トランザクションからも見えなくなった不要タプル(dead version)を数える DeadVersions も持ち、これが第 14 回の VACUUM が回収する対象になる。
  • クエリ処理 — Volcano モデルのイテレータ(開く・一件出す・閉じるの三つで揃えた処理段)を積み重ね、てっぺんから一件ずつ引く実行器。表を頭から読む SeqScan は第 12 回の可視性をくぐらせ、見える行だけを返す。条件で振り分ける Filter、要る列だけ残す Project を上に重ね、Run でてっぺんから尽きるまで引くと、SELECT name FROM users WHERE age > 30 が表を一周しながら一件ずつ流れる。途中に全件を抱えないのが基本で、PostgreSQL の EXPLAIN に出るあのインデントされた木と同じ積み方になる。
  • VACUUM と番号の周回 — 書き換えや削除で残った古い版を実際に表から取り除く回収。回収してよい境界は、いちばん古い現役のトランザクション(OldestActive)が決め、それより前に消された版だけを Vacuum が捨てて行を詰め直す。古いトランザクションが一本でも開いていると境界がそこで止まり、回収できないまま版が溜まる。あわせて、トランザクション番号が 32 ビットで一周すると過去が未来に見える「周回」を、前後を大小ではなく距離(int32(a-b) の符号)で決める xidPrecedes の小さなモデルで再現し、古い行の作成番号を特別な FrozenXID に置き換える freeze(凍結)で、一周しても過去のまま見え続けるようにする。PostgreSQL の autovacuum が背後で回している、回収と凍結の二つの仕事に当たる。
  • EXPLAIN とプランナ — 同じ等値条件に対する全件走査と索引走査に費用の数字をつけ、安いほうの実行の木を選ぶ費用ベースのプランナ。第 10 回の B+tree を列の値から行 ID への索引にした IndexScan を足し、全件走査と同じ三つの約束(開く・一件出す・閉じる)で差し替えられるようにする。当たる行数は列の異なり数から見積もり(Stats / estimateRows、一意な列なら一行、二種類しかない列なら半分)、順番読み 1 件を 1.0・索引経由 1 件を 4.0(PostgreSQL の seq_page_costrandom_page_cost の既定比)として費用を比べる。PlanEquals は一意な id には Index Scan を、二種類しかない city には索引があっても Seq Scan を選ぶ。索引が使われないのは壊れているからではなく、当たる行が多すぎて拾い読みより全件順読みが安いと見積もりが言うから。PlanCostEstRowsExplain の一行は、PostgreSQL の EXPLAIN に出る cost と rows そのもの。
  • チューニングの原理 — 限られたメモリをバッファプールと接続でどう分けるかを、手元の計測で裏づける回。第 9 回のバッファプールに参照列を流してヒット率を測る MeasureHitRate と、よく触る一部に参照を集めた偏りのある参照列を作る LocalReferences で、容量を増やすほどヒット率は上がるが、よく触る一部が収まったあとは伸びが鈍ることを数で見せる(容量 2 で 0.16、10 で 0.65、40 で 0.91、100 で 0.98)。容量を決める基準は、全データ量ではなく、よく触る一部の大きさ。あわせて、接続ごとにプロセスとメモリを持つ PostgreSQL を BackendMemory(メモリは接続数に比例)で、接続プール(PgBouncer 相当)が奥の接続を上限に束ねて総メモリを頭打ちにするさまを PoolingCapsBackends(千接続を五十に、10000 MiB を 500 MiB に)でモデル化する。shared_buffers の効きは pg_stat_database のヒット率で測り、work_mem は接続数との積で見て、接続は増やす前にプールで絞る、という運用判断につながる。
  • 観測(USE と RED) — 測れる値が多すぎる pg_stat_* を、二つの型に落として読む回。処理は RED(流量・失敗・所要時間)で、実行のたびに所要時間と成否を積む REDStatCountErrorRateAvgDuration を返す。資源は USE(使用率・飽和・エラー)で、バッファプールを BufferPoolUSE(使用率=載っているページ数/フレーム数、飽和=第 16 回のミス率)、表を StorageUSE(飽和=生きた版に対する不要タプルの割合、第 14 回の回収が止まると上がり Vacuum で下がる)で表す。使用率はバッファプールが動けば満杯で 1 に張りつき詰まりを表さないので、見るのは飽和(取りこぼし・待ち・溜まり)。PostgreSQL では RED を pg_stat_statementscallsmean_exec_timexact_rollback に、USE を blks_read の割合・n_dead_tup の比・待ちの数に割り当て、RED で処理の異常に気づいて USE で詰まった資源にたどり着く、という読む順に結びつく。
  • 壊れる前提の運用(PITR と SLO) — どれだけ観測しても、いつかは壊れる。壊れたとき何を失い、どれだけで戻すかを、壊れる前に決めておく連載最終回。第 11 回の WAL を先頭からある時点まで適用して止める RecoverUntil(PITR の骨格)と、いまどこまで進んだかの目印を返す WALLength(PostgreSQL の LSN に当たる)で、誤った上書きの手前まで巻き戻せることを示す。同じログを最後まで適用すれば誤りごと再現し、target で止めれば手前に戻る。止める場所を変えるだけで間違いの前後を選べるのが PITR。レプリケーションはこれを止めずに standby へ流し続ける同じ WAL 適用の連続版で、PITR が時間の軸で「どこまで」を選ぶのに対し、空間の軸で「どこで」系を分ける。あわせて、守ると約束した可用率から止まってよい時間を出す ErrorBudget(99.9% を 30 日で守るなら約 43 分)と、使った停止を引いた残りを返す BudgetRemaining で、信頼性を感覚ではなく残高で語る。PostgreSQL では PITR はベースバックアップと WAL の保管でできて目標時点まで適用し、レプリケーションの遅れは pg_stat_replication(非同期で失いうる量)で見え、失ってよい量(RPO)と戻す時間(RTO)から同期・非同期や standby の温め方を逆算する。バックアップは、戻せて初めてバックアップ。

必要なもの

  • Go 1.26 以降

試し方

git clone https://github.com/engineers-hub-ltd-in-house-project/minidb.git
cd minidb

# テスト
go test ./...

# デモ(一時ファイルに 1000 件入れて全件走査する)
go run ./cmd/minidb

コマンドとして手元に入れて試すこともできます。

go install github.com/engineers-hub-ltd-in-house-project/minidb/cmd/minidb@latest
minidb
実際の出力

go test ./... -v の出力:

=== RUN   TestBTreeInsertAndSearch
--- PASS: TestBTreeInsertAndSearch (0.74s)
=== RUN   TestBTreeScanIsSorted
--- PASS: TestBTreeScanIsSorted (0.44s)
=== RUN   TestBTreeDeleteWithMerge
--- PASS: TestBTreeDeleteWithMerge (0.48s)
=== RUN   TestBTreeRandomizedAgainstMap
--- PASS: TestBTreeRandomizedAgainstMap (2.30s)
=== RUN   TestBufferPoolWriteReadBack
--- PASS: TestBufferPoolWriteReadBack (0.00s)
=== RUN   TestBufferPoolEvictionFlushesDirty
--- PASS: TestBufferPoolEvictionFlushesDirty (0.03s)
=== RUN   TestBufferPoolAllPinnedReturnsError
--- PASS: TestBufferPoolAllPinnedReturnsError (0.01s)
=== RUN   TestSlottedPageInsertGet
--- PASS: TestSlottedPageInsertGet (0.00s)
=== RUN   TestSlottedPageDeleteAndReuse
--- PASS: TestSlottedPageDeleteAndReuse (0.00s)
=== RUN   TestHeapFileInsert1000AndScan
    heap_test.go:111: inserted 1000 records across 2 pages, scan returned 1000
--- PASS: TestHeapFileInsert1000AndScan (1.78s)
=== RUN   TestSeqScanVisibility
--- PASS: TestSeqScanVisibility (0.00s)
=== RUN   TestFilter
--- PASS: TestFilter (0.00s)
=== RUN   TestProjectAndPipeline
--- PASS: TestProjectAndPipeline (0.00s)
=== RUN   TestPlannerPicksIndexForSelective
--- PASS: TestPlannerPicksIndexForSelective (0.35s)
=== RUN   TestPlannerPicksSeqForNonSelective
--- PASS: TestPlannerPicksSeqForNonSelective (0.36s)
=== RUN   TestPlannerFallsBackWithoutIndex
--- PASS: TestPlannerFallsBackWithoutIndex (0.34s)
=== RUN   TestPlanExplainShowsChoice
--- PASS: TestPlanExplainShowsChoice (0.34s)
=== RUN   TestHitRateRisesWithDiminishingReturns
    tuning_test.go:47: hit rate: pool=2 0.159, 10 0.652, 40 0.908, 100 0.980
--- PASS: TestHitRateRisesWithDiminishingReturns (0.33s)
=== RUN   TestPoolingCapsBackendMemory
--- PASS: TestPoolingCapsBackendMemory (0.00s)
=== RUN   TestREDRecordsRateErrorsDuration
--- PASS: TestREDRecordsRateErrorsDuration (0.00s)
=== RUN   TestBufferPoolUSEReflectsFillAndMisses
--- PASS: TestBufferPoolUSEReflectsFillAndMisses (0.33s)
=== RUN   TestStorageUSEReflectsDeadTuples
--- PASS: TestStorageUSEReflectsDeadTuples (0.00s)
=== RUN   TestPITRRecoversToPointBeforeBadWrite
--- PASS: TestPITRRecoversToPointBeforeBadWrite (0.03s)
=== RUN   TestErrorBudget
--- PASS: TestErrorBudget (0.00s)
=== RUN   TestVacuumReclaimsDeadVersions
--- PASS: TestVacuumReclaimsDeadVersions (0.00s)
=== RUN   TestVacuumBlockedByOldTx
--- PASS: TestVacuumBlockedByOldTx (0.00s)
=== RUN   TestXIDWraparoundHidesUnfrozenRow
--- PASS: TestXIDWraparoundHidesUnfrozenRow (0.00s)
=== RUN   TestFreezeSurvivesWraparound
--- PASS: TestFreezeSurvivesWraparound (0.00s)
PASS
ok  	github.com/engineers-hub-ltd-in-house-project/minidb	8.637s
?   	github.com/engineers-hub-ltd-in-house-project/minidb/cmd/minidb	[no test files]

go run ./cmd/minidb の出力:

inserted 1000 records across 2 pages, scan returned 1000

ファイル構成

ファイル 役割
page.go スロット付きページ(ページ内のレコード配置と詰め直し)
disk.go ページの入出力(ファイルをページ単位で読み書き)
heap.go ヒープファイルと全件走査(レコードの置き場所と Seq Scan)
heap_test.go ページ/ヒープファイルのテスト
buffer.go バッファプール(フレーム管理・clock 置換・dirty/flush)
buffer_test.go バッファプールのテスト
btree.go B+tree 索引(探索・挿入・分割・削除・借用・併合・縮約・順序走査)
btree_test.go B+tree のテスト(挿入探索・順序走査・削除併合・ランダム照合)
mvcc.go MVCC(XID・スナップショット・バージョン可視性・dead version の計数)
mvcc_test.go MVCC のテスト(スナップショット分離・自己更新・版分岐・削除可視性・abort・DeadVersions)
executor.go クエリ処理(Volcano モデルのイテレータ:SeqScan・Filter・Project と Run)
executor_test.go クエリ処理のテスト(可視性走査・条件振り分け・三段パイプライン)
vacuum.go VACUUM(不要タプルの回収・OldestActive 境界・32 ビットの周回モデルと凍結)
vacuum_test.go VACUUM のテスト(回収・古い tx による回収停止・周回での消失・凍結での生存)
planner.go EXPLAIN とプランナ(索引走査・異なり数からの行数見積もり・費用比較で安い木を選ぶ)
planner_test.go プランナのテスト(選択性が高い時の索引走査・低い時の全件走査・索引なしの退避・Explain)
tuning.go チューニングの原理(ヒット率の計測・偏りのある参照で容量とヒット率の鈍り・接続に比例するメモリとプールによる頭打ち)
tuning_test.go チューニングのテスト(容量を上げるとヒット率の伸びが鈍る・プールで backend と総メモリが頭打ち)
metrics.go 観測(処理を RED/資源を USE で表す・バッファプールの飽和=ミス率・表の飽和=不要タプルの割合)
metrics_test.go 観測のテスト(RED の積み上げ・プール満杯で使用率 1 と飽和・不要タプルで表の飽和が上下)
recovery.go 壊れる前提の運用(WAL をある時点まで適用して止める PITR・目標可用率から止まってよい時間を出すエラーバジェット)
recovery_test.go 復旧のテスト(誤上書きの手前まで戻す PITR・目標可用率と期間からエラーバジェットを計算)
cmd/minidb/main.go 1000 件入れて全件走査するデモ

バージョニング

連載の回ごとにタグ(v0.10 のような形)を打って、各回の状態をあとからたどれるようにする方針です。第 18 回時点ではまだタグを打っていません。

注意

これは連載の教材用に、仕組みを追えることを優先した最小実装です。本番用のデータベースではありません。

ライセンス

MIT License. 詳細は LICENSE を参照してください。

Documentation

Index

Constants

View Source
const FrozenXID = uint32(2)

FrozenXID は、何に対しても過去とみなす特別な番号。 周回の説明のため、64 ビットの XID とは別に、32 ビットの小さなモデルとして閉じてある。

View Source
const PageSize = 8192

PageSize は 1 ページの大きさ。PostgreSQL に合わせて 8KB にする。

Variables

View Source
var ErrNoFreeFrame = errors.New("no free frame available (all pinned)")

ErrNoFreeFrame は、すべてのフレームが pin されていて、 追い出せるものが一つもないときに返す。

View Source
var ErrPageFull = errors.New("page is full")

ErrPageFull はページに空きが足りないときに返す。

View Source
var ErrSlotNotFound = errors.New("slot not found")

ErrSlotNotFound は指定したスロットが空、または範囲外のときに返す。

Functions

func BackendMemory

func BackendMemory(connections int, perConnMiB float64) float64

BackendMemory は、接続数と 1 接続あたりのメモリ(MiB)から、見込みの総メモリを返す。 PostgreSQL は接続ごとにプロセスを持つので、メモリは接続数に比例して増える。

func BudgetRemaining

func BudgetRemaining(objective float64, window, downtime time.Duration) time.Duration

BudgetRemaining は、これまでの停止 downtime を引いた、残りのエラーバジェット。 負になっていたら、約束を破っている。

func ErrorBudget

func ErrorBudget(objective float64, window time.Duration) time.Duration

ErrorBudget は、目標可用率 objective(0 から 1) と期間 window から、許される停止時間を返す。 99.9 パーセントを 30 日で守るなら、許される停止は約 43 分。この 43 分がエラーバジェット。

func LocalReferences

func LocalReferences(rng *rand.Rand, total, hot, count int, hotShare float64) []int

LocalReferences は、偏りのある参照列を作る。 hot 本のページに参照の hotShare を集め、残りを全体へ散らす。 「よく触る一部」と「たまに触る全体」という、現実の偏りの再現。

func PoolingCapsBackends

func PoolingCapsBackends(clientConnections, poolSize int) int

PoolingCapsBackends は、接続プールを挟んだときの実 backend 接続数を返す。 表側の接続がいくつあっても、backend は poolSize までに収まる。PgBouncer の効き目。

func RecoverInto

func RecoverInto(walPath string, disk *DiskManager) (int, error)

RecoverInto は、ログを先頭から読み、各ページの中身をデータファイルへ書き戻す。 途中で切れた末尾や、壊れたレコードに当たったら、そこで安全に止める。 適用したレコード数を返す。

func RecoverUntil

func RecoverUntil(walPath string, disk *DiskManager, target int) (int, error)

RecoverUntil は、ログを先頭から target 個のレコードまで適用して止める。 target の先にレコードが残っていても、適用しない。ある時点まで進めて、そこで止める。 これが PITR(point-in-time recovery)の骨格。誤った操作の手前まで巻き戻せる。

func Vacuum

func Vacuum(t *MVCCTable, oldestActive XID) int

Vacuum は、境界より前に消された版を表から取り除き、回収した数を返す。 残った版で行を詰め直し、版が一つも残らなかった行は表から消す。 回収できる境界は、いつもいちばん古い現役(OldestActive)が決める。

func WALLength

func WALLength(walPath string) (int, error)

WALLength は、ログに入っている正常なレコードの数を返す。 いまどこまで進んでいるかの目印。PostgreSQL の LSN に当たる。

Types

type BPlusTree

type BPlusTree struct {
	// contains filtered or unexported fields
}

BPlusTree は、バッファプールの上に積んだ B+tree の索引。 キー(int64)から RecordID(行の住所、PostgreSQL の ctid に当たる)を引く。

func NewBPlusTree

func NewBPlusTree(bp *BufferPool) (*BPlusTree, error)

NewBPlusTree は空の B+tree を作る。葉だけの根を 1 ページ確保する。 根のページ番号はメモリ上にだけ持つ。再オープン時に引き継ぐための メタページは、まだ作らない(次回以降)。

func (*BPlusTree) Delete

func (t *BPlusTree) Delete(key int64) error

Delete は key を取り除く。無ければ何もしない。

func (*BPlusTree) Insert

func (t *BPlusTree) Insert(key int64, rid RecordID) error

Insert は key -> rid を入れる。すでに同じ key があれば上書きする。

func (*BPlusTree) Scan

func (t *BPlusTree) Scan(fn func(key int64, rid RecordID) error) error

Scan は、すべての要素をキーの昇順で関数へ渡す。 根から最左の葉まで降り、あとは葉の横のつながり(next)をたどるだけ。

func (*BPlusTree) Search

func (t *BPlusTree) Search(key int64) (RecordID, bool, error)

Search は key を引く。見つかれば住所と true を、なければ false を返す。 根の節から葉まで降りるだけ。内部ノードでは、どの子へ降りるかを仕切りで決める。

type BufferPool

type BufferPool struct {
	// contains filtered or unexported fields
}

BufferPool は、限られた数のフレームを持ち、ディスクのページをメモリに載せる。 同じページが要るときは、ディスクへ行かずにメモリから返す。

func NewBufferPool

func NewBufferPool(disk *DiskManager, size int) *BufferPool

NewBufferPool は、size 枚のフレームを持つバッファプールを作る。

func (*BufferPool) FetchPage

func (bp *BufferPool) FetchPage(pageID int) (*Page, error)

FetchPage は、ページをメモリに用意して返し、pin する。 すでにメモリにあれば、それを返す(ヒット)。なければディスクから読む(ミス)。

func (*BufferPool) FlushAll

func (bp *BufferPool) FlushAll() error

FlushAll は、書き換えられた全フレームをディスクへ書き戻す。 PostgreSQL のチェックポイントに当たる動き。

func (*BufferPool) FlushPage

func (bp *BufferPool) FlushPage(pageID int) error

FlushPage は、指定ページが書き換えられていれば、ディスクへ書き戻す。

func (*BufferPool) NewPage

func (bp *BufferPool) NewPage() (int, *Page, error)

NewPage は、ディスクに新しいページを足し、それをメモリに載せて返す(pin 済み)。

func (*BufferPool) Unpin

func (bp *BufferPool) Unpin(pageID int, dirty bool)

Unpin は、ページの使用を終える。dirty が true なら、書き換えた印をつける。

type DiskManager

type DiskManager struct {
	// contains filtered or unexported fields
}

DiskManager はページ単位でファイルを読み書きする。 ファイルを PageSize ごとに区切り、ページ番号で位置を決める。

func OpenDisk

func OpenDisk(path string) (*DiskManager, error)

OpenDisk はファイルを開く(なければ作る)。

func (*DiskManager) AllocatePage

func (d *DiskManager) AllocatePage() (int, error)

AllocatePage は末尾に空のページを足し、その番号を返す。

func (*DiskManager) Close

func (d *DiskManager) Close() error

Close はファイルを閉じる。

func (*DiskManager) NumPages

func (d *DiskManager) NumPages() (int, error)

NumPages はファイルが今いくつのページ分あるかを返す。

func (*DiskManager) ReadPage

func (d *DiskManager) ReadPage(id int) (*Page, error)

ReadPage はページ番号 id のページを読む。

func (*DiskManager) WritePage

func (d *DiskManager) WritePage(id int, p *Page) error

WritePage はページ番号 id の位置へページを書く。

type Filter

type Filter struct {
	// contains filtered or unexported fields
}

Filter は条件で振り分ける段。合うものだけを上へ通す。

func NewFilter

func NewFilter(child Operator, pred func(*Tuple) bool) *Filter

NewFilter は下の段と条件を束ねた振り分けの段を作る。

func (*Filter) Close

func (f *Filter) Close()

func (*Filter) Next

func (f *Filter) Next() (*Tuple, bool)

Next は、条件に合う一件が来るまで下の段に出させ続ける。

func (*Filter) Open

func (f *Filter) Open()

type HeapFile

type HeapFile struct {
	// contains filtered or unexported fields
}

HeapFile はページを並べてレコードを溜める、いちばん素朴な置き場所。

func NewHeapFile

func NewHeapFile(d *DiskManager) *HeapFile

NewHeapFile はディスクマネージャの上にヒープファイルを作る。

func (*HeapFile) Get

func (h *HeapFile) Get(rid RecordID) ([]byte, error)

Get は住所を指定してレコードを 1 件読む。

func (*HeapFile) Insert

func (h *HeapFile) Insert(record []byte) (RecordID, error)

Insert はレコードを 1 件入れ、その住所を返す。 末尾のページから空きを探し、どこにも入らなければ新しいページを足す。

func (*HeapFile) Scan

func (h *HeapFile) Scan(fn func(rid RecordID, record []byte) error) error

Scan は全ページ、全スロットを順にたどり、生きているレコードを関数へ渡す。 PostgreSQL の Seq Scan に当たる、いちばん素朴な全件走査。

type HitStats

type HitStats struct {
	Hits   int
	Misses int
}

HitStats は、ページ参照のヒットとミスを数えた結果。

func MeasureHitRate

func MeasureHitRate(disk *DiskManager, size int, refs []int) (HitStats, error)

MeasureHitRate は、ページ参照列 refs を、容量 size のプールに流して、ヒット率を測る。 すでにプールに載っていればヒット、なければディスクから読むのでミス。 各参照はすぐ離す。次の参照で追い出せるようにするため。

func ObserveBuffer

func ObserveBuffer(bp *BufferPool, refs []int) (HitStats, error)

ObserveBuffer は、参照列を bp に流し、ヒット統計を返す。 bp は呼び出し側が保持するので、流したあとの使用率を BufferPoolUSE で読める。

func (HitStats) HitRate

func (s HitStats) HitRate() float64

HitRate は、参照全体に対するヒットの割合。0 から 1。

func (HitStats) Total

func (s HitStats) Total() int

Total は、参照の総数。

type Index

type Index struct {
	// contains filtered or unexported fields
}

Index は、ある列の値から行 ID への索引。第 10 回の B+tree をそのまま使う。 MVCC 表では行のありかは rowID そのものなので、RecordID にはその rowID を入れる。

func NewIndex

func NewIndex(col string, tree *BPlusTree) *Index

NewIndex は、対象の列名と B+tree を束ねた索引を作る。

func (*Index) Add

func (ix *Index) Add(key int64, rowID int) error

Add は、キー値と行 ID の対応を索引に入れる。

func (*Index) Lookup

func (ix *Index) Lookup(key int64) (int, bool, error)

Lookup は、キー値に対応する行 ID を引く。無ければ二つ目が false。

type IndexScan

type IndexScan struct {
	// contains filtered or unexported fields
}

IndexScan は、索引でキーを一点引きし、該当する行だけを返す段。 全件を舐めない。索引が指した行 ID の版だけを、可視性をくぐらせて返す。

func NewIndexScan

func NewIndexScan(table *MVCCTable, tx *Tx, index *Index, key int64) *IndexScan

NewIndexScan は、表・トランザクション・索引・引くキーを束ねた走査を作る。

func (*IndexScan) Close

func (s *IndexScan) Close()

func (*IndexScan) Next

func (s *IndexScan) Next() (*Tuple, bool)

Next は並べた行を、前から一件ずつ返す。SeqScan と同じ約束。

func (*IndexScan) Open

func (s *IndexScan) Open()

Open は、索引で行 ID を引き、その行の版のうち見えるものだけを並べる。

type MVCCTable

type MVCCTable struct {
	// contains filtered or unexported fields
}

MVCCTable は、行ごとにバージョンの列を持つ表。 書き換えは、古いバージョンを消したことにして、新しいバージョンを足す。

func NewMVCCTable

func NewMVCCTable() *MVCCTable

func (*MVCCTable) DeadVersions

func (t *MVCCTable) DeadVersions(oldestActive XID) int

DeadVersions は、どのトランザクションから見ても、もう要らないバージョンの数を返す。 消した番号が、いちばん古い現役のトランザクションより前なら、誰からも見えない。 これが第 14 回の VACUUM が回収する対象、いわゆる不要タプルに当たる。

func (*MVCCTable) Delete

func (t *MVCCTable) Delete(tx *Tx, rowID int) bool

Delete は、いま見えているバージョンに、消した印をつける。

func (*MVCCTable) Insert

func (t *MVCCTable) Insert(tx *Tx, rowID int, data string)

Insert は、新しい行を作る。

func (*MVCCTable) Read

func (t *MVCCTable) Read(tx *Tx, rowID int) (string, bool)

Read は、トランザクションから見える行のデータを返す。

func (*MVCCTable) Update

func (t *MVCCTable) Update(tx *Tx, rowID int, data string) bool

Update は、いま見えているバージョンを消したことにして、新しいバージョンを足す。

type Observation

type Observation struct {
	Buffer  USEStat // 資源:バッファプール
	Storage USEStat // 資源:表(不要タプルの溜まり)
	Query   REDStat // 処理:クエリ
}

Observation は、ある時点の観測をひとまとめにしたもの。 資源は USE、処理は RED。定期的に取れば、状態の移り変わりが読める。

type Operator

type Operator interface {
	Open()
	Next() (*Tuple, bool) // 二つ目が false なら、もう無い
	Close()
}

Operator は Volcano モデルのイテレータ。開く・一件出す・閉じるの三つで揃える。 PostgreSQL の実行ノードに当たる。

type Page

type Page struct {
	// contains filtered or unexported fields
}

Page はスロット付きページ。固定長 PageSize のバイト列として持つ。 レイアウトは、先頭からヘッダ、続いてスロット配列が前へ伸び、 レコード本体がページの末尾から前へ向かって積まれる。

func LoadPage

func LoadPage(b []byte) *Page

LoadPage はディスクから読んだバイト列をページとして読み込む。

func NewPage

func NewPage() *Page

NewPage は空のページを作る。

func (*Page) Bytes

func (p *Page) Bytes() []byte

Bytes はディスクへ書き出すためのバイト列を返す。

func (*Page) Delete

func (p *Page) Delete(slot int) error

Delete はスロットを削除済みにする(length を 0 にする墓標)。 バイト領域は、その場では戻さない。次の compact で回収される。

func (*Page) Get

func (p *Page) Get(slot int) ([]byte, error)

Get はスロット番号のレコードを返す。削除済みや範囲外なら ErrSlotNotFound。

func (*Page) Insert

func (p *Page) Insert(record []byte) (int, error)

Insert はレコードを 1 件入れ、そのスロット番号を返す。 空きが足りなければ詰め直し(compaction)を試み、それでも入らなければ ErrPageFull。

type Plan

type Plan struct {
	Node    string // "Seq Scan" か "Index Scan"
	EstRows int
	Cost    float64
	// contains filtered or unexported fields
}

Plan は、選ばれた計画。走査の種類、見積もり行数、費用、そして実行する段。

func (Plan) Explain

func (p Plan) Explain() string

Explain は、EXPLAIN のような一行を返す。選んだ走査と、見積もりの行数・費用。

func (Plan) Root

func (p Plan) Root() Operator

Root は、この計画を実行する段を返す。Run に渡して動かす。

type Planner

type Planner struct {
	// contains filtered or unexported fields
}

Planner は、表と統計と使える索引を持ち、条件から安い計画を選ぶ。

func NewPlanner

func NewPlanner(table *MVCCTable, stats Stats, indexes map[string]*Index) *Planner

NewPlanner は、表・統計・列ごとの索引を束ねたプランナを作る。

func (*Planner) PlanEquals

func (pl *Planner) PlanEquals(tx *Tx, col string, key int64) Plan

PlanEquals は、col = key の等値条件に対して計画を立てる。 索引があり、索引走査のほうが安いなら IndexScan。そうでなければ全件走査に Filter。

type Project

type Project struct {
	// contains filtered or unexported fields
}

Project は列を絞る段。要る列だけを残す。

func NewProject

func NewProject(child Operator, cols []string) *Project

NewProject は下の段と残す列を束ねた絞り込みの段を作る。

func (*Project) Close

func (p *Project) Close()

func (*Project) Next

func (p *Project) Next() (*Tuple, bool)

Next は一件もらって、指定された列だけを残して返す。

func (*Project) Open

func (p *Project) Open()

type REDStat

type REDStat struct {
	Count     int           // 流量:処理した数
	Errors    int           // 失敗:失敗した数
	TotalTime time.Duration // 所要時間の合計
}

REDStat は、処理(リクエスト)の観測。Rate, Errors, Duration。

func (REDStat) AvgDuration

func (r REDStat) AvgDuration() time.Duration

AvgDuration は、1 件あたりの平均所要時間。

func (REDStat) ErrorRate

func (r REDStat) ErrorRate() float64

ErrorRate は、処理全体に対する失敗の割合。0 から 1。

func (*REDStat) Record

func (r *REDStat) Record(d time.Duration, err error)

Record は、1 件の処理の所要時間と結果を足し込む。err が非 nil なら失敗。

type RecordID

type RecordID struct {
	PageID int
	Slot   int
}

RecordID はレコードの住所。ページ番号とスロット番号の組。 PostgreSQL の ctid に当たるもの。

type SeqScan

type SeqScan struct {
	// contains filtered or unexported fields
}

SeqScan は表を頭から読む段。第 12 回の可視性をくぐらせ、見える行だけを返す。 PostgreSQL の Seq Scan に当たる。

func NewSeqScan

func NewSeqScan(table *MVCCTable, tx *Tx) *SeqScan

NewSeqScan は表とトランザクションを束ねた走査の段を作る。

func (*SeqScan) Close

func (s *SeqScan) Close()

func (*SeqScan) Next

func (s *SeqScan) Next() (*Tuple, bool)

Next は並べた行を、前から一件ずつ返す。

func (*SeqScan) Open

func (s *SeqScan) Open()

Open は表を一周し、このトランザクションから見える版だけを並べる。

type Snapshot

type Snapshot struct {
	// contains filtered or unexported fields
}

Snapshot は、ある時点での「何が見えるか」を固定したもの。 xmax 以上の番号は未来。xip は、その時点で進行中だった番号。どちらも見えない。

type Stats

type Stats struct {
	RowCount int
	Distinct map[string]int // 列名 → その列の異なる値の数
}

Stats は、計画を立てるための見積もり材料。表の行数と、列ごとの異なり数。 PostgreSQL では ANALYZE が集めて pg_statistic に貯める情報に当たる。

type Tuple

type Tuple struct {
	Values map[string]string
}

Tuple は段から段へ流れる、一行ぶんの値。

func Run

func Run(root Operator) []*Tuple

Run はてっぺんの段を尽きるまで引いて、結果を集める。 この一回の引きが、下の段まで伝わって全体を動かす。

type Tx

type Tx struct {
	// contains filtered or unexported fields
}

Tx は、一つのトランザクション。自分の番号とスナップショットを持つ。

type TxManager

type TxManager struct {
	// contains filtered or unexported fields
}

TxManager は、トランザクション番号を配り、各番号の状態を覚える。

func NewTxManager

func NewTxManager() *TxManager

func (*TxManager) Abort

func (m *TxManager) Abort(tx *Tx)

func (*TxManager) Begin

func (m *TxManager) Begin() *Tx

Begin は、新しいトランザクションを始める。 このとき、いま進行中の他のトランザクションを覚えたスナップショットを取る。

func (*TxManager) Commit

func (m *TxManager) Commit(tx *Tx)

func (*TxManager) OldestActive

func (m *TxManager) OldestActive() XID

OldestActive は、いま回収してよい境界を返す。 いちばん古い現役のトランザクション番号。これより前に消された版は、もう誰からも見えない。 進行中が一つも無ければ、次に配る番号を返す(いま消えている版は全部回収してよい)。

type USEStat

type USEStat struct {
	Utilization float64 // 使用率:資源のうち使っている割合。0 から 1
	Saturation  float64 // 飽和:処理しきれず待たされている度合い
	Errors      int     // エラー:資源が返した失敗の数
}

USEStat は、資源の観測。Utilization, Saturation, Errors。

func BufferPoolUSE

func BufferPoolUSE(bp *BufferPool, s HitStats) USEStat

BufferPoolUSE は、バッファプールを資源として USE で表す。 使用率 = 載っているページ数 / フレーム数。飽和 = ミス率。ミスが多いほど、容量に働きが詰まっている。

func StorageUSE

func StorageUSE(t *MVCCTable, oldestActive XID) USEStat

StorageUSE は、表を資源として USE で表す。 飽和 = 不要タプルの割合。回収が追いつかず溜まっているほど高い。第 14 回の回収の遅れが、ここに出る。

type WAL

type WAL struct {
	// contains filtered or unexported fields
}

WAL は、変更を先に記録する追記専用のログ。 データページをディスクに書く前に、まずここへ書いて fsync する。 これが先行書き込み(write-ahead)。落ちても、ログから復旧できる。

func OpenWAL

func OpenWAL(path string) (*WAL, error)

OpenWAL は、ログファイルを開く(なければ作る)。追記専用で開く。

func (*WAL) AppendPageImage

func (w *WAL) AppendPageImage(pageID int, image []byte) error

AppendPageImage は、ページの新しい中身をログに追記し、fsync する。 1 レコードの形: [payloadLen uint32][crc uint32][pageID int32][image ...] crc を付けるのは、末尾が途中で切れた壊れたレコードを、復旧時に見分けるため。

func (*WAL) Close

func (w *WAL) Close() error

Close はログを閉じる。

type XID

type XID int64

XID はトランザクション番号。単調に増えていく。

Directories

Path Synopsis
cmd
minidb command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL