btree.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. /*
  2. * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package z
  6. import (
  7. "fmt"
  8. "math"
  9. "os"
  10. "reflect"
  11. "strings"
  12. "unsafe"
  13. "github.com/dgraph-io/ristretto/v2/z/simd"
  14. )
  15. var (
  16. pageSize = os.Getpagesize()
  17. maxKeys = (pageSize / 16) - 1
  18. //nolint:unused
  19. oneThird = int(float64(maxKeys) / 3)
  20. )
  21. const (
  22. absoluteMax = uint64(math.MaxUint64 - 1)
  23. minSize = 1 << 20
  24. )
  25. // Tree represents the structure for custom mmaped B+ tree.
  26. // It supports keys in range [1, math.MaxUint64-1] and values [1, math.Uint64].
  27. type Tree struct {
  28. buffer *Buffer
  29. data []byte
  30. nextPage uint64
  31. freePage uint64
  32. stats TreeStats
  33. }
  34. func (t *Tree) initRootNode() {
  35. // This is the root node.
  36. t.newNode(0)
  37. // This acts as the rightmost pointer (all the keys are <= this key).
  38. t.Set(absoluteMax, 0)
  39. }
  40. // NewTree returns an in-memory B+ tree.
  41. func NewTree(tag string) *Tree {
  42. const defaultTag = "tree"
  43. if tag == "" {
  44. tag = defaultTag
  45. }
  46. t := &Tree{buffer: NewBuffer(minSize, tag)}
  47. t.Reset()
  48. return t
  49. }
  50. // NewTree returns a persistent on-disk B+ tree.
  51. func NewTreePersistent(path string) (*Tree, error) {
  52. t := &Tree{}
  53. var err error
  54. // Open the buffer from disk and set it to the maximum allocated size.
  55. t.buffer, err = NewBufferPersistent(path, minSize)
  56. if err != nil {
  57. return nil, err
  58. }
  59. t.buffer.offset = uint64(len(t.buffer.buf))
  60. t.data = t.buffer.Bytes()
  61. // pageID can never be 0 if the tree has been initialized.
  62. root := t.node(1)
  63. isInitialized := root.pageID() != 0
  64. if !isInitialized {
  65. t.nextPage = 1
  66. t.freePage = 0
  67. t.initRootNode()
  68. } else {
  69. t.reinit()
  70. }
  71. return t, nil
  72. }
  73. // reinit sets the internal variables of a Tree, which are normally stored
  74. // in-memory, but are lost when loading from disk.
  75. func (t *Tree) reinit() {
  76. // Calculate t.nextPage by finding the first node whose pageID is not set.
  77. t.nextPage = 1
  78. for int(t.nextPage)*pageSize < len(t.data) {
  79. n := t.node(t.nextPage)
  80. if n.pageID() == 0 {
  81. break
  82. }
  83. t.nextPage++
  84. }
  85. maxPageId := t.nextPage - 1
  86. // Calculate t.freePage by finding the page to which no other page points.
  87. // This would be the head of the page linked list.
  88. // tailPages[i] is true if pageId i+1 is not the head of the list.
  89. tailPages := make([]bool, maxPageId)
  90. // Mark all pages containing nodes as tail pages.
  91. t.Iterate(func(n node) {
  92. i := n.pageID() - 1
  93. tailPages[i] = true
  94. // If this is a leaf node, increment the stats.
  95. if n.isLeaf() {
  96. t.stats.NumLeafKeys += n.numKeys()
  97. }
  98. })
  99. // pointedPages is a list of page IDs that the tail pages point to.
  100. pointedPages := make([]uint64, 0)
  101. for i, isTail := range tailPages {
  102. if !isTail {
  103. pageId := uint64(i) + 1
  104. // Skip if nextPageId = 0, as that is equivalent to null page.
  105. if nextPageId := t.node(pageId).uint64(0); nextPageId != 0 {
  106. pointedPages = append(pointedPages, nextPageId)
  107. }
  108. t.stats.NumPagesFree++
  109. }
  110. }
  111. // Mark all pages being pointed to as tail pages.
  112. for _, pageId := range pointedPages {
  113. i := pageId - 1
  114. tailPages[i] = true
  115. }
  116. // There should only be one head page left.
  117. for i, isTail := range tailPages {
  118. if !isTail {
  119. pageId := uint64(i) + 1
  120. t.freePage = pageId
  121. break
  122. }
  123. }
  124. }
  125. // Reset resets the tree and truncates it to maxSz.
  126. func (t *Tree) Reset() {
  127. // Tree relies on uninitialized data being zeroed out, so we need to Memclr
  128. // the data before using it again.
  129. Memclr(t.buffer.buf)
  130. t.buffer.Reset()
  131. t.buffer.AllocateOffset(minSize)
  132. t.data = t.buffer.Bytes()
  133. t.stats = TreeStats{}
  134. t.nextPage = 1
  135. t.freePage = 0
  136. t.initRootNode()
  137. }
  138. // Close releases the memory used by the tree.
  139. func (t *Tree) Close() error {
  140. if t == nil {
  141. return nil
  142. }
  143. return t.buffer.Release()
  144. }
  145. type TreeStats struct {
  146. Allocated int // Derived.
  147. Bytes int // Derived.
  148. NumLeafKeys int // Calculated.
  149. NumPages int // Derived.
  150. NumPagesFree int // Calculated.
  151. Occupancy float64 // Derived.
  152. PageSize int // Derived.
  153. }
  154. // Stats returns stats about the tree.
  155. func (t *Tree) Stats() TreeStats {
  156. numPages := int(t.nextPage - 1)
  157. out := TreeStats{
  158. Bytes: numPages * pageSize,
  159. Allocated: len(t.data),
  160. NumLeafKeys: t.stats.NumLeafKeys,
  161. NumPages: numPages,
  162. NumPagesFree: t.stats.NumPagesFree,
  163. PageSize: pageSize,
  164. }
  165. out.Occupancy = 100.0 * float64(out.NumLeafKeys) / float64(maxKeys*numPages)
  166. return out
  167. }
  168. // BytesToUint64Slice converts a byte slice to a uint64 slice.
  169. func BytesToUint64Slice(b []byte) []uint64 {
  170. if len(b) == 0 {
  171. return nil
  172. }
  173. var u64s []uint64
  174. hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u64s))
  175. hdr.Len = len(b) / 8
  176. hdr.Cap = hdr.Len
  177. hdr.Data = uintptr(unsafe.Pointer(&b[0]))
  178. return u64s
  179. }
  180. func (t *Tree) newNode(bit uint64) node {
  181. var pageId uint64
  182. if t.freePage > 0 {
  183. pageId = t.freePage
  184. t.stats.NumPagesFree--
  185. } else {
  186. pageId = t.nextPage
  187. t.nextPage++
  188. offset := int(pageId) * pageSize
  189. reqSize := offset + pageSize
  190. if reqSize > len(t.data) {
  191. t.buffer.AllocateOffset(reqSize - len(t.data))
  192. t.data = t.buffer.Bytes()
  193. }
  194. }
  195. n := t.node(pageId)
  196. if t.freePage > 0 {
  197. t.freePage = n.uint64(0)
  198. }
  199. zeroOut(n)
  200. n.setBit(bit)
  201. n.setAt(keyOffset(maxKeys), pageId)
  202. return n
  203. }
  204. func getNode(data []byte) node {
  205. return node(BytesToUint64Slice(data))
  206. }
  207. func zeroOut(data []uint64) {
  208. for i := 0; i < len(data); i++ {
  209. data[i] = 0
  210. }
  211. }
  212. func (t *Tree) node(pid uint64) node {
  213. // page does not exist
  214. if pid == 0 {
  215. return nil
  216. }
  217. start := pageSize * int(pid)
  218. return getNode(t.data[start : start+pageSize])
  219. }
  220. // Set sets the key-value pair in the tree.
  221. func (t *Tree) Set(k, v uint64) {
  222. if k == math.MaxUint64 || k == 0 {
  223. panic("Error setting zero or MaxUint64")
  224. }
  225. root := t.set(1, k, v)
  226. if root.isFull() {
  227. right := t.split(1)
  228. left := t.newNode(root.bits())
  229. // Re-read the root as the underlying buffer for tree might have changed during split.
  230. root = t.node(1)
  231. copy(left[:keyOffset(maxKeys)], root)
  232. left.setNumKeys(root.numKeys())
  233. // reset the root node.
  234. zeroOut(root[:keyOffset(maxKeys)])
  235. root.setNumKeys(0)
  236. // set the pointers for left and right child in the root node.
  237. root.set(left.maxKey(), left.pageID())
  238. root.set(right.maxKey(), right.pageID())
  239. }
  240. }
  241. // For internal nodes, they contain <key, ptr>.
  242. // where all entries <= key are stored in the corresponding ptr.
  243. func (t *Tree) set(pid, k, v uint64) node {
  244. n := t.node(pid)
  245. if n.isLeaf() {
  246. t.stats.NumLeafKeys += n.set(k, v)
  247. return n
  248. }
  249. // This is an internal node.
  250. idx := n.search(k)
  251. if idx >= maxKeys {
  252. panic("search returned index >= maxKeys")
  253. }
  254. // If no key at idx.
  255. if n.key(idx) == 0 {
  256. n.setAt(keyOffset(idx), k)
  257. n.setNumKeys(n.numKeys() + 1)
  258. }
  259. child := t.node(n.val(idx))
  260. if child == nil {
  261. child = t.newNode(bitLeaf)
  262. n = t.node(pid)
  263. n.setAt(valOffset(idx), child.pageID())
  264. }
  265. child = t.set(child.pageID(), k, v)
  266. // Re-read n as the underlying buffer for tree might have changed during set.
  267. n = t.node(pid)
  268. if child.isFull() {
  269. // Just consider the left sibling for simplicity.
  270. // if t.shareWithSibling(n, idx) {
  271. // return n
  272. // }
  273. nn := t.split(child.pageID())
  274. // Re-read n and child as the underlying buffer for tree might have changed during split.
  275. n = t.node(pid)
  276. child = t.node(n.uint64(valOffset(idx)))
  277. // Set child pointers in the node n.
  278. // Note that key for right node (nn) already exist in node n, but the
  279. // pointer is updated.
  280. n.set(child.maxKey(), child.pageID())
  281. n.set(nn.maxKey(), nn.pageID())
  282. }
  283. return n
  284. }
  285. // Get looks for key and returns the corresponding value.
  286. // If key is not found, 0 is returned.
  287. func (t *Tree) Get(k uint64) uint64 {
  288. if k == math.MaxUint64 || k == 0 {
  289. panic("Does not support getting MaxUint64/Zero")
  290. }
  291. root := t.node(1)
  292. return t.get(root, k)
  293. }
  294. func (t *Tree) get(n node, k uint64) uint64 {
  295. if n.isLeaf() {
  296. return n.get(k)
  297. }
  298. // This is internal node
  299. idx := n.search(k)
  300. if idx == n.numKeys() || n.key(idx) == 0 {
  301. return 0
  302. }
  303. child := t.node(n.uint64(valOffset(idx)))
  304. assert(child != nil)
  305. return t.get(child, k)
  306. }
  307. // DeleteBelow deletes all keys with value under ts.
  308. func (t *Tree) DeleteBelow(ts uint64) {
  309. root := t.node(1)
  310. t.stats.NumLeafKeys = 0
  311. t.compact(root, ts)
  312. assert(root.numKeys() >= 1)
  313. }
  314. func (t *Tree) compact(n node, ts uint64) int {
  315. if n.isLeaf() {
  316. numKeys := n.compact(ts)
  317. t.stats.NumLeafKeys += n.numKeys()
  318. return numKeys
  319. }
  320. // Not leaf.
  321. N := n.numKeys()
  322. for i := 0; i < N; i++ {
  323. assert(n.key(i) > 0)
  324. childID := n.uint64(valOffset(i))
  325. child := t.node(childID)
  326. if rem := t.compact(child, ts); rem == 0 && i < N-1 {
  327. // If no valid key is remaining we can drop this child. However, don't do that if this
  328. // is the max key.
  329. t.stats.NumLeafKeys -= child.numKeys()
  330. child.setAt(0, t.freePage)
  331. t.freePage = childID
  332. n.setAt(valOffset(i), 0)
  333. t.stats.NumPagesFree++
  334. }
  335. }
  336. // We use ts=1 here because we want to delete all the keys whose value is 0, which means they no
  337. // longer have a valid page for that key.
  338. return n.compact(1)
  339. }
  340. func (t *Tree) iterate(n node, fn func(node)) {
  341. fn(n)
  342. if n.isLeaf() {
  343. return
  344. }
  345. // Explore children.
  346. for i := 0; i < maxKeys; i++ {
  347. if n.key(i) == 0 {
  348. return
  349. }
  350. childID := n.uint64(valOffset(i))
  351. assert(childID > 0)
  352. child := t.node(childID)
  353. t.iterate(child, fn)
  354. }
  355. }
  356. // Iterate iterates over the tree and executes the fn on each node.
  357. func (t *Tree) Iterate(fn func(node)) {
  358. root := t.node(1)
  359. t.iterate(root, fn)
  360. }
  361. // IterateKV iterates through all keys and values in the tree.
  362. // If newVal is non-zero, it will be set in the tree.
  363. func (t *Tree) IterateKV(f func(key, val uint64) (newVal uint64)) {
  364. t.Iterate(func(n node) {
  365. // Only leaf nodes contain keys.
  366. if !n.isLeaf() {
  367. return
  368. }
  369. for i := 0; i < n.numKeys(); i++ {
  370. key := n.key(i)
  371. val := n.val(i)
  372. // A zero value here means that this is a bogus entry.
  373. if val == 0 {
  374. continue
  375. }
  376. newVal := f(key, val)
  377. if newVal != 0 {
  378. n.setAt(valOffset(i), newVal)
  379. }
  380. }
  381. })
  382. }
  383. func (t *Tree) print(n node, parentID uint64) {
  384. n.print(parentID)
  385. if n.isLeaf() {
  386. return
  387. }
  388. pid := n.pageID()
  389. for i := 0; i < maxKeys; i++ {
  390. if n.key(i) == 0 {
  391. return
  392. }
  393. childID := n.uint64(valOffset(i))
  394. child := t.node(childID)
  395. t.print(child, pid)
  396. }
  397. }
  398. // Print iterates over the tree and prints all valid KVs.
  399. func (t *Tree) Print() {
  400. root := t.node(1)
  401. t.print(root, 0)
  402. }
  403. // Splits the node into two. It moves right half of the keys from the original node to a newly
  404. // created right node. It returns the right node.
  405. func (t *Tree) split(pid uint64) node {
  406. n := t.node(pid)
  407. if !n.isFull() {
  408. panic("This should be called only when n is full")
  409. }
  410. // Create a new node nn, copy over half the keys from n, and set the parent to n's parent.
  411. nn := t.newNode(n.bits())
  412. // Re-read n as the underlying buffer for tree might have changed during newNode.
  413. n = t.node(pid)
  414. rightHalf := n[keyOffset(maxKeys/2):keyOffset(maxKeys)]
  415. copy(nn, rightHalf)
  416. nn.setNumKeys(maxKeys - maxKeys/2)
  417. // Remove entries from node n.
  418. zeroOut(rightHalf)
  419. n.setNumKeys(maxKeys / 2)
  420. return nn
  421. }
  422. // shareWithSiblingXXX is unused for now. The idea is to move some keys to
  423. // sibling when a node is full. But, I don't see any special benefits in our
  424. // access pattern. It doesn't result in better occupancy ratios.
  425. //
  426. //nolint:unused
  427. func (t *Tree) shareWithSiblingXXX(n node, idx int) bool {
  428. if idx == 0 {
  429. return false
  430. }
  431. left := t.node(n.val(idx - 1))
  432. ns := left.numKeys()
  433. if ns >= maxKeys/2 {
  434. // Sibling is already getting full.
  435. return false
  436. }
  437. right := t.node(n.val(idx))
  438. // Copy over keys from right child to left child.
  439. copied := copy(left[keyOffset(ns):], right[:keyOffset(oneThird)])
  440. copied /= 2 // Considering that key-val constitute one key.
  441. left.setNumKeys(ns + copied)
  442. // Update the max key in parent node n for the left sibling.
  443. n.setAt(keyOffset(idx-1), left.maxKey())
  444. // Now move keys to left for the right sibling.
  445. until := copy(right, right[keyOffset(oneThird):keyOffset(maxKeys)])
  446. right.setNumKeys(until / 2)
  447. zeroOut(right[until:keyOffset(maxKeys)])
  448. return true
  449. }
  450. // Each node in the node is of size pageSize. Two kinds of nodes. Leaf nodes and internal nodes.
  451. // Leaf nodes only contain the data. Internal nodes would contain the key and the offset to the
  452. // child node.
  453. // Internal node would have first entry as
  454. // <0 offset to child>, <1000 offset>, <5000 offset>, and so on...
  455. // Leaf nodes would just have: <key, value>, <key, value>, and so on...
  456. // Last 16 bytes of the node are off limits.
  457. // | pageID (8 bytes) | metaBits (1 byte) | 3 free bytes | numKeys (4 bytes) |
  458. type node []uint64
  459. func (n node) uint64(start int) uint64 { return n[start] }
  460. // func (n node) uint32(start int) uint32 { return *(*uint32)(unsafe.Pointer(&n[start])) }
  461. func keyOffset(i int) int { return 2 * i }
  462. func valOffset(i int) int { return 2*i + 1 }
  463. func (n node) numKeys() int { return int(n.uint64(valOffset(maxKeys)) & 0xFFFFFFFF) }
  464. func (n node) pageID() uint64 { return n.uint64(keyOffset(maxKeys)) }
  465. func (n node) key(i int) uint64 { return n.uint64(keyOffset(i)) }
  466. func (n node) val(i int) uint64 { return n.uint64(valOffset(i)) }
  467. func (n node) data(i int) []uint64 { return n[keyOffset(i):keyOffset(i+1)] }
  468. func (n node) setAt(start int, k uint64) {
  469. n[start] = k
  470. }
  471. func (n node) setNumKeys(num int) {
  472. idx := valOffset(maxKeys)
  473. val := n[idx]
  474. val &= 0xFFFFFFFF00000000
  475. val |= uint64(num)
  476. n[idx] = val
  477. }
  478. func (n node) moveRight(lo int) {
  479. hi := n.numKeys()
  480. assert(hi != maxKeys)
  481. // copy works despite of overlap in src and dst.
  482. // See https://golang.org/pkg/builtin/#copy
  483. copy(n[keyOffset(lo+1):keyOffset(hi+1)], n[keyOffset(lo):keyOffset(hi)])
  484. }
  485. const (
  486. bitLeaf = uint64(1 << 63)
  487. )
  488. func (n node) setBit(b uint64) {
  489. vo := valOffset(maxKeys)
  490. val := n[vo]
  491. val &= 0xFFFFFFFF
  492. val |= b
  493. n[vo] = val
  494. }
  495. func (n node) bits() uint64 {
  496. return n.val(maxKeys) & 0xFF00000000000000
  497. }
  498. func (n node) isLeaf() bool {
  499. return n.bits()&bitLeaf > 0
  500. }
  501. // isFull checks that the node is already full.
  502. func (n node) isFull() bool {
  503. return n.numKeys() == maxKeys
  504. }
  505. // Search returns the index of a smallest key >= k in a node.
  506. func (n node) search(k uint64) int {
  507. N := n.numKeys()
  508. if N < 4 {
  509. for i := 0; i < N; i++ {
  510. if ki := n.key(i); ki >= k {
  511. return i
  512. }
  513. }
  514. return N
  515. }
  516. return int(simd.Search(n[:2*N], k))
  517. // lo, hi := 0, N
  518. // // Reduce the search space using binary seach and then do linear search.
  519. // for hi-lo > 32 {
  520. // mid := (hi + lo) / 2
  521. // km := n.key(mid)
  522. // if k == km {
  523. // return mid
  524. // }
  525. // if k > km {
  526. // // key is greater than the key at mid, so move right.
  527. // lo = mid + 1
  528. // } else {
  529. // // else move left.
  530. // hi = mid
  531. // }
  532. // }
  533. // for i := lo; i <= hi; i++ {
  534. // if ki := n.key(i); ki >= k {
  535. // return i
  536. // }
  537. // }
  538. // return N
  539. }
  540. func (n node) maxKey() uint64 {
  541. idx := n.numKeys()
  542. // idx points to the first key which is zero.
  543. if idx > 0 {
  544. idx--
  545. }
  546. return n.key(idx)
  547. }
  548. // compacts the node i.e., remove all the kvs with value < lo. It returns the remaining number of
  549. // keys.
  550. func (n node) compact(lo uint64) int {
  551. N := n.numKeys()
  552. mk := n.maxKey()
  553. var left, right int
  554. for right = 0; right < N; right++ {
  555. if n.val(right) < lo && n.key(right) < mk {
  556. // Skip over this key. Don't copy it.
  557. continue
  558. }
  559. // Valid data. Copy it from right to left. Advance left.
  560. if left != right {
  561. copy(n.data(left), n.data(right))
  562. }
  563. left++
  564. }
  565. // zero out rest of the kv pairs.
  566. zeroOut(n[keyOffset(left):keyOffset(right)])
  567. n.setNumKeys(left)
  568. // If the only key we have is the max key, and its value is less than lo, then we can indicate
  569. // to the caller by returning a zero that it's OK to drop the node.
  570. if left == 1 && n.key(0) == mk && n.val(0) < lo {
  571. return 0
  572. }
  573. return left
  574. }
  575. func (n node) get(k uint64) uint64 {
  576. idx := n.search(k)
  577. // key is not found
  578. if idx == n.numKeys() {
  579. return 0
  580. }
  581. if ki := n.key(idx); ki == k {
  582. return n.val(idx)
  583. }
  584. return 0
  585. }
  586. // set returns true if it added a new key.
  587. func (n node) set(k, v uint64) (numAdded int) {
  588. idx := n.search(k)
  589. ki := n.key(idx)
  590. if n.numKeys() == maxKeys {
  591. // This happens during split of non-root node, when we are updating the child pointer of
  592. // right node. Hence, the key should already exist.
  593. assert(ki == k)
  594. }
  595. if ki > k {
  596. // Found the first entry which is greater than k. So, we need to fit k
  597. // just before it. For that, we should move the rest of the data in the
  598. // node to the right to make space for k.
  599. n.moveRight(idx)
  600. }
  601. // If the k does not exist already, increment the number of keys.
  602. if ki != k {
  603. n.setNumKeys(n.numKeys() + 1)
  604. numAdded = 1
  605. }
  606. if ki == 0 || ki >= k {
  607. n.setAt(keyOffset(idx), k)
  608. n.setAt(valOffset(idx), v)
  609. return
  610. }
  611. panic("shouldn't reach here")
  612. }
  613. func (n node) iterate(fn func(node, int)) {
  614. for i := 0; i < maxKeys; i++ {
  615. if k := n.key(i); k > 0 {
  616. fn(n, i)
  617. } else {
  618. break
  619. }
  620. }
  621. }
  622. func (n node) print(parentID uint64) {
  623. var keys []string
  624. n.iterate(func(n node, i int) {
  625. keys = append(keys, fmt.Sprintf("%d", n.key(i)))
  626. })
  627. if len(keys) > 8 {
  628. copy(keys[4:], keys[len(keys)-4:])
  629. keys[3] = "..."
  630. keys = keys[:8]
  631. }
  632. fmt.Printf("%d Child of: %d num keys: %d keys: %s\n",
  633. n.pageID(), parentID, n.numKeys(), strings.Join(keys, " "))
  634. }