├── .gitignore ├── BUCKET.md ├── DATA_AND_INDEX.md ├── LICENSE ├── README.md ├── STORAGE_AND_CACHE.md ├── TX.md ├── api ├── batch_create.go ├── batch_create.txt ├── delete_buckets.go ├── generate_1000_users.go ├── lib │ ├── crud.go │ ├── db.go │ └── util.go ├── open.go └── range_query.go ├── bucket ├── create_bucket.go └── visitkv.go ├── go.mod ├── go.sum ├── statics ├── imgs │ ├── bucket-insert-bucket2.jpg │ ├── bucket-insert-new-inline-bucket.jpg │ ├── bucket-nested-buckets.jpg │ ├── bucket-new-bucket-1.jpg │ ├── bucket-new-bucket-2.jpg │ ├── bucket-new-bucket-b1.jpg │ ├── bucket-new-inline-bucket.jpg │ ├── bucket-normal-bucket-b-plus-tree.jpg │ ├── bucket-normal-bucket.jpg │ ├── bucket-root-bucket-tree.jpg │ ├── bucket-root-bucket.jpg │ ├── data-and-index-b-plus-tree.jpg │ ├── data-and-index-branch-leaf-page.jpg │ ├── data-and-index-branch-page-node.jpg │ ├── data-and-index-leaf-page-node.jpg │ ├── data-and-index-oversize-page.jpg │ ├── data-and-index-oversized-kv.jpg │ ├── data-and-index-rebalance-1.jpg │ ├── data-and-index-rebalance-2.jpg │ ├── data-and-index-rebalance-3.jpg │ ├── data-and-index-spill-1.jpg │ ├── data-and-index-spill-2.jpg │ ├── data-and-index-spill-3.jpg │ ├── data-and-index-spill-4.jpg │ ├── data-and-index-unbalanced-node.jpg │ ├── search-time-complexity.jpg │ ├── storage-and-cache-branch-leaf-page-layout.jpg │ ├── storage-and-cache-branch-page-element-header.jpg │ ├── storage-and-cache-branch-page-layout.jpg │ ├── storage-and-cache-empty-instance-layout.jpg │ ├── storage-and-cache-freelist-example.jpg │ ├── storage-and-cache-freelist-free-pages.jpg │ ├── storage-and-cache-freelist-page-layout.jpg │ ├── storage-and-cache-leaf-page-element-header.jpg │ ├── storage-and-cache-leaf-page.jpg │ ├── storage-and-cache-meta-page-layout.jpg │ ├── storage-and-cache-overflow-freelist-page.jpg │ ├── storage-and-cache-overflow-page.jpg │ ├── storage-and-cache-page-header.jpg │ ├── storage-and-cache-page-layout.jpg │ ├── storage_bucket_hierarchy.jpg │ ├── storage_mvcc.jpg │ ├── tx-boltdb-mvcc-example.jpg │ ├── tx-boltdb-page.jpg │ └── tx-transactional-memory.jpg └── xmls │ ├── b_plus_tree.drawio │ ├── boltdb.drawio │ ├── bucket.drawio │ └── tx.drawio └── tx └── exec_example.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea 15 | *.db -------------------------------------------------------------------------------- /BUCKET.md: -------------------------------------------------------------------------------- 1 | # 桶 2 | 3 | 桶是 Bolt 中键值数据的容器,开发者可以在同一个 Bolt 实例中创建不同的桶,存放不同门类的数据。桶还支持无限嵌套,即桶里可以创建子桶,为开发者归类数据提供更灵活的方案。 4 | 5 | ## 目录 6 | 7 | * 桶的逻辑结构 8 | * 桶的物理结构 9 | * 游标 10 | 11 | ## 桶的逻辑结构 12 | 13 | Bolt 里的每个桶既可以存放子桶也可以存放键值数据,得益于这个设计,Bolt 实例在初始化时创建一个根桶 (root bucket) 即可。用户创建的所有桶和键值对都会被保存在根桶中,嵌套地构成一棵树。 14 | 15 | 当开发者[创建新桶](./bucket/create_bucket.go)时: 16 | 17 | ```go 18 | // bucket/create_bucket.go 19 | func main() { 20 | db, _ := bolt.Open("1.db", 0600, nil) 21 | defer db.Close() 22 | 23 | _ = db.Update(func(tx *bolt.Tx) error { 24 | _, err := tx.CreateBucketIfNotExists("bucket1") 25 | }) 26 | } 27 | ``` 28 | 29 | Bolt 就在根桶上插入一个新的键值对,键为 "bucket1",值为这个 bucket 实例,如下图所示: 30 | 31 | ![new-bucket-1](./statics/imgs/bucket-new-bucket-1.jpg) 32 | 33 | 继续创建 bucket2, bucket3,可以得到: 34 | 35 | ![new-bucket-2](./statics/imgs/bucket-new-bucket-2.jpg) 36 | 37 | 往 bucket2 中插入键值数据 `{k1: v1}` 和 `{k2: v2}`,可以得到: 38 | 39 | ![insert-bucket2](./statics/imgs/bucket-insert-bucket2.jpg) 40 | 41 | 在 bucket2 中继续创建两个子桶 bucket23 和 bucket24,可以得到: 42 | 43 | ![nested-buckets](./statics/imgs/bucket-nested-buckets.jpg) 44 | 45 | 在上图中,我们已经看到了一棵树的样子。 46 | 47 | ## 桶的物理结构 48 | 49 | 在[B+ 树](DATA_AND_INDEX.md)一节中介绍到 Bolt 使用 B+ 树存储索引及原数据,那么它是如何利用 B+ 树实现桶呢?我们从最简单的情形开始理解: 50 | 51 | ### 初始化 DB 52 | 53 | 在初始化新的实例时 Bolt 会创建根桶,在 DB 的[初始化方法](https://github.com/boltdb/bolt/blob/master/db.go#L343) 中可以看到: 54 | 55 | ```go 56 | func (db *DB) init() error { 57 | buf := make([]byte, db.pageSize*4) 58 | for i := 0; i < 2; i++ { 59 | // ... 60 | // Initialize the meta page. 61 | m := p.meta() 62 | // ... 63 | m.root = bucket{root: 3} 64 | // ... 65 | } 66 | // ... 67 | // Write an empty leaf page at page 4 68 | p = db.pageInBuffer(buf[:], pgid(3)) 69 | p.id = pgid(3) 70 | p.flags = leafPageFlag 71 | p.count = 0 72 | // ... 73 | } 74 | ``` 75 | 76 | 根桶的信息被储存在 meta page 中。初始化完毕后,根桶指向了一个空的 leaf page,后者将成为所有用户创建的子桶及键值数据的容器,如下图所示: 77 | 78 | ![root-bucket](./statics/imgs/bucket-root-bucket.jpg) 79 | 80 | ### 创建一个桶 81 | 82 | 假设用户初始化 Bolt 实例后,创建一个名字叫 "b1" 的桶,反映在图中就是 83 | 84 | ![new-bucket](./statics/imgs/bucket-new-bucket-b1.jpg) 85 | 86 | 在 leaf page 中增加一个键值对,其中键为 `b1`,值为 `bucket` 实例。`bucket` 实例的结构体定义如下: 87 | 88 | ```go 89 | type bucket struct { 90 | root pgid // page id of the bucket's root-level page 91 | sequence uint64 // monotonically incrementing, used by NextSequence() 92 | } 93 | ``` 94 | 95 | 这里的 `root` 指向该桶根节点的 page id。但如果我们为每个新桶都分配一个单独的 page,在需要大量使用小桶的场景下,会产生内部碎片,浪费存储空间。借用 [nested-bucket](https://github.com/boltdb/bolt#nested-buckets) 的例子:假设我们使用 Bolt 做为一个多租户服务的数据库,这个服务需要一个 tanents 桶存放每个租户的数据,每个租户内部包含有 Users, Notes 等其它子桶。假设实际使用情况符合幂律,80% 的租户数据只占总数据量的 20%,这时候如果为这些小租户的子桶都分配新的 page,就可能造成磁盘空间浪费。 96 | 97 | Bolt 使用 inline-bucket 来解决上述问题。inline-bucket 不会占用单独的 page,一个虚拟的 page,逻辑上它就是一个 page。每个 inline-page 中同样存有 page header,element headers 和 data,存储时它将被序列化成一般的二进制数据与桶名一起作为普通键值数据储存。实际上,所有新建的桶,包括我们刚刚创建的 b1,都是 inline-bucket。Bolt 利用 pgid 从 1 开始的特点,用 `pgid = 0` 表示 inline-bucket,因此上图可以细化为: 98 | 99 | ![new-inline-bucket](./statics/imgs/bucket-new-inline-bucket.jpg) 100 | 101 | 向 b1 桶中插入键值对 `{k1:v1}`,可以表示为: 102 | 103 | ![insert-new-inline-bucket](./statics/imgs/bucket-insert-new-inline-bucket.jpg) 104 | 105 | ### 插入更多的键值数据 106 | 107 | 当 b1 桶中的数据达到一定量,即超过 inline-bucket 的大小限制时,inline-bucket 就会被转化成正常的的桶,并能够分配到属于自己的 page,如下图所示: 108 | 109 | ![normal-bucket](./statics/imgs/bucket-normal-bucket.jpg) 110 | 111 | 插入更多的键值数据,b1 桶就会长成一棵更茂盛的 B+ 树: 112 | 113 | ![normal-bucket-b-plus-tree](./statics/imgs/bucket-normal-bucket-b-plus-tree.jpg) 114 | 115 | ### 创建更多的桶 116 | 117 | 假设用户继续创建更多像 b1 一样的桶,直到一个 leaf 节点无法容纳根桶的所有子节点,这时 root bucket 自身也将长成一棵更茂盛的 B+ 树: 118 | 119 | ![root-bucket-tree](./statics/imgs/bucket-root-bucket-tree.jpg) 120 | 121 | 需要注意的是,尽管每个桶内部的数据是合法的 B+ 树,但它们共同组成的树通常不是 B+ 树。 122 | 123 | ## 游标 124 | 125 | 游标是帮助用户顺序或着随机访问桶中键值数据的数据结构,它对外提供的方法包括: 126 | 127 | ```go 128 | // 移动 cursor 到 bucket 中的第一个键值对,并返回键值数据 129 | func (c *Cursor) First() (key []byte, value []byte) 130 | // 移动 cursor 到 bucket 中的最后一个键值对,并返回键值数据 131 | func (c *Cursor) Last() (key []byte, value []byte) 132 | // 移动 cursor 到下一个键值对,并返回键值数据 133 | func (c *Cursor) Next() (key []byte, value []byte) 134 | // 移动 cursor 到上一个键值对,并返回键值数据 135 | func (c *Cursor) Prev() (key []byte, value []byte) 136 | // 移动 cursor 到给定键所在位置,并返回键值数据 137 | func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) 138 | // 删除 cursor 所在位置的键值数据 139 | func (c *Cursor) Delete() error 140 | ``` 141 | 142 | 它们可以被分为两类: 143 | 144 | * 顺序访问/遍历:First、Last、Next、Prev 145 | * 随机访问/检索:Seek 146 | 147 | ### 顺序访问/遍历 148 | 149 | 我们已经知道 Bolt 实例中每个叶子桶的逻辑结构都是 B+ 树,因此遍历叶子桶内数据的过程,就是遍历 B+ 树的过程。在树形数据结构上遍历的算法从实现上可以分为递归和迭代两种,前者具有更强的可读性,后者在运行时更节约栈空间。由于数据库中的 B+ 树单个节点较大,整棵树呈矮胖状,在递归时不会占用过多的栈空间,这里栈空间的占用就可以不予考虑。在使用游标的过程中,通常不会一次性遍历完所有数据,而是读到哪,走到哪,因此游标需要在每次移动后记录当前的位置信息。以下是游标的数据结构: 150 | 151 | ```go 152 | type Cursor struct { 153 | bucket *Bucket 154 | stack []elemRef 155 | } 156 | ``` 157 | 158 | 将栈放进结构体中,方便游标记录上一次所在的位置信息。B+ 树的中间节点不直接存储键值数据,因此遍历过程没有前序、中序、后序的区别。通内可能嵌套其它子桶,当游标在返回数据时,如果遇到值是子桶的情况,就会将其置为 nil,从而区分两种情况。我们可以通过以下[例子](./bucket/visitkv.go)看到这点: 159 | 160 | ```go 161 | // bucket/visitkv.go 162 | func main() { 163 | // init db 164 | // ignore errors for simplicity 165 | _ = db.Update(func(tx *bolt.Tx) error { 166 | b1, _ := tx.CreateBucketIfNotExists([]byte("b1")) 167 | _, _ = b1.CreateBucketIfNotExists([]byte("b11")) 168 | _ = b1.Put([]byte("k1"), []byte("v1")) 169 | _ = b1.Put([]byte("k2"), []byte("v2")) 170 | 171 | return b1.ForEach(func(k, v []byte) error { 172 | fmt.Printf("key: %s, val: %s, nil: %v\n", k, v, v == nil) 173 | return nil 174 | }) 175 | }) 176 | } 177 | ``` 178 | 179 | 执行程序: 180 | 181 | ```sh 182 | $ go run visitkv.go 183 | key: b11, val: , nil: true 184 | key: k1, val: v1, nil: false 185 | key: k2, val: v2, nil: false 186 | ``` 187 | 188 | 可以看到,当访问到 b11 桶时,返回的值是 nil。 189 | 190 | ### 随机访问/检索 191 | 192 | 由于 B+ 树上所有数据都按照键的字节序排列,因此检索的过程与二叉查找树相似。与二叉查找树不同,B+ 树上单个节点通常较大,存放数据较多,因此在单个节点上检索时会使用二分查找来提高检索效率。我们可以尝试估算检索过程的算法复杂度,如下图所示: 193 | 194 | ![search-time-complexity](./statics/imgs/search-time-complexity.jpg) 195 | 196 | 设数据的总数为 `N`,单个节点能够容纳的键值对数量上限为 `C`,那么单个节点的查找复杂度为 `Clog(C)`,树的高度为 `log(N)`,整个检索过程的时间复杂度为 `Clog(C)log(N)`。CMU 15-445 的 Tree Index 一节提供的数据显示:一棵 3 层的 B+ 树可以容纳 2,406,104 条数据,一棵 4 层的 B+ 树可以容纳 312,900,721 条数据,尽管这里的绝对数值没有参考意义 (与 page 大小和单个键值数据平均大小有关),我们可以认为树的高度为常数,因此整个检索的算法复杂度可以近似为 `O(1)`。 197 | 198 | 值得一提的是,在实现中,游标在 seek 时采用递归算法,且都是尾递归,具体可查阅源码。 199 | 200 | ## 小结 201 | 202 | 本节介绍桶的逻辑结构、物理结构以及如何访问桶中的数据: 203 | 204 | * 逻辑上,每个 Bolt 实例保持一个根桶,内部盛放所有用户创建的子桶和键值数据,用户可以在这些子桶中插入普通键值数据或者按需继续嵌套地创建子桶。整个实例中存储的数据可以被看作是一棵大树; 205 | * 物理上,每个桶是一棵 B+ 树,这些树根据嵌套关系共同组成一棵巨大的树,这棵大树一般不是 B+ 树; 206 | * 游标支持顺序和随机两种访问键值数据的方式。 -------------------------------------------------------------------------------- /DATA_AND_INDEX.md: -------------------------------------------------------------------------------- 1 | # 数据与索引 2 | 3 | 以数据主要存放的地点来划分,数据库可以分为 in-memory 和 disk-based 两种。前者将所有数据放进内存中,读写的过程不存在磁盘 I/O;后者将所有数据存放在磁盘中,读写过程需要频繁与磁盘交互,Bolt 就属于后者。对于 disk-based 数据库来说,数据库的性能瓶颈常常出现在磁盘 I/O 上,因此磁盘 I/O 次数常常被用来衡量这类数据库各操作的性能,减少磁盘 I/O 也就成为重要的设计目标。B+ 树正是为优化数据查询发生的磁盘 I/O 而生,它可以很好地将数据查询与块存储相结合,减少每次数据读取所需的 I/O 次数。Bolt 使用 B+ 树作为索引,将所有键值数据放在叶子节点。 4 | 5 | 磁盘中的一个 page 对应 B+ 树上的一个 node,数据库从磁盘载入 page 的过程就是将其反序列化成 node 的过程,将数据写入磁盘的过程就是将 node 序列化成 page 的过程。在[存储与缓存](STORAGE_AND_CACHE.md)一节中介绍的 branch page 和 leaf page 对应的正是 B+ 树上的 branch node 和 leaf node。Bolt 是键值数据库,不是关系型数据库,可以理解成每条数据只有一个字段,因此也只需要在这个字段上建立索引。Bolt 直接将数据存储在 B+ 树的 leaf node 上,类似关系型数据库中的一级索引。 6 | 7 | 为了方便,本文将以 node 为中心来讨论 Bolt 中的 B+ 树的结构及相关算法。 8 | 9 | ## 目录 10 | 11 | * 结构 12 | * 算法 13 | * 插入和删除 14 | * rebalance 与 spill 15 | * 小结 16 | 17 | ## 结构 18 | 19 | B+ 树上的每个 node 的结构都由 header,element header 列表及数据列表依次构成,branch node 中存储的数据只有键,leaf node 中存储的数据则是键值对: 20 | 21 | ![branch/leaf page](./statics/imgs/data-and-index-branch-leaf-page.jpg) 22 | 23 | 简化后的 branch node 如下: 24 | 25 | ![branch page/node](./statics/imgs/data-and-index-branch-page-node.jpg) 26 | 27 | 简化后的 leaf node 如下: 28 | 29 | ![leaf page/node](./statics/imgs/data-and-index-leaf-page-node.jpg) 30 | 31 | 利用简化后的 branch node 和 leaf node,就能够构建出一棵合法的 B+ 树: 32 | 33 | ![b+tree](./statics/imgs/data-and-index-b-plus-tree.jpg) 34 | 35 | 这棵树的特性基本与教科书上的类似,但略有不同: 36 | 37 | * 整棵 B+ 树是完美平衡树,即每个 leaf node 的深度都一样; 38 | * 除 root node之外,其它 node 的填充率需要大于 FillPercent,默认为 50%; 39 | * 所有的键值对都存储在 leaf node 中,branch node 只存储每个 child node 的最小键 (如上图箭头所示),键的大小由键的字节序决定。 40 | 41 | ## 算法 42 | 43 | 与教科书上介绍的 B+ 树操作不同,Bolt 在更新 B+ 树数据时不会直接修改树的结构,而是只更新数据。在数据写入磁盘前才按需合并、分裂 node,恢复 B+ 树的不变性;严格地说,Bolt 中的 B+ 树是一棵磁盘中的 B+ 树,在内存中执行更新操作且尚未写入磁盘前,它可能不符合 B+ 树的特性。 44 | 45 | ### 插入和删除 46 | 47 | 由于 Bolt 在更新 B+ 树时不会直接修改树的结构,只更新数据,读写事务插入数据或删除数据只需执行两步: 48 | 49 | * 顺着 root node 找到目标键所在的 leaf node 50 | * 插入或删除键值对数据 51 | 52 | *插入* 53 | 54 | 插入数据可能造成 node 中的数据超过单个 page 大小: 55 | 56 | ![oversize-page](./statics/imgs/data-and-index-oversize-page.jpg) 57 | 58 | 同时也要考虑单个键值对数据过大的情况: 59 | 60 | ![oversized-kv](./statics/imgs/data-and-index-oversized-kv.jpg) 61 | 62 | *删除* 63 | 64 | 删除数据可能使得 node 处于不平衡的状态,即键值对删除后 node 中的总数据量少于最低填充率 ( FillPercent): 65 | 66 | ![unbalanced-node](./statics/imgs/data-and-index-unbalanced-node.jpg) 67 | 68 | 当删除操作导致 node 的填充率低于要求时,虽然不会立即与兄弟节点合并,但会在结束前将该 node 标记为 `unbalanced`,等数据要写入磁盘前再统一合并。 69 | 70 | ### rebalance 与 spill 71 | 72 | 在读写事务提交时,Bolt 通过 rebalance 和 spill 来保证最终写入磁盘的是一棵合法的 B+ 树: 73 | 74 | * rebalance:将填充率不足的 node 与 sibling node 合并 75 | * spill:将填充率过高的 node 分裂成多个较小的 node 76 | 77 | *rebalance* 78 | 79 | > rebalance attempts to combine the node with sibling nodes if the node fill size is below a threshold. 80 | 81 | 执行过删除操作的 leaf node 都会被打上 unbalanced 的记号,而这些记号的消费者正是 rebalance,它负责将填充率不足 `FillPercent` 的 node 与对应的 sibling node 合并,以下图为例 (图中虚线为填充率): 82 | 83 | ![rebalance-1](./statics/imgs/data-and-index-rebalance-1.jpg) 84 | 85 | 当 L2 的数据总量不足,rebalance 找到它的 sibling node L3,将二者合并,并更新它们的 parent node B2: 86 | 87 | ![rebalance-2](./statics/imgs/data-and-index-rebalance-2.jpg) 88 | 89 | 这时,B2 的数据总量小于 `FillPercent`,rebalance 需要递归地继续合并 B2 与 B3: 90 | 91 | ![rebalance-3](./statics/imgs/data-and-index-rebalance-3.jpg) 92 | 93 | 如果有必要,这样的递归过程将持续到 root node 为止。这里还有一种特殊情况需要考虑:当 root node 为 branch node,且它只有一个 child node 时,可以直接将唯一的 child node 提拔成 root node,并释放原先的 root node。 94 | 95 | rebalance 之后,内存中的树装结构满足:所有 node 的数据填充率都大于 `FillPercent`。但这些 node 的填充率可能远远大于 100%,因此就需要 spill 操作从反方向将所有 node 的填充率控制在 `FillPercent` 之下。 96 | 97 | *spill* 98 | 99 | > spill writes the nodes to dirty pages and splits nodes as it goes. 100 | 101 | *注:spill 除了将 node 拆分,还会将其转化成 page,以下讨论不涉及转化的部分。* 102 | 103 | spill 本意是「水太满而从容器中溢出」,这里指的就是 node 中填充的数据太多,溢出到多个 node,举例如下: 104 | 105 | ![spill-1](./statics/imgs/data-and-index-spill-1.jpg) 106 | 107 | 显然 L1 中填充率过高,spill 需要将它拆分成多个 node: 108 | 109 | ![spill-2](./statics/imgs/data-and-index-spill-2.jpg) 110 | 111 | 拆分 L1 的过程会导致它的 parent node 填充率升高,进而可能引发 parent node 的拆分: 112 | 113 | ![spill-3](./statics/imgs/data-and-index-spill-3.jpg) 114 | 115 | 如果有必要,这样的递归会到达 root node 为止。 116 | 117 | parent node 超载的原因除了键值对数量过多,也可能是单个数据过大。如果是后面这种情况,spill 不会再将 node 拆分,而是保留这些超载的 node: 118 | 119 | ![spill-4](./statics/imgs/data-and-index-spill-4.jpg) 120 | 121 | 在序列化时,这种超载的 node 会被转化为 overflow page 存储。 122 | 123 | ## 小结 124 | 125 | Bolt 中的 B+ 树实现: 126 | 127 | * 将键值数据直接存储在叶子节点 128 | * 支持存储变长键值数据 129 | * 在内存中允许 B+ 树发生临时「变形」,落盘前再统一矫正,保证磁盘中的 B+ 树符合要求 130 | 131 | ## 参考 132 | 133 | * [boltdb src code: node.go](https://github.com/boltdb/bolt/blob/master/node.go) 134 | * [Lessons Learned While Implementing a B+Tree](https://hackthology.com/lessons-learned-while-implementing-a-btree.html) 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # learn-boltdb 2 | 3 | 在最近的闲暇时间 (2019 年初),我开始弥补自己数据库知识的盲点。这里也推荐一下自己最喜欢的课程和书籍: 4 | 5 | * CMU 15-445/645 Intro to Database Systems 6 | * Designing Data-Intensive Applications (DDIA) 7 | 8 | 尽管站在巨人的肩膀上,可以体会到数据库的博大精深,但这些努力始终只是在外部远观,并未真正进入其中。 9 | 10 | > What I cannot create, I do not understand. --- Richard Feynman 11 | 12 | 我一直将 Richard Feynman 的这句话记在心中,时刻提醒自己作为软件工程师,不能只满足于看懂,还要能做出来。在动手写一个数据库之前,我想先认真阅读某个数据库的源码,将那些常被提及的概念与实现关联。 13 | 14 | 在 DDIA 中,Martin Kleppmann 曾多次提及 Bolt,印象颇深的一点是它通过单线程执行读写事务的方式简单粗暴地实现最苛刻的可序列化事务隔离级别。后来的某一天,我心血来潮地读起 Bolt 项目的源码,发现它极简的设计理念正好满足我的学习需求。在阅读过程中,为梳理源码中的概念和逻辑,我开始将自己的理解转化成短文,逐渐形成了本项目。目前,Bolt 项目已经归档,不再更新,这也保证了本文内容不会过时。 15 | 16 | ## 为什么选择 Bolt 17 | 18 | > The original goal of Bolt was to provide a simple pure Go key/value store and to not bloat the code with extraneous features. --- Ben Johnson 19 | 20 | Bolt 可能是最适合 Go 语言工程师阅读的第一个数据库项目,原因在于它功能简单: 21 | 22 | * 单机部署:没有像 Raft、Paxos 这样的共识协议 23 | * 无服务端:没有通信协议,直接读写数据库文件 24 | * 存储键值:没有关系代数,没有数据字典,只有键和值 25 | * 无跨语言:仅支持 Go SDK 访问,没有 DSL 26 | 27 | 实现也简单: 28 | 29 | * 数据结构:所有数据存储在同一个树形结构中 30 | * 索引结构:键值数据天然地只有主键索引,使用经典的 B+ 树 31 | * 事务隔离:仅允许多个只读事务和最多一个读写事务同时运行 32 | * 缓存管理:仅管理写缓存,利用 mmap 管理读缓存 33 | 34 | ## 阅读建议 35 | 36 | 每篇短文覆盖一个话题,描述对应模块的实现。本系列文章将自底向上地介绍 Bolt,各个模块相对独立,顺序阅读和单篇阅读皆可。 37 | 38 | | 主题 | 源码 | 39 | | ------------------------------- | --------------------------------- | 40 | | [存储与缓存](./STORAGE_AND_CACHE.md) | page.go, freelist.go, db.go | 41 | | [数据与索引](./DATA_AND_INDEX.md) | node.go | 42 | | [桶](./BUCKET.md) | bucket.go, cursor.go | 43 | | [事务](./TX.md) | tx.go | 44 | 45 | ## 名词解释 46 | 47 | ### 中英对照 48 | 49 | 为避免专业术语的名称歧义,除已经有稳定中文翻译的词语外,其它词语将保留原始英文形式。下面是一份核心专业词语的中英对照表,供读者参考: 50 | 51 | | 中文 | 英文 | 52 | | ------------ | ------------------------------------ | 53 | | B+ 树 | B+Tree | 54 | | 事务 | transaction/tx | 55 | | 读写事务 | read-write transaction/tx | 56 | | 只读事务 | read-only transaction/tx | 57 | | 隐式事务 | managed/implicit transaction/tx | 58 | | 显式事务 | explicit transaction/tx | 59 | | 提交 | commit | 60 | | 回滚 | rollback | 61 | | 桶 | bucket | 62 | | 游标 | cursor | 63 | | 键/值/键值对/键值数据 | key/value/key-value(kv) pair/kv data | 64 | | 分裂 | split | 65 | | 合并 | merge | 66 | 67 | ### Page 68 | 69 | 块存储中的数据通常会被分割为等长的数据块,作为读写的最小单元。教科书称之为 frame 或 page frame,这是**物理概念**。当这些数据块被读入内存,在程序中被引用、读写时,教科书称之为 page,那是**逻辑概念**。在 Bolt 中,二者都被称为 page,如: 70 | 71 | * 常数 pageSize 中的 page 是物理概念 72 | * page.go 中提到的 page 多是逻辑概念 73 | 74 | 在阅读源码的过程中要注意区分。 75 | 76 | ### Bolt 与 Bolt 实例 77 | 78 | Bolt 指这个[项目](https://github.com/boltdb/bolt)本身,而 Bolt 实例指利用 api 创建的一个新的数据库,也指文件系统中存储对应数据的二进制文件。 79 | 80 | ## 参考 81 | 82 | * [Github: bolt/boltdb](https://github.com/boltdb/bolt) 83 | * [CMU 15-445/645 Intro to Database Systems](https://www.youtube.com/playlist?list=PLSE8ODhjZXja3hgmuwhf89qboV1kOxMx7) 84 | * [Designing Data-Intensive Applications](https://dataintensive.net/) 85 | 86 | 转载请注明出处为本项目: https://github.com/ZhengHe-MD/learn-bolt 87 | -------------------------------------------------------------------------------- /STORAGE_AND_CACHE.md: -------------------------------------------------------------------------------- 1 | # 存储与缓存 2 | 3 | 存储层负责管理数据库元数据以及数据的读写,它主要关心:**元数据、索引和原数据如何存储在磁盘上**;缓存层负责管理数据库各数据在磁盘与内存之间的移动,它主要关心:**何时将何种数据读入到内存的何地,何时将何种数据写出到磁盘的何地**。由于 Bolt 的缓存层处理较简单,因此本文将缓存层与存储层合并讨论。 4 | 5 | ## 目录 6 | 7 | * 存储 8 | * Page 9 | * 类型与布局 10 | * 数据溢出 11 | * Heap File Organization 12 | * Freelist 13 | * 缓存 14 | * 读缓存:mmap 15 | * 写缓存 16 | 17 | ## 存储 18 | 19 | 当我们[新建一个 Bolt 实例](./storage_and_cache/open.go)时: 20 | 21 | ```go 22 | // api/open.go 23 | func main() { 24 | db, err := bolt.Open("1.db", 0600, nil) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | defer db.Close() 29 | } 30 | ``` 31 | 32 | 当前目录下会新生成一个文件`1.db`,暂时称之为数据库文件,因为该文件包含了这个实例的**所有数据**,即**元数据、索引和原数据**。 33 | 34 | ### Page 35 | 36 | Bolt 将数据库文件划分成固定大小的块,作为读写的最小单元。每个数据块被称为一个 page,每个 page 的大小由文件系统中的 `PAGE_SIZE` 决定,后者是数据在磁盘与内存中搬运的最小单位,由计算机组成结构决定,以我的 MacBook Pro 为例,单个 page 大小为 4k: 37 | 38 | ```sh 39 | $ getconf PAGE_SIZE 40 | 4096 41 | ``` 42 | 43 | 以 `PAGE_SIZE` 来划分文件能最大效率地享受文件系统提供的读写能力。利用 Bolt 提供的命令行工具,可以看到刚刚创建的数据库文件的结构: 44 | 45 | ```sh 46 | $ bolt pages 1.db 47 | ID TYPE ITEMS OVRFLW 48 | ======== ========== ====== ====== 49 | 0 meta 0 50 | 1 meta 0 51 | 2 freelist 0 52 | 3 leaf 0 53 | ``` 54 | 55 | 一个空的 Bolt 实例由 4 个 page 构成,其中 2 个 meta page、1 个 freelist page 和 1 个 leaf page: 56 | 57 | ![db_layout](statics/imgs/storage-and-cache-empty-instance-layout.jpg) 58 | 59 | #### 类型与布局 60 | 61 | 在 [page.go#17](https://github.com/boltdb/bolt/blob/master/page.go#L17) 中,Bolt 定义了 4 种 page: 62 | 63 | ```go 64 | const ( 65 | branchPageFlag = 0x01 66 | leafPageFlag = 0x02 67 | metaPageFlag = 0x04 68 | freelistPageFlag = 0x10 69 | ) 70 | ``` 71 | 72 | 不论是哪种 page 都有 header 和 body 两部分: 73 | 74 | ![page_layout](./statics/imgs/storage-and-cache-page-layout.jpg) 75 | 76 | 展开 page header,其结构如下: 77 | 78 | ![page_header](./statics/imgs/storage-and-cache-page-header.jpg) 79 | 80 | | 字段名 | 说明 | 81 | | -------- | ----------------------------------------------------- | 82 | | id | page id | 83 | | flags | 区分 page 类型的标识 | 84 | | count | 记录 page 中的元素个数 | 85 | | overflow | 当遇到体积巨大、单个 page 无法装下的数据时,会溢出到其它 pages,overflow 记录溢出总数 | 86 | | ptr | 指向 page 数据的内存地址,*该字段仅在内存中存在* | 87 | 88 | *meta page* 89 | 90 | meta page 记录 Bolt 实例的所有元数据,它告诉用户**这是什么文件**以及**如何解读这个文件**,具体结构如下: 91 | 92 | ![meta_page_layout](./statics/imgs/storage-and-cache-meta-page-layout.jpg) 93 | 94 | | 字段名 | 说明 | 95 | | --------- | ---------------------------------------------------------- | 96 | | magic | 一个固定的 32 位随机数,用来确定该文件是一个 Bolt 数据库文件(另一种文件起始位置拥有相同数据的可能性极低) | 97 | | version | 表明该文件所属的 Bolt 版本,便于日后做兼容与迁移 | 98 | | page_size | 上文提到的 `PAGE_SIZE` | 99 | | flags | 保留字段,未使用 | 100 | | root | Bolt 实例所有**索引和原数据**被组织成一个树形结构,root 就是根节点 | 101 | | freelist | Bolt 删除数据时可能出现富余的空间,这些空间的信息会被记录在 freelist 中备用 | 102 | | pgid | 下一个要被分配的 page 的 id,取值大于已分配的所有 pages 的 id | 103 | | txid | 下一个要被分配的事务 id。事务 id 单调递增,可以被理解为事务执行的逻辑时间 | 104 | | checksum | 用于确认 meta page 数据本身的完整性,保证读取的是上一次写入的数据 | 105 | 106 | 其中 magic, version 和 checksum 一同用于[验证数据库文件的合法性](https://github.com/boltdb/bolt/blob/master/db.go#L982)。 107 | 108 | 仔细观察`1.db`的布局,你可能会问:**为什么有两份 meta page?** 这可以理解为一种本地容错方案:如果一个事务在 meta page 落盘的过程中崩溃,磁盘上的数据就可能处于不正确的状态,导致数据库文件不可用。于是 Bolt 准备了两份 meta page,暂且用 A 和 B 表示。如果上次写入的是 A,这次就写入 B,反之亦然。从而保证,读取数据库文件发现一份 meta page 失效时,可以立即将数据恢复到另一个 meta page 所记录的状态。如何恢复到上一个状态?Bolt 的思路很精简,所有发生更新的数据都写入到新的 page 中,保证数据库文件中总是保留上一次写入的数据,**用空间换取更简单的处理逻辑**。 109 | 110 | *freelist page* 111 | 112 | freelist 记录着一个有序的 page id 列表: 113 | 114 | ![freelist_page_layout](./statics/imgs/storage-and-cache-freelist-page-layout.jpg) 115 | 116 | 有序排列的 page id 方便 freelist 实施空间分配策略,具体将在 Heap File Organization 一节介绍。 117 | 118 | *branch/leaf page* 119 | 120 | Bolt 利用 B+ 树存储索引和键值数据本身,这里的 branch/leaf 指的就是 B+ 树的分支/叶子。 branch/leaf 需要存储大小不同键值数据,二者布局如下: 121 | 122 | ![storage-and-cache-branch-leaf-page-layout](./statics/imgs/storage-and-cache-branch-leaf-page-layout.jpg) 123 | 124 | 在 page header 之后,顺序地排列着 page 中每个键(值)数据的元信息,即为 element header,每个 element header 记录具体键(值)数据的位置和大小,这种布局技术也被称为 Slotted Pages。 125 | 126 | branch page 存储 B+ 树的分支节点数据,每个分支节点需要存储若干个键,用来表示子节点所包含键的上下界,其布局如下: 127 | 128 | ![branch_page](./statics/imgs/storage-and-cache-branch-page-layout.jpg) 129 | 130 | 将 element header 展开得到: 131 | 132 | ![branch_page_element_header](./statics/imgs/storage-and-cache-branch-page-element-header.jpg) 133 | 134 | | 字段名 | 说明 | 135 | | ----- | --------------- | 136 | | pos | 键的位置,即位移 | 137 | | ksize | 键的长度 | 138 | | pgid | 子节点所在 page 的 id | 139 | 140 | leaf page 存储 B+ 树的叶子节点数据,即键值对数据,其布局如下: 141 | 142 | ![leaf_page](./statics/imgs/storage-and-cache-leaf-page.jpg) 143 | 144 | 将 element header 展开得到: 145 | 146 | ![leaf_page_element_header](./statics/imgs/storage-and-cache-leaf-page-element-header.jpg) 147 | 148 | | 字段名 | 说明 | 149 | | ----- | ----------- | 150 | | flags | 保留字段,同时方便对齐 | 151 | | pos | 键值的位置,即位移 | 152 | | ksize | 键的长度 | 153 | | vsize | 值的长度 | 154 | 155 | #### 数据溢出 156 | 157 | 当需要存储超过单个 page 大小的数据时,比如: 158 | 159 | * branch/leaf page 遇到大的键值对数据 160 | * freelist page 中空闲 page 列表较长 161 | 162 | 我们该如何处理?Bolt 通过引入 overflow page 来解决这个问题,当需要存储超过单个 page 大小的数据时,就申请满足大小需求的连续 N 个 page,其中第 2 个到第 N 个是第 1 个 page 的 overflow,它们与第 1 个 page 共用 page header: 163 | 164 | ![overflow-pages](./statics/imgs/storage-and-cache-overflow-page.jpg) 165 | 166 | 这些连续的 pages 在 Bolt 眼里就是一个巨大的逻辑 page,方便统一读写逻辑。在写出 overflow page 时,Bolt 会预先根据 page header 和 element header 计算出整个 page 的大小,若它的大小超过单个 page,则申请连续的多个 pages,并记录 overflow page 数量。在读取时,同样将其看作一个逻辑 page 读入,由于 pages 之间连续,直接根据 page header 中记录的 overflow page 数量就可以读出相关数据。但有一种情况需要特别考虑: 167 | 168 | *超大的 freelist page* 169 | 170 | freelist page 中的元素都是 pgid,没有 element header,没有数据的位移信息,只能通过 page header 中的 `count` 来判断整个逻辑 page 的大小。但有可能出现 freelist 的实际长度可能超过 count(uint16) 的上限 65535 的情况,Bolt 通过特殊的 `count` 取值来处理这种问题: 171 | 172 | ```go 173 | // If the page.count is at the max uint16 value (64k) then it's considered 174 | // an overflow and the size of the freelist is stored as the first element. 175 | idx, count := 0, int(p.count) 176 | if count == 0xFFFF { 177 | idx = 1 178 | count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0]) 179 | } 180 | ``` 181 | 182 | 当 `count` 取值恰好为上限值时,就认为在 page header 之后的 8 个字节 (uint64),记录 freelist 的实际长度: 183 | 184 | ![overflow-freelist-page](./statics/imgs/storage-and-cache-overflow-freelist-page.jpg) 185 | 186 | ### Heap File Organization 187 | 188 | 数据库从操作系统申请的文件空间一般只增不减,因为实践经验告诉我们:**一旦数据库的数据存储量超过某个值,即使出现数据删除操作,在不久的将来存储量必将再次超过该值**。Bolt 也不例外,我们可以做一个实验: 189 | 190 | 1. 利用 [api/generate_1000_users.go](./api/generate_1000_users.go) 往数据库中插入一定规模的数据 191 | 2. 利用 [api/delete_buckets.go](./api/delete_buckets.go) 删除这些数据 192 | 193 | 在步骤 1 后利用 bolt stats 查看数据库文件: 194 | 195 | ```sh 196 | $ bolt stats 2.db 197 | Page count statistics 198 | Number of logical branch pages: 1 199 | Number of physical branch overflow pages: 0 200 | Number of logical leaf pages: 80 201 | Number of physical leaf overflow pages: 0 202 | ... 203 | $ du -h 2.db 204 | 356K 2.db 205 | ``` 206 | 207 | 在步骤 2 后再次查看: 208 | 209 | ```sh 210 | $ bolt stats 2.db 211 | Page count statistics 212 | Number of logical branch pages: 0 213 | Number of physical branch overflow pages: 0 214 | Number of logical leaf pages: 0 215 | Number of physical leaf overflow pages: 0 216 | ... 217 | $ du -h 2.db 218 | 356K 2.db 219 | ``` 220 | 221 | 可以看出,boltDB 并未将删除后多出的磁盘空间还给文件系统。那么 **Bolt 如何管理多出的磁盘空间呢**?这个问题就是 File Organization,而 Bolt 使用的方法被称为 Heap File Organization,可以类比程序运行时的 Heap 内存管理。实际负责管理这些磁盘空间的模块就是耳熟能详的 **freelist**。 222 | 223 | #### Freelist 224 | 225 | freelist 负责管理空闲 pages 的分配与回收。读写事务申请磁盘空间时,freelist 总是先尝试寻找一段物理上连续的满足所需大小的磁盘空间,若找到则直接分配,否则就向操作系统申请新的磁盘空间。 226 | 227 | freelist 中保存着顺序排列的空闲 page id,举例如下: 228 | 229 | ![freelist-example](./statics/imgs/storage-and-cache-freelist-example.jpg) 230 | 231 | *分配策略* 232 | 233 | 由于 freelist 总是顺序保存当前的空闲 page id,分配时就从小到大遍历一次列表,一旦发现连续的 pages 满足需求,就将其从 freelist 中移除,分配给申请空间的事务。 234 | 235 | 以上图为例,若某读写事务向 freelist 申请 2 个 page 空间,则 page 5-6 将被分配;若申请 3 个 page 空间,则 page 15-17 将被分配;若申请 5 个 page 空间,则会向操作系统申请额外的磁盘空间来分配。 236 | 237 | *释放策略* 238 | 239 | 当遇到删除数据时,读写事务会将多余的磁盘空间释放回 freelist 中,此时 freelist 不会立即将其分配出去,而是将这些释放的 page 标记成 pending 状态,直到系统中不存在任何读事务依赖这些 page 中的旧数据时,才正式将其回收再分配。场景举例如下: 240 | 241 | ![freelist-free-pages](./statics/imgs/storage-and-cache-freelist-free-pages.jpg) 242 | 243 | 只读事务 B 正在运行,这时读写事务 A 从数据库中删除了一部分数据,并将相应的磁盘空间释放回 freelist 中,若此时 freelist 立即将这些刚释放的 page 分配给下一个读写事务 C,后者可能对其修改后将其落盘,在这个过程中只读事务 B 仍然在读取数据,就可能读到相同数据的不同版本。而实际上,事务 A 释放磁盘空间时,freelist 仅仅将其标记为 pending 状态,等待类似 B 一样仍然依赖这些 page 的事务执行完毕后再将其回收再利用,从而避免数据不一致,保证事务隔离。这种释放策略在实现数据库多版本并发控制(MVCC)中尤为重要,我们将在[事务](./TX.md)一节中继续讨论这个话题。 244 | 245 | ## 缓存 246 | 247 | 计算机在内存中读写数据的速度要远远快于在磁盘中读写数据。通过预测数据的读取模式,缓存未来可能再次用到的数据避免重复从磁盘读取,就能提高数据库读效率;写出数据时,合理地将短期内不会再被修改的脏数据批量写出,减少写出频率,就能提高数据库写效率。以上两部分正是缓存层的主要工作。 248 | 249 | ### 读缓存:mmap 250 | 251 | mmap 是操作系统提供的系统调用,它将系统中的某个文件或设备的数据直接映射到一块大小给定的虚拟内存中,这块虚拟内存可以大于或小于该文件或设备的实际所占空间大小。映射完毕后,应用程序可以认为 (实际并不会) 映射对象的数据已经全部读入内存中,任意访问这块虚拟内存中的数据,操作系统在背后全权负责数据的缓冲读写。 252 | 253 | Bolt 利用 mmap 管理数据的读缓冲,即让操作系统来决定何时将数据读入内存,当缓冲区大小不足时如何选择被置换的数据,Bolt 只管像访问字节数组一样访问数据库文件即可。当然,使用 mmap 也失去了数据读取的控制权,无法根据数据库系统的运行逻辑来优化数据预取 (prefetching) 及缓存置换 (buffer replacement)。话虽如此,实际情况是 Bolt 每次都选择将数据库文件 mmap 到一块不小于数据库文件本身大小的虚拟内存中,因此实际上只有 demand paging,没有 buffer replacement。 254 | 255 | 以以下的简化代码为例: 256 | 257 | ```go 258 | func mmap(db *DB, sz int) error { 259 | b, _ := syscall.Mmap(fd, 0, sz, flags) 260 | db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0])) 261 | } 262 | ``` 263 | 264 | 在执行 mmap 函数后,Bolt 实例得到一个内存地址。在 boltDB 实例眼里,以这个地址为起点的一段连续内存空间中映射着数据库文件,它可以随机访问其中的数据: 265 | 266 | ```go 267 | func (db *DB) page(id pgid) *page { 268 | pos := id * pgid(db.pageSize) 269 | return (*page)(unsafe.Pointer(&db.data[pos])) 270 | } 271 | ``` 272 | 273 | ### 写缓存 274 | 275 | 读写事务提交时,会将所有脏页 (dirty pages) 落盘。落盘前,Bolt 会将这些 page 按 page id 升序排列,然后依次写出到磁盘中。由于数据库文件中的所有 page 按 id 升序排列并两两相邻,这样写出就是顺序写,对块存储设备十分友好。 276 | 277 | # 参考 278 | 279 | * [linux: mmap2](http://man7.org/linux/man-pages/man2/mmap.2.html) 280 | * [CMU 15-445/645 Database Systems (Fall 2020):: Database Storage I](https://15445.courses.cs.cmu.edu/fall2020/slides/03-storage1.pdf) 281 | -------------------------------------------------------------------------------- /TX.md: -------------------------------------------------------------------------------- 1 | # 事务 2 | 3 | 事务是数据库提供给应用程序的一层抽象,这层抽象将所有并发控制、软硬件各种异常信息隐藏,只对应用暴露出两种状态:成功(commit)和终止(abort)。读写事务中的数据修改要么执行完成、要么回滚到修改前的状态,不存在执行到一半的中间态。有了事务支持,应用遇到执行失败时按需重试即可。Bolt 提供**可序列化(serializable)的、满足 ACID** 的事务支持,本节就来讨论一下 Bolt 的相关实现。 4 | 5 | ## 目录 6 | 7 | * 并发控制 8 | * 数据库文件: flock 9 | * 数据库实例: DB.rwlock 10 | * 瓶颈和场景 11 | * 执行过程 12 | * ACID 13 | * MVCC 14 | * 可序列化 15 | 16 | ## 并发控制 17 | 18 | Bolt 只允许单个读写事务同时执行。但是在使用过程中,不同进程可以访问相同的数据库文件,单个进程也可以同时开启多个事务,因此 Bolt 既需要在数据库文件层面,也需要在单个数据库实例层面完成相应的并发控控制。 19 | 20 | ### 数据库文件: flock 21 | 22 | flock 是操作系统提供的基于某个文件描述符的锁,支持共享和互斥两种模式。由于每个 Bolt 实例各自对应一个独立的文件,flock 就自然而然地成为数据库文件层面并发控制的首选。 23 | 24 | 以下是相应的加锁逻辑: 25 | 26 | ```go 27 | func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error { 28 | // ... 29 | flag := syscall.LOCK_SH // 共享 30 | if exclusive { 31 | flag = syscall.LOCK_EX // 互斥 32 | } 33 | 34 | err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB) 35 | // ... 36 | } 37 | ``` 38 | 39 | 若用户选择只读模式打开 Bolt 实例, 40 | 41 | ```go 42 | db, err := bolt.Open("1.db", 0600, &bolt.Options{ReadOnly: true}) 43 | ``` 44 | 45 | Bolt 实例会获取`1.db`的共享锁,若用户以非只读模式打开,则将获取`1.db`的互斥锁。这样就不可能有超过一个进程以非只读模式打开数据库文件,从而实现文件级别的并发控制。 46 | 47 | ### 数据库实例: DB.rwlock 48 | 49 | 每个 Bolt 实例都有一个 rwlock,用于保证同时在执行的读写事务数量最多只有一个,相应的逻辑在每个读写事务 Begin 的子逻辑 `DB.beginRWTx` 中: 50 | 51 | ```go 52 | func (db *DB) beginRWTx() (*Tx, error) { 53 | // ... 54 | 55 | // Obtain writer lock. This is released by the transaction when it closes. 56 | // This enforces only one writer transaction at a time. 57 | db.rwlock.Lock() 58 | 59 | // ... 60 | } 61 | ``` 62 | 63 | ### 瓶颈和场景 64 | 65 | Bolt 这种粗粒度的并发控制意味着,即便不同事务读写的数据互不相关,只要有事务在读取整个数据库文件上的任意数据,其它事务就不能往数据库里写数据。因此使用 Bolt 时,每个读写事务的执行时间不宜太长,数据量不宜过大。如果遇到一些吞吐大的写场景,就要求数据库能够支持更细粒度的并发控制,比如支持在命名空间级别、键级别上加锁,用 Bolt 也许不是最佳选择。 66 | 67 | ## 执行过程 68 | 69 | Bolt 支持的事务,按是否修改数据来划分,有只读事务和读写事务;按谁来管理事务生命周期,有隐式事务和显示事务。在隐式事务中,Bolt 负责管理事务的生命周期,如初始化、执行、关闭、回滚事务;在显式事务中,用户需要自行管理相关资源的申请和回收。一个典型的显式事务执行过程如下: 70 | 71 | ```go 72 | func Explicit(db *DB, fn func(*Tx) error) error { 73 | t := db.Begin(true) 74 | // ... 75 | err := fn(t) // 数据存取逻辑 76 | if err != nil { 77 | t.Rollback() 78 | return err 79 | } 80 | return t.Commit() 81 | } 82 | ``` 83 | 84 | 在隐式事务中,用户只需要关心数据存取逻辑 `fn` 即可。 85 | 86 | ## ACID 87 | 88 | * 原子性(A): 单个读写事务要么执行成功,要么回滚; 89 | * 一致性(C): 只要程序正确合理地使用事务操纵数据,Bolt 保证原子性和可序列化隔离级别,就能保证一致; 90 | * 隔离性(I): Bolt 只允许一个读写事务同时执行,所有读写事务只能顺序执行,满足可序列化数据隔离级别; 91 | * 持久性(D):每次读写事务执行`t.Commit()`提交时,就会将脏数据写入磁盘(手动开启`NoSync`除外),如果落盘失败则回滚。因此事务执行返回且无错误发生时,数据即已持久化。 92 | 93 | ## MVCC(Multi-version concurrency control) 94 | 95 | 一般数据库中如果有多个事务并发执行,就可能出现多版本数据。如果处理不好「修改的数据什么时候对哪些事务可见」的问题,就可能出现数据不一致的问题,这些问题与 ACID 中的 I 和 C 息息相关。我们先用一个具体的例子来说明多版本数据存在的必要性。 96 | 97 | ![transactional-memory](./statics/imgs/tx-transactional-memory.jpg) 98 | 99 | 以上图为例:数据库里的数据在不同时刻应该满足: 100 | 101 | | 时刻 | 满足条件 | 102 | |-----|---------| 103 | | t < t1 | 未发生任何变化,记为 v1 版本 | 104 | | t1 <= t < t2 | RW-Tx-1 完成后落盘的数据,记为 v2 版本 | 105 | | t2 <= t < t3 | RW-Tx-1 和 RW-Tx-2 完成后落盘的数据,记为 v3 版本 | 106 | | t >= t3 | RW-Tx-1、RW-Tx-2 和 RW-Tx-3 完成后落盘的数据,记为 v4 版本 | 107 | 108 | 如果只读事务从开始到结束都落在单个区间内,它能看到的数据应为对应区间内数据所属的版本。但如果只读事务跨越 t1、t2、t3 的边界怎么办?如只读事务 A 在 t1 之前开始,t3 之后结束,那么它应该读取到哪些数据?v1、v2、v3、v4 都有可能,如果是 v2 或 v3 会让用户感到疑惑,不能肯定自己每次能读到的数据版本。比较合理的做法应该是 t1 之前的数据或 t3 之后的数据,即 v1 或 v4。但如果取 v4,为了保证只读事务读取的数据是一致的,该只读事务将被阻塞直到 t3 之后才被执行从而给数据库带来负担。因此,通常事务 A 在结束前读到的数据都应该是 v1 版本的数据。 109 | 110 | 在多事务并发执行的场景下,要做一款合格的数据库,需要保存数据的多个版本,这就是 MVCC。在数据库中存储单个数据的多个版本,可以**让只读事务不阻塞读写事务的进行、读写事务也不阻塞只读事务的进行**,只读事务只会读取到该事务启动时已经提交的所有读写事务的所有数据,在只读事务启动后写入的数据不会被只读事务访问到。 111 | 112 | Bolt 最多只允许一个读写事务同时进行,因此它支持 MVCC 时不需要考虑多个读写事务同时进行的场景。只允许一个读写事务同时进行是否意味着 Bolt 只需要同时保存最多 2 个版本的数据?看个例子: 113 | 114 | ![boltdb-mvcc](./statics/imgs/tx-boltdb-mvcc-example.jpg) 115 | 116 | 横轴上方是读写事务,下方是只读事务。在 t3 到 t4 之间,只读事务 RO-Tx-1 要求 v1 版本数据还保存在数据库中;RO-Tx-2 要求 v2 版本数据还保存在数据库中;RO-Tx-3 要求 v3 版本数据还保存在数据库中。因此即使只允许一个读写事务同时进行,数据库仍然需要保存多版本的数据。 117 | 118 | 我们在[存储与缓存](./STORAGE_AND_CACHE.md)中介绍过,Bolt 将数据库文件分成大小相等的 page 来存储元数据和数据,如下图所示: 119 | 120 | ![boltdb-page](./statics/imgs/tx-boltdb-page.jpg) 121 | 122 | 如果每个 page 都带有版本,每当 page 中的数据将被读写事务修改时,就先复制数据到新申请的 page 中,然后修改相应的数据,旧版本的 page 直到没有只读事务依赖它时才被回收。使用这种方案使得 MVCC 的实现无需考虑新旧版本共存于同一个 page 的情况,用空间换取了设计复杂度的降低,符合 Bolt 的设计理念。下面罗列一些值得关注的细节: 123 | 124 | *meta page* 125 | 126 | meta page 存储数据库的元信息,包括 root bucket 等。在读写事务执行过程中,可能在增删改键值数据的过程中修改 root bucket,引起 meta page 的变化。因此在初始化事务时,每个事务都需要复制一份独立 meta,以防止读写事务的执行影响到只读事务。 127 | 128 | *freelist page* 129 | 130 | freelist 负责记录整个实例的可分配 page 信息,在读写事务执行过程中,会从 freelist 中申请新的 pages,也会释放 pages 到 freelist 中,引起 freelist page 的变化。由于 Bolt 只允许一个读写事务同时进行,且只有读写事务需要访问 freelist page,因此 freelist page 全局只存一份即可,无需复制。 131 | 132 | *mmap* 133 | 134 | 在数据存储层一节中介绍过,Bolt 将数据的读缓存托管给 mmap。每个只读事务在启动时需要获取 mmap 的读锁,保证所读取数据的正确性;当读写事务申请新 pages 时,可能出现当前 mmap 的空间不足,需要重新 mmap 的情况,这时读写事务要获取 mmap 的写锁,就需要等待所有只读事务执行完毕后才能继续。因此 Bolt 也建议用户,如果可能出现执行时间较长的只读事务,务必将 mmap 的初始大小调高一些。 135 | 136 | *版本号* 137 | 138 | 每当 Bolt 执行新的读写事务,就有可能产生新版本的数据,因此只要读写事务的 id 是单调递增的,就可以利用事务 id 作为数据的版本号。 139 | 140 | ### 可序列化 141 | 142 | 数据库用户一般认为库里的数据是「单调突变」的,这种「单调突变」用计算机语言来描述,就是:**数据库状态看起来是以读写事务为单位,顺序地、原子地发生变化**,其实这就是最严格的可序列化事务隔离级别。一个 Bolt 实例中最多允许一个读写事务同时执行,不仅做到了「看起来是」,而且「实际就是」。 143 | 144 | ## 参考 145 | 146 | * Designing Data-Intensive Applications - Transactions 147 | * [linux: flock](http://man7.org/linux/man-pages/man2/flock.2.html) 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /api/batch_create.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ZhengHe-MD/learn-bolt/api/lib" 6 | "github.com/boltdb/bolt" 7 | "log" 8 | "time" 9 | ) 10 | 11 | func estimateDuration(d *lib.Store, title string, f func(d *lib.Store) error) (err error) { 12 | if err = d.EnsureBuckets(); err != nil { 13 | return 14 | } 15 | 16 | st := time.Now() 17 | if err = f(d); err != nil { 18 | return 19 | } 20 | log.Printf("%s takes:%v", title, time.Since(st)) 21 | 22 | if err = d.CleanupBuckets(); err != nil { 23 | return 24 | } 25 | 26 | return 27 | } 28 | 29 | func main() { 30 | db, err := bolt.Open("2.db", 0600, &bolt.Options{ 31 | // 进程 Open DB 会给 DB 文件加锁,只有一个进 32 | Timeout: 0, 33 | NoGrowSync: false, 34 | // 只读副本使用 35 | ReadOnly: false, 36 | MmapFlags: 0, 37 | InitialMmapSize: 0, 38 | }) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | defer db.Close() 43 | 44 | store := lib.NewStore(db) 45 | 46 | _ = store.CleanupBuckets() 47 | 48 | n := 1000 49 | 50 | for ng := 1; ng <= 100; ng++ { 51 | _ = estimateDuration( 52 | store, 53 | fmt.Sprintf("insert data in batch, with %d goroutine", ng), 54 | func(d *lib.Store) error { 55 | return d.GenerateFakeUserDataConcurrently(n, ng) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /api/batch_create.txt: -------------------------------------------------------------------------------- 1 | 2019/07/13 09:49:29 seed gofakeit 2 | 2019/07/13 09:49:51 insert data one by one takes:21.82452158s 3 | 2019/07/13 09:50:24 insert data in batch, with 1 goroutine takes:33.638457812s 4 | 2019/07/13 09:50:41 insert data in batch, with 2 goroutine takes:16.880093899s 5 | 2019/07/13 09:50:53 insert data in batch, with 3 goroutine takes:11.122125192s 6 | 2019/07/13 09:51:01 insert data in batch, with 4 goroutine takes:8.377929688s 7 | 2019/07/13 09:51:07 insert data in batch, with 5 goroutine takes:6.466934463s 8 | 2019/07/13 09:51:13 insert data in batch, with 6 goroutine takes:5.778274724s 9 | 2019/07/13 09:51:19 insert data in batch, with 7 goroutine takes:5.239121107s 10 | 2019/07/13 09:51:23 insert data in batch, with 8 goroutine takes:4.357364648s 11 | 2019/07/13 09:51:27 insert data in batch, with 9 goroutine takes:3.737384322s 12 | 2019/07/13 09:51:30 insert data in batch, with 10 goroutine takes:3.380839546s 13 | 2019/07/13 09:51:33 insert data in batch, with 11 goroutine takes:3.012669482s 14 | 2019/07/13 09:51:36 insert data in batch, with 12 goroutine takes:2.737584523s 15 | 2019/07/13 09:51:39 insert data in batch, with 13 goroutine takes:2.6100757s 16 | 2019/07/13 09:51:41 insert data in batch, with 14 goroutine takes:2.600656464s 17 | 2019/07/13 09:51:44 insert data in batch, with 15 goroutine takes:2.132445352s 18 | 2019/07/13 09:51:46 insert data in batch, with 16 goroutine takes:2.091812142s 19 | 2019/07/13 09:51:48 insert data in batch, with 17 goroutine takes:1.922468734s 20 | 2019/07/13 09:51:49 insert data in batch, with 18 goroutine takes:1.764378052s 21 | 2019/07/13 09:51:51 insert data in batch, with 19 goroutine takes:1.752452168s 22 | 2019/07/13 09:51:53 insert data in batch, with 20 goroutine takes:1.689196608s 23 | 2019/07/13 09:51:55 insert data in batch, with 21 goroutine takes:1.638496867s 24 | 2019/07/13 09:51:56 insert data in batch, with 22 goroutine takes:1.481378909s 25 | 2019/07/13 09:51:58 insert data in batch, with 23 goroutine takes:1.386163138s 26 | 2019/07/13 09:51:59 insert data in batch, with 24 goroutine takes:1.40664272s 27 | 2019/07/13 09:52:00 insert data in batch, with 25 goroutine takes:1.338634013s 28 | 2019/07/13 09:52:02 insert data in batch, with 26 goroutine takes:1.251111695s 29 | 2019/07/13 09:52:03 insert data in batch, with 27 goroutine takes:1.241444222s 30 | 2019/07/13 09:52:04 insert data in batch, with 28 goroutine takes:1.194692448s 31 | 2019/07/13 09:52:05 insert data in batch, with 29 goroutine takes:1.165278627s 32 | 2019/07/13 09:52:07 insert data in batch, with 30 goroutine takes:1.102905874s 33 | 2019/07/13 09:52:08 insert data in batch, with 31 goroutine takes:1.122461607s 34 | 2019/07/13 09:52:09 insert data in batch, with 32 goroutine takes:1.090747392s 35 | 2019/07/13 09:52:10 insert data in batch, with 33 goroutine takes:1.09534357s 36 | 2019/07/13 09:52:11 insert data in batch, with 34 goroutine takes:976.709095ms 37 | 2019/07/13 09:52:12 insert data in batch, with 35 goroutine takes:943.466991ms 38 | 2019/07/13 09:52:13 insert data in batch, with 36 goroutine takes:901.925286ms 39 | 2019/07/13 09:52:14 insert data in batch, with 37 goroutine takes:924.560299ms 40 | 2019/07/13 09:52:15 insert data in batch, with 38 goroutine takes:888.834869ms 41 | 2019/07/13 09:52:16 insert data in batch, with 39 goroutine takes:838.779321ms 42 | 2019/07/13 09:52:17 insert data in batch, with 40 goroutine takes:844.145955ms 43 | 2019/07/13 09:52:17 insert data in batch, with 41 goroutine takes:800.467255ms 44 | 2019/07/13 09:52:18 insert data in batch, with 42 goroutine takes:774.289148ms 45 | 2019/07/13 09:52:19 insert data in batch, with 43 goroutine takes:771.886749ms 46 | 2019/07/13 09:52:20 insert data in batch, with 44 goroutine takes:711.398774ms 47 | 2019/07/13 09:52:21 insert data in batch, with 45 goroutine takes:738.639737ms 48 | 2019/07/13 09:52:21 insert data in batch, with 46 goroutine takes:691.345062ms 49 | 2019/07/13 09:52:22 insert data in batch, with 47 goroutine takes:708.501981ms 50 | 2019/07/13 09:52:23 insert data in batch, with 48 goroutine takes:676.752264ms 51 | 2019/07/13 09:52:24 insert data in batch, with 49 goroutine takes:684.737272ms 52 | 2019/07/13 09:52:24 insert data in batch, with 50 goroutine takes:689.263853ms 53 | 2019/07/13 09:52:25 insert data in batch, with 51 goroutine takes:658.921888ms 54 | 2019/07/13 09:52:26 insert data in batch, with 52 goroutine takes:623.648257ms 55 | 2019/07/13 09:52:26 insert data in batch, with 53 goroutine takes:596.588128ms 56 | 2019/07/13 09:52:27 insert data in batch, with 54 goroutine takes:621.745786ms 57 | 2019/07/13 09:52:28 insert data in batch, with 55 goroutine takes:593.625499ms 58 | 2019/07/13 09:52:28 insert data in batch, with 56 goroutine takes:563.145676ms 59 | 2019/07/13 09:52:29 insert data in batch, with 57 goroutine takes:562.31152ms 60 | 2019/07/13 09:52:29 insert data in batch, with 58 goroutine takes:582.986793ms 61 | 2019/07/13 09:52:30 insert data in batch, with 59 goroutine takes:517.445348ms 62 | 2019/07/13 09:52:31 insert data in batch, with 60 goroutine takes:546.848616ms 63 | 2019/07/13 09:52:31 insert data in batch, with 61 goroutine takes:515.044707ms 64 | 2019/07/13 09:52:32 insert data in batch, with 62 goroutine takes:538.044091ms 65 | 2019/07/13 09:52:32 insert data in batch, with 63 goroutine takes:491.512781ms 66 | 2019/07/13 09:52:33 insert data in batch, with 64 goroutine takes:521.021084ms 67 | 2019/07/13 09:52:33 insert data in batch, with 65 goroutine takes:492.344877ms 68 | 2019/07/13 09:52:34 insert data in batch, with 66 goroutine takes:534.102879ms 69 | 2019/07/13 09:52:34 insert data in batch, with 67 goroutine takes:451.946036ms 70 | 2019/07/13 09:52:35 insert data in batch, with 68 goroutine takes:489.784276ms 71 | 2019/07/13 09:52:36 insert data in batch, with 69 goroutine takes:469.1912ms 72 | 2019/07/13 09:52:36 insert data in batch, with 70 goroutine takes:457.190014ms 73 | 2019/07/13 09:52:37 insert data in batch, with 71 goroutine takes:469.344577ms 74 | 2019/07/13 09:52:37 insert data in batch, with 72 goroutine takes:468.156596ms 75 | 2019/07/13 09:52:38 insert data in batch, with 73 goroutine takes:441.273187ms 76 | 2019/07/13 09:52:38 insert data in batch, with 74 goroutine takes:428.697491ms 77 | 2019/07/13 09:52:38 insert data in batch, with 75 goroutine takes:433.676225ms 78 | 2019/07/13 09:52:39 insert data in batch, with 76 goroutine takes:433.771432ms 79 | 2019/07/13 09:52:39 insert data in batch, with 77 goroutine takes:405.890019ms 80 | 2019/07/13 09:52:40 insert data in batch, with 78 goroutine takes:395.221908ms 81 | 2019/07/13 09:52:40 insert data in batch, with 79 goroutine takes:392.366046ms 82 | 2019/07/13 09:52:41 insert data in batch, with 80 goroutine takes:421.112477ms 83 | 2019/07/13 09:52:41 insert data in batch, with 81 goroutine takes:394.111369ms 84 | 2019/07/13 09:52:42 insert data in batch, with 82 goroutine takes:396.784262ms 85 | 2019/07/13 09:52:42 insert data in batch, with 83 goroutine takes:405.683125ms 86 | 2019/07/13 09:52:42 insert data in batch, with 84 goroutine takes:368.378117ms 87 | 2019/07/13 09:52:43 insert data in batch, with 85 goroutine takes:369.394356ms 88 | 2019/07/13 09:52:43 insert data in batch, with 86 goroutine takes:380.566011ms 89 | 2019/07/13 09:52:44 insert data in batch, with 87 goroutine takes:391.269234ms 90 | 2019/07/13 09:52:44 insert data in batch, with 88 goroutine takes:378.690441ms 91 | 2019/07/13 09:52:45 insert data in batch, with 89 goroutine takes:365.868518ms 92 | 2019/07/13 09:52:45 insert data in batch, with 90 goroutine takes:380.894787ms 93 | 2019/07/13 09:52:45 insert data in batch, with 91 goroutine takes:342.536615ms 94 | 2019/07/13 09:52:46 insert data in batch, with 92 goroutine takes:341.246944ms 95 | 2019/07/13 09:52:46 insert data in batch, with 93 goroutine takes:335.791364ms 96 | 2019/07/13 09:52:47 insert data in batch, with 94 goroutine takes:347.72164ms 97 | 2019/07/13 09:52:47 insert data in batch, with 95 goroutine takes:333.575765ms 98 | 2019/07/13 09:52:47 insert data in batch, with 96 goroutine takes:345.427544ms 99 | 2019/07/13 09:52:48 insert data in batch, with 97 goroutine takes:326.513916ms 100 | 2019/07/13 09:52:48 insert data in batch, with 98 goroutine takes:329.335024ms 101 | 2019/07/13 09:52:48 insert data in batch, with 99 goroutine takes:340.044998ms 102 | 2019/07/13 09:52:49 insert data in batch, with 100 goroutine takes:330.204915ms 103 | -------------------------------------------------------------------------------- /api/delete_buckets.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ZhengHe-MD/learn-bolt/api/lib" 5 | "github.com/boltdb/bolt" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | db, err := bolt.Open("2.db", 0600, nil) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | 15 | store := lib.NewStore(db) 16 | 17 | _ = store.CleanupBuckets() 18 | } 19 | -------------------------------------------------------------------------------- /api/generate_1000_users.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ZhengHe-MD/learn-bolt/api/lib" 5 | "github.com/boltdb/bolt" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | db, err := bolt.Open("2.db", 0600, nil) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | defer db.Close() 15 | 16 | store := lib.NewStore(db) 17 | if err = store.EnsureBuckets(); err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | err = store.GenerateFakeUserDataConcurrently(1000, 20) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api/lib/crud.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/boltdb/bolt" 7 | ) 8 | 9 | type User struct { 10 | ID uint64 11 | Name string 12 | Gender uint8 13 | Age uint8 14 | Phone string 15 | Email string 16 | CreatedAt int64 17 | } 18 | 19 | type UserDao struct { 20 | DB *bolt.DB 21 | } 22 | 23 | func NewUserDao(db *bolt.DB) *UserDao { 24 | return &UserDao{db} 25 | } 26 | 27 | func (d *UserDao) genCreateUserFunc(user *User) func(*bolt.Tx) error { 28 | return func(tx *bolt.Tx) error { 29 | b := tx.Bucket([]byte(BucketUsers)) 30 | 31 | id, err := b.NextSequence() 32 | if err != nil { 33 | return err 34 | } 35 | user.ID = id 36 | 37 | buf, err := json.Marshal(user) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | return b.Put(uint64tob(user.ID), buf) 43 | } 44 | } 45 | 46 | func (d *UserDao) CreateUser(user *User) error { 47 | return d.DB.Update(d.genCreateUserFunc(user)) 48 | } 49 | 50 | func (d *UserDao) CreateUserInBatch(user *User) error { 51 | return d.DB.Batch(d.genCreateUserFunc(user)) 52 | } 53 | 54 | func (d *UserDao) GetUserByID(id uint64) (user *User, err error) { 55 | user = &User{} 56 | err = d.DB.View(func(tx *bolt.Tx) error { 57 | b := tx.Bucket([]byte(BucketUsers)) 58 | 59 | buf := b.Get(uint64tob(id)) 60 | 61 | bufCopy := make([]byte, len(buf)) 62 | copy(bufCopy, buf) 63 | 64 | err := json.Unmarshal(bufCopy, user) 65 | 66 | if err != nil { 67 | return err 68 | } 69 | return nil 70 | }) 71 | return 72 | } 73 | 74 | func (d *UserDao) GetUsers() (users []*User, err error) { 75 | err = d.DB.View(func(tx *bolt.Tx) error { 76 | b := tx.Bucket([]byte(BucketUsers)) 77 | 78 | return b.ForEach(func(k, v []byte) error { 79 | var user User 80 | if err := json.Unmarshal(v, &user); err != nil { 81 | return err 82 | } 83 | users = append(users, &user) 84 | return nil 85 | }) 86 | }) 87 | 88 | return 89 | } 90 | 91 | func (d *UserDao) PutUser(user *User) error { 92 | return d.DB.Update(func(tx *bolt.Tx) error { 93 | b := tx.Bucket([]byte(BucketUsers)) 94 | 95 | buf, err := json.Marshal(user) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | return b.Put(uint64tob(user.ID), buf) 101 | }) 102 | } 103 | 104 | func (d *UserDao) DeleteUserByID(id uint64) error { 105 | return d.DB.Update(func(tx *bolt.Tx) error { 106 | b := tx.Bucket([]byte(BucketUsers)) 107 | return b.Delete(uint64tob(id)) 108 | }) 109 | } 110 | 111 | type Event struct { 112 | Time int64 113 | Name string 114 | Type uint8 115 | Cancel bool 116 | } 117 | 118 | type EventDao struct { 119 | DB *bolt.DB 120 | } 121 | 122 | func NewEventDao(db *bolt.DB) *EventDao { 123 | return &EventDao{db} 124 | } 125 | 126 | func (d *EventDao) genCreateEventFunc(event *Event) func(*bolt.Tx) error { 127 | return func(tx *bolt.Tx) error { 128 | b := tx.Bucket([]byte(BucketEvents)) 129 | 130 | buf, err := json.Marshal(event) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | return b.Put(uint64tob(uint64(event.Time)), buf) 136 | } 137 | } 138 | 139 | func (d *EventDao) CreateEventInBatch(event *Event) error { 140 | return d.DB.Batch(d.genCreateEventFunc(event)) 141 | } 142 | 143 | func (d *EventDao) CreateEvent(event *Event) error { 144 | return d.DB.Update(d.genCreateEventFunc(event)) 145 | } 146 | 147 | func (d *EventDao) GetEventsBetween(start, end int64) (events []*Event, err error) { 148 | err = d.DB.View(func(tx *bolt.Tx) error { 149 | c := tx.Bucket([]byte(BucketEvents)).Cursor() 150 | 151 | min := uint64tob(uint64(start)) 152 | max := uint64tob(uint64(end)) 153 | 154 | for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { 155 | var event Event 156 | if err := json.Unmarshal(v, &event); err != nil { 157 | return err 158 | } 159 | events = append(events, &event) 160 | } 161 | return nil 162 | }) 163 | return 164 | } 165 | 166 | type BucketDao struct { 167 | DB *bolt.DB 168 | } 169 | 170 | func NewBucketDao(db *bolt.DB) *BucketDao { 171 | return &BucketDao{db} 172 | } 173 | 174 | func (d *BucketDao) CreateBucket(name []byte) error { 175 | return d.DB.Update(func(tx *bolt.Tx) error { 176 | _, err := tx.CreateBucketIfNotExists(name) 177 | return err 178 | }) 179 | } 180 | 181 | func (d *BucketDao) DeleteBucket(name []byte) error { 182 | return d.DB.Update(func(tx *bolt.Tx) error { 183 | return tx.DeleteBucket(name) 184 | }) 185 | } -------------------------------------------------------------------------------- /api/lib/db.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "github.com/boltdb/bolt" 5 | "github.com/brianvoe/gofakeit" 6 | "log" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | const ( 12 | BucketUsers = "Users" 13 | BucketEvents = "Events" 14 | ) 15 | 16 | var Buckets = []string{ 17 | BucketUsers, 18 | BucketEvents, 19 | } 20 | 21 | type Store struct { 22 | Users *UserDao 23 | Events *EventDao 24 | Buckets *BucketDao 25 | } 26 | 27 | func NewStore(db *bolt.DB) *Store { 28 | return &Store{ 29 | Users: NewUserDao(db), 30 | Events: NewEventDao(db), 31 | Buckets: NewBucketDao(db), 32 | } 33 | } 34 | 35 | func (d *Store) EnsureBuckets() error { 36 | for _, bucket := range Buckets { 37 | if err := d.Buckets.CreateBucket([]byte(bucket)); err != nil { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | 44 | func (d *Store) CleanupBuckets() error { 45 | for _, bucket := range Buckets { 46 | if err := d.Buckets.DeleteBucket([]byte(bucket)); err != nil { 47 | return err 48 | } 49 | } 50 | return nil 51 | } 52 | 53 | func newFakeUser() *User { 54 | return &User{ 55 | Name: gofakeit.Name(), 56 | Gender: uint8(gofakeit.Number(0, 1)), 57 | Age: uint8(gofakeit.Number(0, 100)), 58 | Phone: gofakeit.Phone(), 59 | Email: gofakeit.Email(), 60 | CreatedAt: randInt64Range( 61 | time.Now().Unix()-60*60*24*365, 62 | time.Now().Unix(), 63 | ), 64 | } 65 | } 66 | 67 | func newFakeEvent() *Event { 68 | return &Event{ 69 | Time: randInt64Range( 70 | time.Now().Unix()-60*60*24*365, 71 | time.Now().Unix()), 72 | Name: gofakeit.Name(), 73 | Type: gofakeit.Uint8(), 74 | Cancel: gofakeit.Bool(), 75 | } 76 | } 77 | 78 | func (d *Store) GenerateFakeUserData(n int) error { 79 | for i := 0; i < n; i++ { 80 | if err := d.Users.CreateUser(newFakeUser()); err != nil { 81 | return err 82 | } 83 | } 84 | return nil 85 | } 86 | 87 | func (d *Store) GenerateFakeEventData(n int) error { 88 | for i := 0; i < n; i++ { 89 | if err := d.Events.CreateEvent(newFakeEvent()); err != nil { 90 | return err 91 | } 92 | } 93 | return nil 94 | } 95 | 96 | func (d *Store) generateFakeUserDataInBatch(n int) error { 97 | for i := 0; i < n; i++ { 98 | if err := d.Users.CreateUserInBatch(newFakeUser()); err != nil { 99 | return err 100 | } 101 | } 102 | return nil 103 | } 104 | 105 | func (d *Store) generateFakeEventDataInBatch(n int) error { 106 | for i := 0; i < n; i++ { 107 | if err := d.Events.CreateEventInBatch(newFakeEvent()); err != nil { 108 | return err 109 | } 110 | } 111 | return nil 112 | } 113 | 114 | // n should be multiples of ng 115 | func (d *Store) GenerateFakeUserDataConcurrently(n int, ng int) error { 116 | var wg sync.WaitGroup 117 | wg.Add(ng) 118 | for i := 0; i < ng; i++ { 119 | go func() { 120 | _ = d.generateFakeUserDataInBatch(n/ng) 121 | wg.Done() 122 | }() 123 | } 124 | wg.Wait() 125 | return nil 126 | } 127 | 128 | func (d *Store) GenerateFakeEventConcurrently(n int, ng int) error { 129 | var wg sync.WaitGroup 130 | wg.Add(ng) 131 | for i := 0; i < ng; i++ { 132 | go func() { 133 | _ = d.generateFakeEventDataInBatch(n/ng) 134 | wg.Done() 135 | }() 136 | } 137 | wg.Wait() 138 | return nil 139 | } 140 | 141 | func init() { 142 | log.Println("seed gofakeit") 143 | gofakeit.Seed(time.Now().UnixNano()) 144 | } -------------------------------------------------------------------------------- /api/lib/util.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/binary" 5 | "math/rand" 6 | ) 7 | 8 | func uint64tob(v uint64) []byte { 9 | b := make([]byte, 8) 10 | binary.BigEndian.PutUint64(b, v) 11 | return b 12 | } 13 | 14 | func randInt64Range(min, max int64) int64 { 15 | if min == max { 16 | return min 17 | } 18 | return rand.Int63n((max+1)-min) + min 19 | } 20 | 21 | -------------------------------------------------------------------------------- /api/open.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/boltdb/bolt" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | db, err := bolt.Open("1.db", 0600, nil) 10 | if err != nil { 11 | log.Fatal(err) 12 | } 13 | defer db.Close() 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /api/range_query.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ZhengHe-MD/learn-bolt/api/lib" 6 | "github.com/boltdb/bolt" 7 | "log" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | db, err := bolt.Open("3.db", 0600, nil) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | defer db.Close() 17 | 18 | store := lib.NewStore(db) 19 | 20 | _ = store.CleanupBuckets() 21 | _ = store.EnsureBuckets() 22 | 23 | n := 1000 24 | 25 | if err = store.GenerateFakeEventConcurrently(n, 8); err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | start := time.Now().Unix() - 60*60*24*90 30 | end := time.Now().Unix() - 60*60*24*30 31 | 32 | events, err := store.Events.GetEventsBetween(start, end) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | for _, event := range events { 38 | if event.Time < start || event.Time > end { 39 | log.Printf("event:%v should not be in events", event) 40 | } 41 | } 42 | 43 | fmt.Printf("total:%d", len(events)) 44 | } 45 | -------------------------------------------------------------------------------- /bucket/create_bucket.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/boltdb/bolt" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | db, err := bolt.Open("1.db", 0600, nil) 10 | if err != nil { 11 | log.Fatal(err) 12 | } 13 | defer db.Close() 14 | 15 | _ = db.Update(func(tx *bolt.Tx) error { 16 | _, err := tx.CreateBucketIfNotExists([]byte("bucket3")) 17 | return err 18 | }) 19 | } -------------------------------------------------------------------------------- /bucket/visitkv.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/boltdb/bolt" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | db, err := bolt.Open("1.db", 0600, nil) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | defer db.Close() 15 | 16 | err = db.Update(func(tx *bolt.Tx) error { 17 | b1, err := tx.CreateBucketIfNotExists([]byte("b1")) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | _, err = b1.CreateBucketIfNotExists([]byte("b11")) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | _ = b1.Put([]byte("k1"), []byte("v1")) 28 | _ = b1.Put([]byte("k2"), []byte("v2")) 29 | 30 | return b1.ForEach(func(k, v []byte) error { 31 | fmt.Printf("key: %s, val: %s, nil: %v\n", k, v, v == nil) 32 | return nil 33 | }) 34 | }) 35 | 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ZhengHe-MD/learn-bolt 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/boltdb/bolt v1.3.1 7 | github.com/brianvoe/gofakeit v3.18.0+incompatible 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= 2 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 3 | github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKOW72Y5+4WNxUIkjM8= 4 | github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc= 5 | -------------------------------------------------------------------------------- /statics/imgs/bucket-insert-bucket2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-insert-bucket2.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-insert-new-inline-bucket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-insert-new-inline-bucket.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-nested-buckets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-nested-buckets.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-new-bucket-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-new-bucket-1.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-new-bucket-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-new-bucket-2.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-new-bucket-b1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-new-bucket-b1.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-new-inline-bucket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-new-inline-bucket.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-normal-bucket-b-plus-tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-normal-bucket-b-plus-tree.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-normal-bucket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-normal-bucket.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-root-bucket-tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-root-bucket-tree.jpg -------------------------------------------------------------------------------- /statics/imgs/bucket-root-bucket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/bucket-root-bucket.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-b-plus-tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-b-plus-tree.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-branch-leaf-page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-branch-leaf-page.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-branch-page-node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-branch-page-node.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-leaf-page-node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-leaf-page-node.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-oversize-page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-oversize-page.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-oversized-kv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-oversized-kv.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-rebalance-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-rebalance-1.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-rebalance-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-rebalance-2.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-rebalance-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-rebalance-3.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-spill-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-spill-1.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-spill-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-spill-2.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-spill-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-spill-3.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-spill-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-spill-4.jpg -------------------------------------------------------------------------------- /statics/imgs/data-and-index-unbalanced-node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/data-and-index-unbalanced-node.jpg -------------------------------------------------------------------------------- /statics/imgs/search-time-complexity.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/search-time-complexity.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-branch-leaf-page-layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-branch-leaf-page-layout.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-branch-page-element-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-branch-page-element-header.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-branch-page-layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-branch-page-layout.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-empty-instance-layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-empty-instance-layout.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-freelist-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-freelist-example.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-freelist-free-pages.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-freelist-free-pages.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-freelist-page-layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-freelist-page-layout.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-leaf-page-element-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-leaf-page-element-header.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-leaf-page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-leaf-page.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-meta-page-layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-meta-page-layout.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-overflow-freelist-page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-overflow-freelist-page.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-overflow-page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-overflow-page.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-page-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-page-header.jpg -------------------------------------------------------------------------------- /statics/imgs/storage-and-cache-page-layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage-and-cache-page-layout.jpg -------------------------------------------------------------------------------- /statics/imgs/storage_bucket_hierarchy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage_bucket_hierarchy.jpg -------------------------------------------------------------------------------- /statics/imgs/storage_mvcc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/storage_mvcc.jpg -------------------------------------------------------------------------------- /statics/imgs/tx-boltdb-mvcc-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/tx-boltdb-mvcc-example.jpg -------------------------------------------------------------------------------- /statics/imgs/tx-boltdb-page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/tx-boltdb-page.jpg -------------------------------------------------------------------------------- /statics/imgs/tx-transactional-memory.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhengHe-MD/learn-bolt/881ff9810263a51aee5ea761208d435fbc618b9f/statics/imgs/tx-transactional-memory.jpg -------------------------------------------------------------------------------- /statics/xmls/b_plus_tree.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /statics/xmls/boltdb.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /statics/xmls/bucket.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /statics/xmls/tx.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /tx/exec_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/boltdb/bolt" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | db, err := bolt.Open("1.db", 0600, nil) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | 15 | err = db.Update(func(tx *bolt.Tx) error { 16 | bucket, err := tx.CreateBucketIfNotExists([]byte("b1")) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | return bucket.Put([]byte("k1"), []byte("v1")) 22 | }) 23 | 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | err = db.View(func(tx *bolt.Tx) error { 29 | bucket := tx.Bucket([]byte("b1")) 30 | if bucket != nil { 31 | v := bucket.Get([]byte("k1")) 32 | fmt.Printf("%s\n", v) 33 | } 34 | return nil 35 | }) 36 | } 37 | --------------------------------------------------------------------------------