github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/tool/db.go (about)

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