Skip to content

Commit 424f6c0

Browse files
committed
Add support for db vacate command
1 parent 8ad0eed commit 424f6c0

File tree

13 files changed

+380
-8
lines changed

13 files changed

+380
-8
lines changed

api/api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ type QueryWarning struct {
122122
Warning string `json:"warning" super:"warning"`
123123
}
124124

125+
type VacateResponse struct {
126+
CommitIDs []ksuid.KSUID `super:"commit_ids"`
127+
}
128+
125129
type VacuumResponse struct {
126130
ObjectIDs []ksuid.KSUID `super:"object_ids"`
127131
}

api/client/connection.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,17 @@ func (c *Connection) delete(ctx context.Context, poolID ksuid.KSUID, branchName
373373
return commit, err
374374
}
375375

376+
func (c *Connection) Vacate(ctx context.Context, pool, revision string, dryrun bool) (api.VacateResponse, error) {
377+
path := urlPath("pool", pool, "revision", revision, "vacate")
378+
if dryrun {
379+
path += "?dryrun=true"
380+
}
381+
req := c.NewRequest(ctx, http.MethodPost, path, nil)
382+
var res api.VacateResponse
383+
err := c.doAndUnmarshal(req, &res)
384+
return res, err
385+
}
386+
376387
func (c *Connection) Vacuum(ctx context.Context, pool, revision string, dryrun bool) (api.VacuumResponse, error) {
377388
path := urlPath("pool", pool, "revision", revision, "vacuum")
378389
if dryrun {

cmd/super/db/vacate/command.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ package vacate
33
import (
44
"errors"
55
"flag"
6+
"fmt"
7+
"strings"
68

9+
"github.com/brimdata/super/cli/poolflags"
710
"github.com/brimdata/super/cmd/super/db"
811
"github.com/brimdata/super/pkg/charm"
12+
"github.com/brimdata/super/pkg/plural"
913
)
1014

1115
var spec = &charm.Spec{
1216
Name: "vacate",
1317
Usage: "vacate [options] commit",
14-
Short: "compact a pool's commit history by squashing old commit objects",
18+
Short: "compact a pool's commit history by removing old commit objects",
1519
Long: `
1620
See https://superdb.org/command/db.html#super-db-vacate
1721
`,
@@ -24,12 +28,62 @@ func init() {
2428

2529
type Command struct {
2630
*db.Command
31+
poolFlags poolflags.Flags
32+
dryrun bool
33+
force bool
2734
}
2835

2936
func New(parent charm.Command, f *flag.FlagSet) (charm.Command, error) {
30-
return &Command{Command: parent.(*db.Command)}, nil
37+
c := &Command{Command: parent.(*db.Command)}
38+
c.poolFlags.SetFlags(f)
39+
f.BoolVar(&c.dryrun, "dryrun", false, "view the number of commits to be deleted")
40+
f.BoolVar(&c.force, "f", false, "do not prompt for confirmation")
41+
return c, nil
3142
}
3243

3344
func (c *Command) Run(args []string) error {
34-
return errors.New("issue #2545")
45+
ctx, cleanup, err := c.Init()
46+
if err != nil {
47+
return err
48+
}
49+
defer cleanup()
50+
at, err := c.poolFlags.HEAD()
51+
if err != nil {
52+
return err
53+
}
54+
db, err := c.DBFlags.Open(ctx)
55+
if err != nil {
56+
return err
57+
}
58+
verb := "would vacate"
59+
if !c.dryrun {
60+
verb = "vacated"
61+
if err := c.confirm(at.String()); err != nil {
62+
return err
63+
}
64+
}
65+
cids, err := db.Vacate(ctx, at.Pool, at.Branch, c.dryrun)
66+
if err != nil {
67+
return err
68+
}
69+
if !c.DBFlags.Quiet {
70+
fmt.Printf("%s %d commit%s\n", verb, len(cids), plural.Slice(cids, "s"))
71+
}
72+
return nil
73+
}
74+
75+
func (c *Command) confirm(name string) error {
76+
if c.force {
77+
return nil
78+
}
79+
fmt.Printf("Are you sure you want to vacate previous commits from %q? There is no going back... [y|n]\n", name)
80+
var input string
81+
if _, err := fmt.Scanln(&input); err != nil {
82+
return err
83+
}
84+
input = strings.ToLower(input)
85+
if input == "y" || input == "yes" {
86+
return nil
87+
}
88+
return errors.New("operation canceled")
3589
}

db/api/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Interface interface {
3838
Revert(ctx context.Context, poolID ksuid.KSUID, branch string, commitID ksuid.KSUID, commit api.CommitMessage) (ksuid.KSUID, error)
3939
AddVectors(ctx context.Context, pool, revision string, objects []ksuid.KSUID, message api.CommitMessage) (ksuid.KSUID, error)
4040
DeleteVectors(ctx context.Context, pool, revision string, objects []ksuid.KSUID, message api.CommitMessage) (ksuid.KSUID, error)
41+
Vacate(ctx context.Context, pool, revision string, dryrun bool) ([]ksuid.KSUID, error)
4142
Vacuum(ctx context.Context, pool, revision string, dryrun bool) ([]ksuid.KSUID, error)
4243
}
4344

db/api/local.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,22 @@ func (l *local) DeleteVectors(ctx context.Context, pool, revision string, ids []
206206
return branch.DeleteVectors(ctx, ids, message.Author, message.Body)
207207
}
208208

209+
func (l *local) Vacate(ctx context.Context, pool, revision string, dryrun bool) ([]ksuid.KSUID, error) {
210+
poolID, err := l.PoolID(ctx, pool)
211+
if err != nil {
212+
return nil, err
213+
}
214+
p, err := l.db.OpenPool(ctx, poolID)
215+
if err != nil {
216+
return nil, err
217+
}
218+
commit, err := p.ResolveRevision(ctx, revision)
219+
if err != nil {
220+
return nil, err
221+
}
222+
return p.Vacate(ctx, commit, dryrun)
223+
}
224+
209225
func (l *local) Vacuum(ctx context.Context, pool, revision string, dryrun bool) ([]ksuid.KSUID, error) {
210226
poolID, err := l.PoolID(ctx, pool)
211227
if err != nil {

db/api/remote.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ func (r *remote) DeleteVectors(ctx context.Context, pool, revision string, ids [
148148
return res.Commit, err
149149
}
150150

151+
func (r *remote) Vacate(ctx context.Context, pool, revision string, dryrun bool) ([]ksuid.KSUID, error) {
152+
res, err := r.conn.Vacate(ctx, pool, revision, dryrun)
153+
return res.CommitIDs, err
154+
}
155+
151156
func (r *remote) Vacuum(ctx context.Context, pool, revision string, dryrun bool) ([]ksuid.KSUID, error) {
152157
res, err := r.conn.Vacuum(ctx, pool, revision, dryrun)
153158
return res.ObjectIDs, err

db/commits/reader.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package commits
22

33
import (
44
"context"
5+
"errors"
6+
"io/fs"
57

68
"github.com/brimdata/super"
79
"github.com/brimdata/super/sio"
@@ -37,6 +39,10 @@ func (r *LogReader) Read() (*super.Value, error) {
3739
}
3840
_, commitObject, err := r.store.GetBytes(r.ctx, r.cursor)
3941
if err != nil {
42+
if errors.Is(err, fs.ErrNotExist) {
43+
r.cursor = ksuid.Nil
44+
err = nil
45+
}
4046
return nil, err
4147
}
4248
next := commitObject.Parent

db/commits/store.go

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"io/fs"
10+
"runtime"
1011
"sync"
1112

1213
"github.com/brimdata/super"
@@ -18,6 +19,7 @@ import (
1819
arc "github.com/hashicorp/golang-lru/arc/v2"
1920
"github.com/segmentio/ksuid"
2021
"go.uber.org/zap"
22+
"golang.org/x/sync/errgroup"
2123
)
2224

2325
var (
@@ -98,6 +100,27 @@ func (s *Store) Remove(ctx context.Context, o *Object) error {
98100
return s.engine.Delete(ctx, s.pathOf(o.Commit))
99101
}
100102

103+
// DANGER ZONE - commits should only be removed once a new base has been
104+
// established.
105+
func (s *Store) DropCommits(ctx context.Context, commits []ksuid.KSUID) error {
106+
group, ctx := errgroup.WithContext(ctx)
107+
group.SetLimit(runtime.GOMAXPROCS(0))
108+
for _, c := range commits {
109+
group.Go(func() error {
110+
return s.engine.Delete(ctx, s.pathOf(c))
111+
})
112+
// delete snapshot (if it exists)
113+
group.Go(func() error {
114+
err := s.engine.Delete(ctx, s.snapshotPathOf(c))
115+
if errors.Is(err, fs.ErrNotExist) {
116+
err = nil
117+
}
118+
return err
119+
})
120+
}
121+
return group.Wait()
122+
}
123+
101124
func (s *Store) Snapshot(ctx context.Context, leaf ksuid.KSUID) (*Snapshot, error) {
102125
if snap, ok := s.snapshots.Get(leaf); ok {
103126
return snap, nil
@@ -108,6 +131,18 @@ func (s *Store) Snapshot(ctx context.Context, leaf ksuid.KSUID) (*Snapshot, erro
108131
s.snapshots.Add(leaf, snap)
109132
return snap, nil
110133
}
134+
snap, err := s.buildSnapshot(ctx, leaf)
135+
if err != nil {
136+
return nil, err
137+
}
138+
if err := s.putSnapshot(ctx, leaf, snap); err != nil {
139+
s.logger.Error("Storing snapshot", zap.Error(err))
140+
}
141+
s.snapshots.Add(leaf, snap)
142+
return snap, nil
143+
}
144+
145+
func (s *Store) buildSnapshot(ctx context.Context, leaf ksuid.KSUID) (*Snapshot, error) {
111146
var objects []*Object
112147
var base *Snapshot
113148
for at := leaf; at != ksuid.Nil; {
@@ -135,6 +170,18 @@ func (s *Store) Snapshot(ctx context.Context, leaf ksuid.KSUID) (*Snapshot, erro
135170
// No snapshot found, so wait for data object.
136171
wg.Wait()
137172
if oErr != nil {
173+
if errors.Is(oErr, fs.ErrNotExist) {
174+
// If object get error is not exists then perhaps commits have
175+
// been vacated at this point, check if previous is a base
176+
// commit.
177+
snap, err := s.getBase(ctx)
178+
if err != nil {
179+
return nil, fmt.Errorf("system error: error fetching base: %w", err)
180+
}
181+
s.snapshots.Add(ksuid.Nil, snap)
182+
base = snap
183+
break
184+
}
138185
return nil, oErr
139186
}
140187
objects = append(objects, o)
@@ -153,10 +200,6 @@ func (s *Store) Snapshot(ctx context.Context, leaf ksuid.KSUID) (*Snapshot, erro
153200
}
154201
}
155202
}
156-
if err := s.putSnapshot(ctx, leaf, snap); err != nil {
157-
s.logger.Error("Storing snapshot", zap.Error(err))
158-
}
159-
s.snapshots.Add(leaf, snap)
160203
return snap, nil
161204
}
162205

@@ -181,6 +224,27 @@ func (s *Store) snapshotPathOf(commit ksuid.KSUID) *storage.URI {
181224
return s.path.JoinPath(commit.String() + ".snap.bsup")
182225
}
183226

227+
func (s *Store) getBase(ctx context.Context) (*Snapshot, error) {
228+
r, err := s.engine.Get(ctx, s.basePath())
229+
if err != nil {
230+
return nil, err
231+
}
232+
defer r.Close()
233+
return decodeSnapshot(r)
234+
}
235+
236+
func (s *Store) putBase(ctx context.Context, snap *Snapshot) error {
237+
b, err := snap.serialize()
238+
if err != nil {
239+
return err
240+
}
241+
return storage.Put(ctx, s.engine, s.basePath(), bytes.NewReader(b))
242+
}
243+
244+
func (s *Store) basePath() *storage.URI {
245+
return s.path.JoinPath("base.bsup")
246+
}
247+
184248
// Path return the entire path from the commit object to the root
185249
// in leaf to root order.
186250
func (s *Store) Path(ctx context.Context, leaf ksuid.KSUID) ([]ksuid.KSUID, error) {
@@ -210,11 +274,16 @@ func (s *Store) PathRange(ctx context.Context, from, to ksuid.KSUID) ([]ksuid.KS
210274
}
211275
break
212276
}
213-
path = append(path, at)
214277
o, err := s.Get(ctx, at)
215278
if err != nil {
279+
// If we get fs.ErrNotExist it means we have vacated and so we can
280+
// just return the path at this point.
281+
if errors.Is(err, fs.ErrNotExist) && to.IsNil() {
282+
break
283+
}
216284
return nil, err
217285
}
286+
path = append(path, at)
218287
if at == to {
219288
break
220289
}
@@ -247,6 +316,9 @@ func (s *Store) ReadAll(ctx context.Context, commit, stop ksuid.KSUID) ([]byte,
247316
for commit != ksuid.Nil && commit != stop {
248317
b, commitObject, err := s.GetBytes(ctx, commit)
249318
if err != nil {
319+
if errors.Is(err, fs.ErrNotExist) {
320+
break
321+
}
250322
return nil, err
251323
}
252324
size += len(b)
@@ -341,6 +413,30 @@ func (s *Store) PatchOfPath(ctx context.Context, base *Snapshot, baseID, commit
341413
return patch, nil
342414
}
343415

416+
// SetBase establishes a new base (snapshot) at the provided commit and resets
417+
// the attached caches. The caller is responsible for deleting prior commits.
418+
func (s *Store) SetBase(ctx context.Context, commit ksuid.KSUID) (*Snapshot, error) {
419+
path, err := s.Path(ctx, commit)
420+
if err != nil {
421+
return nil, err
422+
}
423+
if len(path) <= 1 {
424+
return nil, errors.New("cannot set base on earliest commit")
425+
}
426+
// Create snapshot of previous commit.
427+
snap, err := s.buildSnapshot(ctx, path[1])
428+
if err != nil {
429+
return nil, err
430+
}
431+
if err := s.putBase(ctx, snap); err != nil {
432+
return nil, err
433+
}
434+
s.cache.Purge()
435+
s.paths.Purge()
436+
s.snapshots.Purge()
437+
return snap, nil
438+
}
439+
344440
// Vacuumable returns the set of data.Objects in the path of leaf that are not referenced
345441
// by the leaf's snapshot.
346442
func (s *Store) Vacuumable(ctx context.Context, leaf ksuid.KSUID, out chan<- *data.Object) error {

0 commit comments

Comments
 (0)