Line data Source code
1 : // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2 : // of this source code is governed by a BSD-style license that can be found in
3 : // the LICENSE file.
4 :
5 : package tool
6 :
7 : import (
8 : "bufio"
9 : "context"
10 : "fmt"
11 : "io"
12 : "math/rand"
13 : "strings"
14 : "text/tabwriter"
15 :
16 : "github.com/cockroachdb/errors"
17 : "github.com/cockroachdb/errors/oserror"
18 : "github.com/cockroachdb/pebble"
19 : "github.com/cockroachdb/pebble/internal/base"
20 : "github.com/cockroachdb/pebble/internal/humanize"
21 : "github.com/cockroachdb/pebble/internal/manifest"
22 : "github.com/cockroachdb/pebble/internal/sstableinternal"
23 : "github.com/cockroachdb/pebble/objstorage"
24 : "github.com/cockroachdb/pebble/objstorage/objstorageprovider"
25 : "github.com/cockroachdb/pebble/record"
26 : "github.com/cockroachdb/pebble/sstable"
27 : "github.com/cockroachdb/pebble/tool/logs"
28 : "github.com/cockroachdb/pebble/vfs"
29 : "github.com/spf13/cobra"
30 : )
31 :
32 : // dbT implements db-level tools, including both configuration state and the
33 : // commands themselves.
34 : type dbT struct {
35 : Root *cobra.Command
36 : Check *cobra.Command
37 : Checkpoint *cobra.Command
38 : Get *cobra.Command
39 : Logs *cobra.Command
40 : LSM *cobra.Command
41 : Properties *cobra.Command
42 : Scan *cobra.Command
43 : Set *cobra.Command
44 : Space *cobra.Command
45 : IOBench *cobra.Command
46 : Excise *cobra.Command
47 :
48 : // Configuration.
49 : opts *pebble.Options
50 : comparers sstable.Comparers
51 : mergers sstable.Mergers
52 : openErrEnhancer func(error) error
53 : openOptions []OpenOption
54 : exciseSpanFn DBExciseSpanFn
55 :
56 : // Flags.
57 : comparerName string
58 : mergerName string
59 : fmtKey keyFormatter
60 : fmtValue valueFormatter
61 : start key
62 : end key
63 : count int64
64 : allLevels bool
65 : ioCount int
66 : ioParallelism int
67 : ioSizes string
68 : verbose bool
69 : bypassPrompt bool
70 : lsmURL bool
71 : }
72 :
73 : func newDB(
74 : opts *pebble.Options,
75 : comparers sstable.Comparers,
76 : mergers sstable.Mergers,
77 : openErrEnhancer func(error) error,
78 : openOptions []OpenOption,
79 : exciseSpanFn DBExciseSpanFn,
80 1 : ) *dbT {
81 1 : d := &dbT{
82 1 : opts: opts,
83 1 : comparers: comparers,
84 1 : mergers: mergers,
85 1 : openErrEnhancer: openErrEnhancer,
86 1 : openOptions: openOptions,
87 1 : exciseSpanFn: exciseSpanFn,
88 1 : }
89 1 : d.fmtKey.mustSet("quoted")
90 1 : d.fmtValue.mustSet("[%x]")
91 1 :
92 1 : d.Root = &cobra.Command{
93 1 : Use: "db",
94 1 : Short: "DB introspection tools",
95 1 : }
96 1 : d.Check = &cobra.Command{
97 1 : Use: "check <dir>",
98 1 : Short: "verify checksums and metadata",
99 1 : Long: `
100 1 : Verify sstable, manifest, and WAL checksums. Requires that the specified
101 1 : database not be in use by another process.
102 1 : `,
103 1 : Args: cobra.ExactArgs(1),
104 1 : Run: d.runCheck,
105 1 : }
106 1 : d.Checkpoint = &cobra.Command{
107 1 : Use: "checkpoint <src-dir> <dest-dir>",
108 1 : Short: "create a checkpoint",
109 1 : Long: `
110 1 : Creates a Pebble checkpoint in the specified destination directory. A checkpoint
111 1 : is a point-in-time snapshot of DB state. Requires that the specified
112 1 : database not be in use by another process.
113 1 : `,
114 1 : Args: cobra.ExactArgs(2),
115 1 : Run: d.runCheckpoint,
116 1 : }
117 1 : d.Get = &cobra.Command{
118 1 : Use: "get <dir> <key>",
119 1 : Short: "get value for a key",
120 1 : Long: `
121 1 : Gets a value for a key, if it exists in DB. Prints a "not found" error if key
122 1 : does not exist. Requires that the specified database not be in use by another
123 1 : process.
124 1 : `,
125 1 : Args: cobra.ExactArgs(2),
126 1 : Run: d.runGet,
127 1 : }
128 1 : d.Logs = logs.NewCmd()
129 1 : d.LSM = &cobra.Command{
130 1 : Use: "lsm <dir>",
131 1 : Short: "print LSM structure",
132 1 : Long: `
133 1 : Print the structure of the LSM tree. Requires that the specified database not
134 1 : be in use by another process.
135 1 : `,
136 1 : Args: cobra.ExactArgs(1),
137 1 : Run: d.runLSM,
138 1 : }
139 1 : d.Properties = &cobra.Command{
140 1 : Use: "properties <dir>",
141 1 : Short: "print aggregated sstable properties",
142 1 : Long: `
143 1 : Print SSTable properties, aggregated per level of the LSM.
144 1 : `,
145 1 : Args: cobra.ExactArgs(1),
146 1 : Run: d.runProperties,
147 1 : }
148 1 : d.Scan = &cobra.Command{
149 1 : Use: "scan <dir>",
150 1 : Short: "print db records",
151 1 : Long: `
152 1 : Print the records in the DB. Requires that the specified database not be in use
153 1 : by another process.
154 1 : `,
155 1 : Args: cobra.ExactArgs(1),
156 1 : Run: d.runScan,
157 1 : }
158 1 : d.Set = &cobra.Command{
159 1 : Use: "set <dir> <key> <value>",
160 1 : Short: "set a value for a key",
161 1 : Long: `
162 1 : Adds a new key/value to the DB. Requires that the specified database
163 1 : not be in use by another process.
164 1 : `,
165 1 : Args: cobra.ExactArgs(3),
166 1 : Run: d.runSet,
167 1 : }
168 1 : d.Space = &cobra.Command{
169 1 : Use: "space <dir>",
170 1 : Short: "print filesystem space used",
171 1 : Long: `
172 1 : Print the estimated filesystem space usage for the inclusive-inclusive range
173 1 : specified by --start and --end. Requires that the specified database not be in
174 1 : use by another process.
175 1 : `,
176 1 : Args: cobra.ExactArgs(1),
177 1 : Run: d.runSpace,
178 1 : }
179 1 : d.Excise = &cobra.Command{
180 1 : Use: "excise <dir>",
181 1 : Short: "excise a key range",
182 1 : Long: `
183 1 : Excise a key range, removing all SSTs inside the range and virtualizing any SSTs
184 1 : that partially overlap the range.
185 1 : `,
186 1 : Args: cobra.ExactArgs(1),
187 1 : Run: d.runExcise,
188 1 : }
189 1 : d.IOBench = &cobra.Command{
190 1 : Use: "io-bench <dir>",
191 1 : Short: "perform sstable IO benchmark",
192 1 : Long: `
193 1 : Run a random IO workload with various IO sizes against the sstables in the
194 1 : specified database.
195 1 : `,
196 1 : Args: cobra.ExactArgs(1),
197 1 : Run: d.runIOBench,
198 1 : }
199 1 :
200 1 : d.Root.AddCommand(d.Check, d.Checkpoint, d.Get, d.Logs, d.LSM, d.Properties, d.Scan, d.Set, d.Space, d.Excise, d.IOBench)
201 1 : d.Root.PersistentFlags().BoolVarP(&d.verbose, "verbose", "v", false, "verbose output")
202 1 :
203 1 : for _, cmd := range []*cobra.Command{d.Check, d.Checkpoint, d.Get, d.LSM, d.Properties, d.Scan, d.Set, d.Space, d.Excise} {
204 1 : cmd.Flags().StringVar(
205 1 : &d.comparerName, "comparer", "", "comparer name (use default if empty)")
206 1 : cmd.Flags().StringVar(
207 1 : &d.mergerName, "merger", "", "merger name (use default if empty)")
208 1 : }
209 :
210 1 : for _, cmd := range []*cobra.Command{d.Scan, d.Get} {
211 1 : cmd.Flags().Var(
212 1 : &d.fmtValue, "value", "value formatter")
213 1 : }
214 :
215 1 : d.LSM.Flags().BoolVar(
216 1 : &d.lsmURL, "url", false, "generate LSM viewer URL")
217 1 :
218 1 : d.Space.Flags().Var(
219 1 : &d.start, "start", "start key for the range")
220 1 : d.Space.Flags().Var(
221 1 : &d.end, "end", "inclusive end key for the range")
222 1 :
223 1 : d.Scan.Flags().Var(
224 1 : &d.fmtKey, "key", "key formatter")
225 1 : d.Scan.Flags().Var(
226 1 : &d.start, "start", "start key for the range")
227 1 : d.Scan.Flags().Var(
228 1 : &d.end, "end", "exclusive end key for the range")
229 1 : d.Scan.Flags().Int64Var(
230 1 : &d.count, "count", 0, "key count for scan (0 is unlimited)")
231 1 :
232 1 : d.Excise.Flags().Var(
233 1 : &d.start, "start", "start key for the excised range")
234 1 : d.Excise.Flags().Var(
235 1 : &d.end, "end", "exclusive end key for the excised range")
236 1 : d.Excise.Flags().BoolVar(
237 1 : &d.bypassPrompt, "yes", false, "bypass prompt")
238 1 :
239 1 : d.IOBench.Flags().BoolVar(
240 1 : &d.allLevels, "all-levels", false, "if set, benchmark all levels (default is only L5/L6)")
241 1 : d.IOBench.Flags().IntVar(
242 1 : &d.ioCount, "io-count", 10000, "number of IOs (per IO size) to benchmark")
243 1 : d.IOBench.Flags().IntVar(
244 1 : &d.ioParallelism, "io-parallelism", 16, "number of goroutines issuing IO")
245 1 : d.IOBench.Flags().StringVar(
246 1 : &d.ioSizes, "io-sizes-kb", "4,16,64,128,256,512,1024", "comma separated list of IO sizes in KB")
247 1 :
248 1 : return d
249 : }
250 :
251 1 : func (d *dbT) loadOptions(dir string) error {
252 1 : ls, err := d.opts.FS.List(dir)
253 1 : if err != nil || len(ls) == 0 {
254 1 : // NB: We don't return the error here as we prefer to return the error from
255 1 : // pebble.Open. Another way to put this is that a non-existent directory is
256 1 : // not a failure in loading the options.
257 1 : return nil
258 1 : }
259 :
260 1 : hooks := &pebble.ParseHooks{
261 1 : NewComparer: func(name string) (*pebble.Comparer, error) {
262 1 : if c := d.comparers[name]; c != nil {
263 1 : return c, nil
264 1 : }
265 0 : return nil, errors.Errorf("unknown comparer %q", errors.Safe(name))
266 : },
267 1 : NewMerger: func(name string) (*pebble.Merger, error) {
268 1 : if m := d.mergers[name]; m != nil {
269 1 : return m, nil
270 1 : }
271 0 : return nil, errors.Errorf("unknown merger %q", errors.Safe(name))
272 : },
273 0 : SkipUnknown: func(name, value string) bool {
274 0 : return true
275 0 : },
276 : }
277 :
278 : // TODO(peter): RocksDB sometimes leaves multiple OPTIONS files in
279 : // existence. We parse all of them as the comparer and merger shouldn't be
280 : // changing. We could parse only the first or the latest. Not clear if this
281 : // matters.
282 1 : var dbOpts pebble.Options
283 1 : for _, filename := range ls {
284 1 : ft, _, ok := base.ParseFilename(d.opts.FS, filename)
285 1 : if !ok {
286 1 : continue
287 : }
288 1 : switch ft {
289 1 : case base.FileTypeOptions:
290 1 : err := func() error {
291 1 : f, err := d.opts.FS.Open(d.opts.FS.PathJoin(dir, filename))
292 1 : if err != nil {
293 0 : return err
294 0 : }
295 1 : defer f.Close()
296 1 :
297 1 : data, err := io.ReadAll(f)
298 1 : if err != nil {
299 0 : return err
300 0 : }
301 :
302 1 : if err := dbOpts.Parse(string(data), hooks); err != nil {
303 1 : return err
304 1 : }
305 1 : return nil
306 : }()
307 1 : if err != nil {
308 1 : return err
309 1 : }
310 : }
311 : }
312 :
313 1 : if dbOpts.Comparer != nil {
314 1 : d.opts.Comparer = dbOpts.Comparer
315 1 : }
316 1 : if dbOpts.Merger != nil {
317 1 : d.opts.Merger = dbOpts.Merger
318 1 : }
319 1 : return nil
320 : }
321 :
322 : // OpenOption is an option that may be applied to the *pebble.Options before
323 : // calling pebble.Open.
324 : type OpenOption interface {
325 : Apply(dirname string, opts *pebble.Options)
326 : }
327 :
328 1 : func (d *dbT) openDB(dir string, openOptions ...OpenOption) (*pebble.DB, error) {
329 1 : db, err := d.openDBInternal(dir, openOptions...)
330 1 : if err != nil {
331 1 : if d.openErrEnhancer != nil {
332 1 : err = d.openErrEnhancer(err)
333 1 : }
334 1 : return nil, err
335 : }
336 1 : return db, nil
337 : }
338 :
339 1 : func (d *dbT) openDBInternal(dir string, openOptions ...OpenOption) (*pebble.DB, error) {
340 1 : if err := d.loadOptions(dir); err != nil {
341 1 : return nil, errors.Wrap(err, "error loading options")
342 1 : }
343 1 : if d.comparerName != "" {
344 1 : d.opts.Comparer = d.comparers[d.comparerName]
345 1 : if d.opts.Comparer == nil {
346 1 : return nil, errors.Errorf("unknown comparer %q", errors.Safe(d.comparerName))
347 1 : }
348 : }
349 1 : if d.mergerName != "" {
350 1 : d.opts.Merger = d.mergers[d.mergerName]
351 1 : if d.opts.Merger == nil {
352 1 : return nil, errors.Errorf("unknown merger %q", errors.Safe(d.mergerName))
353 1 : }
354 : }
355 1 : opts := *d.opts
356 1 : for _, opt := range openOptions {
357 1 : opt.Apply(dir, &opts)
358 1 : }
359 1 : for _, opt := range d.openOptions {
360 0 : opt.Apply(dir, &opts)
361 0 : }
362 1 : opts.Cache = pebble.NewCache(128 << 20 /* 128 MB */)
363 1 : defer opts.Cache.Unref()
364 1 : return pebble.Open(dir, &opts)
365 : }
366 :
367 1 : func (d *dbT) closeDB(stderr io.Writer, db *pebble.DB) {
368 1 : if err := db.Close(); err != nil {
369 0 : fmt.Fprintf(stderr, "%s\n", err)
370 0 : }
371 : }
372 :
373 1 : func (d *dbT) runCheck(cmd *cobra.Command, args []string) {
374 1 : stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
375 1 : db, err := d.openDB(args[0])
376 1 : if err != nil {
377 1 : fmt.Fprintf(stderr, "%s\n", err)
378 1 : return
379 1 : }
380 1 : defer d.closeDB(stderr, db)
381 1 :
382 1 : var stats pebble.CheckLevelsStats
383 1 : if err := db.CheckLevels(&stats); err != nil {
384 0 : fmt.Fprintf(stderr, "%s\n", err)
385 0 : }
386 1 : fmt.Fprintf(stdout, "checked %d %s and %d %s\n",
387 1 : stats.NumPoints, makePlural("point", stats.NumPoints), stats.NumTombstones, makePlural("tombstone", int64(stats.NumTombstones)))
388 : }
389 :
390 : type nonReadOnly struct{}
391 :
392 1 : func (n nonReadOnly) Apply(dirname string, opts *pebble.Options) {
393 1 : opts.ReadOnly = false
394 1 : // Increase the L0 compaction threshold to reduce the likelihood of an
395 1 : // unintended compaction changing test output.
396 1 : opts.L0CompactionThreshold = 10
397 1 : }
398 :
399 1 : func (d *dbT) runCheckpoint(cmd *cobra.Command, args []string) {
400 1 : stderr := cmd.ErrOrStderr()
401 1 : db, err := d.openDB(args[0], nonReadOnly{})
402 1 : if err != nil {
403 0 : fmt.Fprintf(stderr, "%s\n", err)
404 0 : return
405 0 : }
406 1 : defer d.closeDB(stderr, db)
407 1 : destDir := args[1]
408 1 :
409 1 : if err := db.Checkpoint(destDir); err != nil {
410 0 : fmt.Fprintf(stderr, "%s\n", err)
411 0 : }
412 : }
413 :
414 1 : func (d *dbT) runGet(cmd *cobra.Command, args []string) {
415 1 : stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
416 1 : db, err := d.openDB(args[0])
417 1 : if err != nil {
418 0 : fmt.Fprintf(stderr, "%s\n", err)
419 0 : return
420 0 : }
421 1 : defer d.closeDB(stderr, db)
422 1 : var k key
423 1 : if err := k.Set(args[1]); err != nil {
424 0 : fmt.Fprintf(stderr, "%s\n", err)
425 0 : return
426 0 : }
427 :
428 1 : val, closer, err := db.Get(k)
429 1 : if err != nil {
430 1 : fmt.Fprintf(stderr, "%s\n", err)
431 1 : return
432 1 : }
433 1 : defer func() {
434 1 : if closer != nil {
435 1 : closer.Close()
436 1 : }
437 : }()
438 1 : if val != nil {
439 1 : fmt.Fprintf(stdout, "%s\n", d.fmtValue.fn(k, val))
440 1 : }
441 : }
442 :
443 1 : func (d *dbT) runLSM(cmd *cobra.Command, args []string) {
444 1 : stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
445 1 : db, err := d.openDB(args[0])
446 1 : if err != nil {
447 1 : fmt.Fprintf(stderr, "%s\n", err)
448 1 : return
449 1 : }
450 1 : defer d.closeDB(stderr, db)
451 1 :
452 1 : fmt.Fprintf(stdout, "%s", db.Metrics())
453 1 : if d.lsmURL {
454 1 : fmt.Fprintf(stdout, "\nLSM viewer: %s\n", db.LSMViewURL())
455 1 : }
456 : }
457 :
458 1 : func (d *dbT) runScan(cmd *cobra.Command, args []string) {
459 1 : stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
460 1 : db, err := d.openDB(args[0])
461 1 : if err != nil {
462 1 : fmt.Fprintf(stderr, "%s\n", err)
463 1 : return
464 1 : }
465 1 : defer d.closeDB(stderr, db)
466 1 :
467 1 : // Update the internal formatter if this comparator has one specified.
468 1 : if d.opts.Comparer != nil {
469 1 : d.fmtKey.setForComparer(d.opts.Comparer.Name, d.comparers)
470 1 : d.fmtValue.setForComparer(d.opts.Comparer.Name, d.comparers)
471 1 : }
472 :
473 1 : start := timeNow()
474 1 : fmtKeys := d.fmtKey.spec != "null"
475 1 : fmtValues := d.fmtValue.spec != "null"
476 1 : var count int64
477 1 :
478 1 : iter, _ := db.NewIter(&pebble.IterOptions{
479 1 : UpperBound: d.end,
480 1 : })
481 1 : for valid := iter.SeekGE(d.start); valid; valid = iter.Next() {
482 1 : if fmtKeys || fmtValues {
483 1 : needDelimiter := false
484 1 : if fmtKeys {
485 1 : fmt.Fprintf(stdout, "%s", d.fmtKey.fn(iter.Key()))
486 1 : needDelimiter = true
487 1 : }
488 1 : if fmtValues {
489 1 : if needDelimiter {
490 1 : stdout.Write([]byte{' '})
491 1 : }
492 1 : fmt.Fprintf(stdout, "%s", d.fmtValue.fn(iter.Key(), iter.Value()))
493 : }
494 1 : stdout.Write([]byte{'\n'})
495 : }
496 :
497 1 : count++
498 1 : if d.count > 0 && count >= d.count {
499 1 : break
500 : }
501 : }
502 :
503 1 : if err := iter.Close(); err != nil {
504 0 : fmt.Fprintf(stderr, "%s\n", err)
505 0 : }
506 :
507 1 : elapsed := timeNow().Sub(start)
508 1 :
509 1 : fmt.Fprintf(stdout, "scanned %d %s in %0.1fs\n",
510 1 : count, makePlural("record", count), elapsed.Seconds())
511 : }
512 :
513 1 : func (d *dbT) runSpace(cmd *cobra.Command, args []string) {
514 1 : stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
515 1 : db, err := d.openDB(args[0])
516 1 : if err != nil {
517 0 : fmt.Fprintf(stderr, "%s\n", err)
518 0 : return
519 0 : }
520 1 : defer d.closeDB(stdout, db)
521 1 :
522 1 : bytes, err := db.EstimateDiskUsage(d.start, d.end)
523 1 : if err != nil {
524 0 : fmt.Fprintf(stderr, "%s\n", err)
525 0 : return
526 0 : }
527 1 : fmt.Fprintf(stdout, "%d\n", bytes)
528 : }
529 :
530 1 : func (d *dbT) getExciseSpan() (pebble.KeyRange, error) {
531 1 : // If a DBExciseSpanFn is specified, try to use it and see if it returns a
532 1 : // valid span.
533 1 : if d.exciseSpanFn != nil {
534 0 : span, err := d.exciseSpanFn()
535 0 : if err != nil {
536 0 : return pebble.KeyRange{}, err
537 0 : }
538 0 : if span.Valid() {
539 0 : if d.start != nil || d.end != nil {
540 0 : return pebble.KeyRange{}, errors.Errorf(
541 0 : "--start/--end cannot be used when span is specified by other methods.")
542 0 : }
543 0 : return span, nil
544 : }
545 : }
546 1 : if d.start == nil || d.end == nil {
547 1 : return pebble.KeyRange{}, errors.Errorf("excise range not specified.")
548 1 : }
549 1 : return pebble.KeyRange{
550 1 : Start: d.start,
551 1 : End: d.end,
552 1 : }, nil
553 : }
554 :
555 1 : func (d *dbT) runExcise(cmd *cobra.Command, args []string) {
556 1 : stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
557 1 :
558 1 : span, err := d.getExciseSpan()
559 1 : if err != nil {
560 1 : fmt.Fprintf(stderr, "Error: %v\n", err)
561 1 : return
562 1 : }
563 :
564 1 : dbOpts := d.opts.EnsureDefaults()
565 1 : // Disable all processes that would try to open tables: table stats,
566 1 : // consistency check, automatic compactions.
567 1 : dbOpts.DisableTableStats = true
568 1 : dbOpts.DisableConsistencyCheck = true
569 1 : dbOpts.DisableAutomaticCompactions = true
570 1 :
571 1 : dbDir := args[0]
572 1 : db, err := d.openDB(dbDir, nonReadOnly{})
573 1 : if err != nil {
574 0 : fmt.Fprintf(stderr, "%s\n", err)
575 0 : return
576 0 : }
577 1 : defer d.closeDB(stdout, db)
578 1 :
579 1 : // Update the internal formatter if this comparator has one specified.
580 1 : if d.opts.Comparer != nil {
581 1 : d.fmtKey.setForComparer(d.opts.Comparer.Name, d.comparers)
582 1 : }
583 :
584 1 : fmt.Fprintf(stdout, "Excising range:\n")
585 1 : fmt.Fprintf(stdout, " start: %s\n", d.fmtKey.fn(span.Start))
586 1 : fmt.Fprintf(stdout, " end: %s\n", d.fmtKey.fn(span.End))
587 1 :
588 1 : if !d.bypassPrompt {
589 0 : fmt.Fprintf(stdout, "WARNING!!!\n")
590 0 : fmt.Fprintf(stdout, "This command will remove all keys in this range!\n")
591 0 : reader := bufio.NewReader(cmd.InOrStdin())
592 0 : for {
593 0 : fmt.Fprintf(stdout, "Continue? [Y/N] ")
594 0 : answer, _ := reader.ReadString('\n')
595 0 : answer = strings.ToLower(strings.TrimSpace(answer))
596 0 : if answer == "y" || answer == "yes" {
597 0 : break
598 : }
599 :
600 0 : if answer == "n" || answer == "no" {
601 0 : fmt.Fprintf(stderr, "Aborting\n")
602 0 : return
603 0 : }
604 : }
605 : }
606 :
607 : // Write a temporary sst that only has excise tombstones. We write it inside
608 : // the database directory so that the command works against any FS.
609 : // TODO(radu): remove this if we add a separate DB.Excise method.
610 1 : path := dbOpts.FS.PathJoin(dbDir, fmt.Sprintf("excise-%0x.sst", rand.Uint32()))
611 1 : defer dbOpts.FS.Remove(path)
612 1 : f, err := dbOpts.FS.Create(path, vfs.WriteCategoryUnspecified)
613 1 : if err != nil {
614 0 : fmt.Fprintf(stderr, "Error creating temporary sst file %q: %s\n", path, err)
615 0 : return
616 0 : }
617 1 : writable := objstorageprovider.NewFileWritable(f)
618 1 : writerOpts := dbOpts.MakeWriterOptions(0, db.FormatMajorVersion().MaxTableFormat())
619 1 : w := sstable.NewWriter(writable, writerOpts)
620 1 : err = w.DeleteRange(span.Start, span.End)
621 1 : err = errors.CombineErrors(err, w.RangeKeyDelete(span.Start, span.End))
622 1 : err = errors.CombineErrors(err, w.Close())
623 1 : if err != nil {
624 0 : fmt.Fprintf(stderr, "Error writing temporary sst file %q: %s\n", path, err)
625 0 : return
626 0 : }
627 :
628 1 : _, err = db.IngestAndExcise(context.Background(), []string{path}, nil, nil, span, true /* sstsContainExciseTombstone */)
629 1 : if err != nil {
630 0 : fmt.Fprintf(stderr, "Error excising: %s\n", err)
631 0 : return
632 0 : }
633 1 : fmt.Fprintf(stdout, "Excise complete.\n")
634 : }
635 :
636 1 : func (d *dbT) runProperties(cmd *cobra.Command, args []string) {
637 1 : stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
638 1 : dirname := args[0]
639 1 : err := func() error {
640 1 : desc, err := pebble.Peek(dirname, d.opts.FS)
641 1 : if err != nil {
642 1 : return err
643 1 : } else if !desc.Exists {
644 0 : return oserror.ErrNotExist
645 0 : }
646 1 : manifestFilename := d.opts.FS.PathBase(desc.ManifestFilename)
647 1 :
648 1 : // Replay the manifest to get the current version.
649 1 : f, err := d.opts.FS.Open(desc.ManifestFilename)
650 1 : if err != nil {
651 0 : return errors.Wrapf(err, "pebble: could not open MANIFEST file %q", manifestFilename)
652 0 : }
653 1 : defer f.Close()
654 1 :
655 1 : cmp := base.DefaultComparer
656 1 : var bve manifest.BulkVersionEdit
657 1 : bve.AddedByFileNum = make(map[base.FileNum]*manifest.FileMetadata)
658 1 : rr := record.NewReader(f, 0 /* logNum */)
659 1 : for {
660 1 : r, err := rr.Next()
661 1 : if err == io.EOF {
662 1 : break
663 : }
664 1 : if err != nil {
665 0 : return errors.Wrapf(err, "pebble: reading manifest %q", manifestFilename)
666 0 : }
667 1 : var ve manifest.VersionEdit
668 1 : err = ve.Decode(r)
669 1 : if err != nil {
670 0 : return err
671 0 : }
672 1 : if err := bve.Accumulate(&ve); err != nil {
673 0 : return err
674 0 : }
675 1 : if ve.ComparerName != "" {
676 1 : cmp = d.comparers[ve.ComparerName]
677 1 : d.fmtKey.setForComparer(ve.ComparerName, d.comparers)
678 1 : d.fmtValue.setForComparer(ve.ComparerName, d.comparers)
679 1 : }
680 : }
681 1 : v, err := bve.Apply(
682 1 : nil /* version */, cmp, d.opts.FlushSplitBytes,
683 1 : d.opts.Experimental.ReadCompactionRate,
684 1 : )
685 1 : if err != nil {
686 0 : return err
687 0 : }
688 :
689 1 : objProvider, err := objstorageprovider.Open(objstorageprovider.DefaultSettings(d.opts.FS, dirname))
690 1 : if err != nil {
691 0 : return err
692 0 : }
693 1 : defer objProvider.Close()
694 1 :
695 1 : // Load and aggregate sstable properties.
696 1 : tw := tabwriter.NewWriter(stdout, 2, 1, 4, ' ', 0)
697 1 : var total props
698 1 : var all []props
699 1 : for _, l := range v.Levels {
700 1 : iter := l.Iter()
701 1 : var level props
702 1 : for t := iter.First(); t != nil; t = iter.Next() {
703 1 : if t.Virtual {
704 0 : // TODO(bananabrick): Handle virtual sstables here. We don't
705 0 : // really have any stats or properties at this point. Maybe
706 0 : // we could approximate some of these properties for virtual
707 0 : // sstables by first grabbing properties for the backing
708 0 : // physical sstable, and then extrapolating.
709 0 : continue
710 : }
711 1 : err := d.addProps(objProvider, t.PhysicalMeta(), &level)
712 1 : if err != nil {
713 0 : return err
714 0 : }
715 : }
716 1 : all = append(all, level)
717 1 : total.update(level)
718 : }
719 1 : all = append(all, total)
720 1 :
721 1 : fmt.Fprintln(tw, "\tL0\tL1\tL2\tL3\tL4\tL5\tL6\tTOTAL")
722 1 :
723 1 : fmt.Fprintf(tw, "count\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
724 1 : propArgs(all, func(p *props) interface{} { return p.Count })...)
725 :
726 1 : fmt.Fprintln(tw, "seq num\t\t\t\t\t\t\t\t")
727 1 : fmt.Fprintf(tw, " smallest\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
728 1 : propArgs(all, func(p *props) interface{} { return p.SmallestSeqNum })...)
729 1 : fmt.Fprintf(tw, " largest\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
730 1 : propArgs(all, func(p *props) interface{} { return p.LargestSeqNum })...)
731 :
732 1 : fmt.Fprintln(tw, "size\t\t\t\t\t\t\t\t")
733 1 : fmt.Fprintf(tw, " data\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
734 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.DataSize) })...)
735 1 : fmt.Fprintf(tw, " blocks\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
736 1 : propArgs(all, func(p *props) interface{} { return p.NumDataBlocks })...)
737 1 : fmt.Fprintf(tw, " index\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
738 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.IndexSize) })...)
739 1 : fmt.Fprintf(tw, " blocks\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
740 1 : propArgs(all, func(p *props) interface{} { return p.NumIndexBlocks })...)
741 1 : fmt.Fprintf(tw, " top-level\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
742 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.TopLevelIndexSize) })...)
743 1 : fmt.Fprintf(tw, " filter\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
744 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.FilterSize) })...)
745 1 : fmt.Fprintf(tw, " raw-key\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
746 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.RawKeySize) })...)
747 1 : fmt.Fprintf(tw, " raw-value\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
748 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.RawValueSize) })...)
749 1 : fmt.Fprintf(tw, " pinned-key\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
750 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.SnapshotPinnedKeySize) })...)
751 1 : fmt.Fprintf(tw, " pinned-value\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
752 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.SnapshotPinnedValueSize) })...)
753 1 : fmt.Fprintf(tw, " point-del-key-size\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
754 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.RawPointTombstoneKeySize) })...)
755 1 : fmt.Fprintf(tw, " point-del-value-size\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
756 1 : propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.RawPointTombstoneValueSize) })...)
757 :
758 1 : fmt.Fprintln(tw, "records\t\t\t\t\t\t\t\t")
759 1 : fmt.Fprintf(tw, " set\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
760 1 : propArgs(all, func(p *props) interface{} {
761 1 : return humanize.Count.Uint64(p.NumEntries - p.NumDeletions - p.NumMergeOperands)
762 1 : })...)
763 1 : fmt.Fprintf(tw, " delete\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
764 1 : propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumDeletions - p.NumRangeDeletions) })...)
765 1 : fmt.Fprintf(tw, " delete-sized\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
766 1 : propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumSizedDeletions) })...)
767 1 : fmt.Fprintf(tw, " range-delete\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
768 1 : propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumRangeDeletions) })...)
769 1 : fmt.Fprintf(tw, " range-key-sets\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
770 1 : propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumRangeKeySets) })...)
771 1 : fmt.Fprintf(tw, " range-key-unsets\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
772 1 : propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumRangeKeyUnSets) })...)
773 1 : fmt.Fprintf(tw, " range-key-deletes\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
774 1 : propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumRangeKeyDeletes) })...)
775 1 : fmt.Fprintf(tw, " merge\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
776 1 : propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumMergeOperands) })...)
777 1 : fmt.Fprintf(tw, " pinned\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
778 1 : propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.SnapshotPinnedKeys) })...)
779 :
780 1 : if err := tw.Flush(); err != nil {
781 0 : return err
782 0 : }
783 1 : return nil
784 : }()
785 1 : if err != nil {
786 1 : fmt.Fprintln(stderr, err)
787 1 : }
788 : }
789 :
790 1 : func (d *dbT) runSet(cmd *cobra.Command, args []string) {
791 1 : stderr := cmd.ErrOrStderr()
792 1 : db, err := d.openDB(args[0], nonReadOnly{})
793 1 : if err != nil {
794 0 : fmt.Fprintf(stderr, "%s\n", err)
795 0 : return
796 0 : }
797 1 : defer d.closeDB(stderr, db)
798 1 : var k, v key
799 1 : if err := k.Set(args[1]); err != nil {
800 0 : fmt.Fprintf(stderr, "%s\n", err)
801 0 : return
802 0 : }
803 1 : if err := v.Set(args[2]); err != nil {
804 0 : fmt.Fprintf(stderr, "%s\n", err)
805 0 : return
806 0 : }
807 :
808 1 : if err := db.Set(k, v, nil); err != nil {
809 0 : fmt.Fprintf(stderr, "%s\n", err)
810 0 : }
811 : }
812 :
813 1 : func propArgs(props []props, getProp func(*props) interface{}) []interface{} {
814 1 : args := make([]interface{}, 0, len(props))
815 1 : for _, p := range props {
816 1 : args = append(args, getProp(&p))
817 1 : }
818 1 : return args
819 : }
820 :
821 : type props struct {
822 : Count uint64
823 : SmallestSeqNum base.SeqNum
824 : LargestSeqNum base.SeqNum
825 : DataSize uint64
826 : FilterSize uint64
827 : IndexSize uint64
828 : NumDataBlocks uint64
829 : NumIndexBlocks uint64
830 : NumDeletions uint64
831 : NumSizedDeletions uint64
832 : NumEntries uint64
833 : NumMergeOperands uint64
834 : NumRangeDeletions uint64
835 : NumRangeKeySets uint64
836 : NumRangeKeyUnSets uint64
837 : NumRangeKeyDeletes uint64
838 : RawKeySize uint64
839 : RawPointTombstoneKeySize uint64
840 : RawPointTombstoneValueSize uint64
841 : RawValueSize uint64
842 : SnapshotPinnedKeys uint64
843 : SnapshotPinnedKeySize uint64
844 : SnapshotPinnedValueSize uint64
845 : TopLevelIndexSize uint64
846 : }
847 :
848 1 : func (p *props) update(o props) {
849 1 : p.Count += o.Count
850 1 : if o.SmallestSeqNum != 0 && (o.SmallestSeqNum < p.SmallestSeqNum || p.SmallestSeqNum == 0) {
851 1 : p.SmallestSeqNum = o.SmallestSeqNum
852 1 : }
853 1 : if o.LargestSeqNum > p.LargestSeqNum {
854 1 : p.LargestSeqNum = o.LargestSeqNum
855 1 : }
856 1 : p.DataSize += o.DataSize
857 1 : p.FilterSize += o.FilterSize
858 1 : p.IndexSize += o.IndexSize
859 1 : p.NumDataBlocks += o.NumDataBlocks
860 1 : p.NumIndexBlocks += o.NumIndexBlocks
861 1 : p.NumDeletions += o.NumDeletions
862 1 : p.NumSizedDeletions += o.NumSizedDeletions
863 1 : p.NumEntries += o.NumEntries
864 1 : p.NumMergeOperands += o.NumMergeOperands
865 1 : p.NumRangeDeletions += o.NumRangeDeletions
866 1 : p.NumRangeKeySets += o.NumRangeKeySets
867 1 : p.NumRangeKeyUnSets += o.NumRangeKeyUnSets
868 1 : p.NumRangeKeyDeletes += o.NumRangeKeyDeletes
869 1 : p.RawKeySize += o.RawKeySize
870 1 : p.RawPointTombstoneKeySize += o.RawPointTombstoneKeySize
871 1 : p.RawPointTombstoneValueSize += o.RawPointTombstoneValueSize
872 1 : p.RawValueSize += o.RawValueSize
873 1 : p.SnapshotPinnedKeySize += o.SnapshotPinnedKeySize
874 1 : p.SnapshotPinnedValueSize += o.SnapshotPinnedValueSize
875 1 : p.SnapshotPinnedKeys += o.SnapshotPinnedKeys
876 1 : p.TopLevelIndexSize += o.TopLevelIndexSize
877 : }
878 :
879 : func (d *dbT) addProps(
880 : objProvider objstorage.Provider, m manifest.PhysicalFileMeta, p *props,
881 1 : ) error {
882 1 : ctx := context.Background()
883 1 : f, err := objProvider.OpenForReading(ctx, base.FileTypeTable, m.FileBacking.DiskFileNum, objstorage.OpenOptions{})
884 1 : if err != nil {
885 0 : return err
886 0 : }
887 1 : opts := sstable.ReaderOptions{
888 1 : Mergers: d.mergers,
889 1 : Comparers: d.comparers,
890 1 : }
891 1 : opts.SetInternal(sstableinternal.ReaderOptions{
892 1 : CacheOpts: sstableinternal.CacheOptions{
893 1 : FileNum: m.FileBacking.DiskFileNum,
894 1 : },
895 1 : })
896 1 : r, err := sstable.NewReader(ctx, f, sstable.ReaderOptions{
897 1 : Mergers: d.mergers,
898 1 : Comparers: d.comparers,
899 1 : })
900 1 : if err != nil {
901 0 : _ = f.Close()
902 0 : return err
903 0 : }
904 1 : p.update(props{
905 1 : Count: 1,
906 1 : SmallestSeqNum: m.SmallestSeqNum,
907 1 : LargestSeqNum: m.LargestSeqNum,
908 1 : DataSize: r.Properties.DataSize,
909 1 : FilterSize: r.Properties.FilterSize,
910 1 : IndexSize: r.Properties.IndexSize,
911 1 : NumDataBlocks: r.Properties.NumDataBlocks,
912 1 : NumIndexBlocks: 1 + r.Properties.IndexPartitions,
913 1 : NumDeletions: r.Properties.NumDeletions,
914 1 : NumSizedDeletions: r.Properties.NumSizedDeletions,
915 1 : NumEntries: r.Properties.NumEntries,
916 1 : NumMergeOperands: r.Properties.NumMergeOperands,
917 1 : NumRangeDeletions: r.Properties.NumRangeDeletions,
918 1 : NumRangeKeySets: r.Properties.NumRangeKeySets,
919 1 : NumRangeKeyUnSets: r.Properties.NumRangeKeyUnsets,
920 1 : NumRangeKeyDeletes: r.Properties.NumRangeKeyDels,
921 1 : RawKeySize: r.Properties.RawKeySize,
922 1 : RawPointTombstoneKeySize: r.Properties.RawPointTombstoneKeySize,
923 1 : RawPointTombstoneValueSize: r.Properties.RawPointTombstoneValueSize,
924 1 : RawValueSize: r.Properties.RawValueSize,
925 1 : SnapshotPinnedKeySize: r.Properties.SnapshotPinnedKeySize,
926 1 : SnapshotPinnedValueSize: r.Properties.SnapshotPinnedValueSize,
927 1 : SnapshotPinnedKeys: r.Properties.SnapshotPinnedKeys,
928 1 : TopLevelIndexSize: r.Properties.TopLevelIndexSize,
929 1 : })
930 1 : return r.Close()
931 : }
932 :
933 1 : func makePlural(singular string, count int64) string {
934 1 : if count > 1 {
935 1 : return fmt.Sprintf("%ss", singular)
936 1 : }
937 1 : return singular
938 : }
|