-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8008 from filecoin-project/feat/splitstore-sortle…
…ss-compaction splitstore sortless compaction
- Loading branch information
Showing
18 changed files
with
1,744 additions
and
233 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package splitstore | ||
|
||
import ( | ||
"bufio" | ||
"io" | ||
"os" | ||
|
||
"golang.org/x/xerrors" | ||
|
||
cid "github.com/ipfs/go-cid" | ||
mh "github.com/multiformats/go-multihash" | ||
) | ||
|
||
type Checkpoint struct { | ||
file *os.File | ||
buf *bufio.Writer | ||
} | ||
|
||
func NewCheckpoint(path string) (*Checkpoint, error) { | ||
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_SYNC, 0644) | ||
if err != nil { | ||
return nil, xerrors.Errorf("error creating checkpoint: %w", err) | ||
} | ||
buf := bufio.NewWriter(file) | ||
|
||
return &Checkpoint{ | ||
file: file, | ||
buf: buf, | ||
}, nil | ||
} | ||
|
||
func OpenCheckpoint(path string) (*Checkpoint, cid.Cid, error) { | ||
filein, err := os.Open(path) | ||
if err != nil { | ||
return nil, cid.Undef, xerrors.Errorf("error opening checkpoint for reading: %w", err) | ||
} | ||
defer filein.Close() //nolint:errcheck | ||
|
||
bufin := bufio.NewReader(filein) | ||
start, err := readRawCid(bufin, nil) | ||
if err != nil && err != io.EOF { | ||
return nil, cid.Undef, xerrors.Errorf("error reading cid from checkpoint: %w", err) | ||
} | ||
|
||
fileout, err := os.OpenFile(path, os.O_WRONLY|os.O_SYNC, 0644) | ||
if err != nil { | ||
return nil, cid.Undef, xerrors.Errorf("error opening checkpoint for writing: %w", err) | ||
} | ||
bufout := bufio.NewWriter(fileout) | ||
|
||
return &Checkpoint{ | ||
file: fileout, | ||
buf: bufout, | ||
}, start, nil | ||
} | ||
|
||
func (cp *Checkpoint) Set(c cid.Cid) error { | ||
if _, err := cp.file.Seek(0, io.SeekStart); err != nil { | ||
return xerrors.Errorf("error seeking beginning of checkpoint: %w", err) | ||
} | ||
|
||
if err := writeRawCid(cp.buf, c, true); err != nil { | ||
return xerrors.Errorf("error writing cid to checkpoint: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (cp *Checkpoint) Close() error { | ||
if cp.file == nil { | ||
return nil | ||
} | ||
|
||
err := cp.file.Close() | ||
cp.file = nil | ||
cp.buf = nil | ||
|
||
return err | ||
} | ||
|
||
func readRawCid(buf *bufio.Reader, hbuf []byte) (cid.Cid, error) { | ||
sz, err := buf.ReadByte() | ||
if err != nil { | ||
return cid.Undef, err // don't wrap EOF as it is not an error here | ||
} | ||
|
||
if hbuf == nil { | ||
hbuf = make([]byte, int(sz)) | ||
} else { | ||
hbuf = hbuf[:int(sz)] | ||
} | ||
|
||
if _, err := io.ReadFull(buf, hbuf); err != nil { | ||
return cid.Undef, xerrors.Errorf("error reading hash: %w", err) // wrap EOF, it's corrupt | ||
} | ||
|
||
hash, err := mh.Cast(hbuf) | ||
if err != nil { | ||
return cid.Undef, xerrors.Errorf("error casting multihash: %w", err) | ||
} | ||
|
||
return cid.NewCidV1(cid.Raw, hash), nil | ||
} | ||
|
||
func writeRawCid(buf *bufio.Writer, c cid.Cid, flush bool) error { | ||
hash := c.Hash() | ||
if err := buf.WriteByte(byte(len(hash))); err != nil { | ||
return err | ||
} | ||
if _, err := buf.Write(hash); err != nil { | ||
return err | ||
} | ||
if flush { | ||
return buf.Flush() | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package splitstore | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/ipfs/go-cid" | ||
"github.com/multiformats/go-multihash" | ||
) | ||
|
||
func TestCheckpoint(t *testing.T) { | ||
dir, err := ioutil.TempDir("", "checkpoint.*") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
t.Cleanup(func() { | ||
_ = os.RemoveAll(dir) | ||
}) | ||
|
||
path := filepath.Join(dir, "checkpoint") | ||
|
||
makeCid := func(key string) cid.Cid { | ||
h, err := multihash.Sum([]byte(key), multihash.SHA2_256, -1) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
return cid.NewCidV1(cid.Raw, h) | ||
} | ||
|
||
k1 := makeCid("a") | ||
k2 := makeCid("b") | ||
k3 := makeCid("c") | ||
k4 := makeCid("d") | ||
|
||
cp, err := NewCheckpoint(path) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := cp.Set(k1); err != nil { | ||
t.Fatal(err) | ||
} | ||
if err := cp.Set(k2); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := cp.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
cp, start, err := OpenCheckpoint(path) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !start.Equals(k2) { | ||
t.Fatalf("expected start to be %s; got %s", k2, start) | ||
} | ||
|
||
if err := cp.Set(k3); err != nil { | ||
t.Fatal(err) | ||
} | ||
if err := cp.Set(k4); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := cp.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
cp, start, err = OpenCheckpoint(path) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !start.Equals(k4) { | ||
t.Fatalf("expected start to be %s; got %s", k4, start) | ||
} | ||
|
||
if err := cp.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// also test correct operation with an empty checkpoint | ||
cp, err = NewCheckpoint(path) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := cp.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
cp, start, err = OpenCheckpoint(path) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if start.Defined() { | ||
t.Fatal("expected start to be undefined") | ||
} | ||
|
||
if err := cp.Set(k1); err != nil { | ||
t.Fatal(err) | ||
} | ||
if err := cp.Set(k2); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := cp.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
cp, start, err = OpenCheckpoint(path) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !start.Equals(k2) { | ||
t.Fatalf("expected start to be %s; got %s", k2, start) | ||
} | ||
|
||
if err := cp.Set(k3); err != nil { | ||
t.Fatal(err) | ||
} | ||
if err := cp.Set(k4); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := cp.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
cp, start, err = OpenCheckpoint(path) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !start.Equals(k4) { | ||
t.Fatalf("expected start to be %s; got %s", k4, start) | ||
} | ||
|
||
if err := cp.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package splitstore | ||
|
||
import ( | ||
"bufio" | ||
"io" | ||
"os" | ||
|
||
"golang.org/x/xerrors" | ||
|
||
cid "github.com/ipfs/go-cid" | ||
) | ||
|
||
type ColdSetWriter struct { | ||
file *os.File | ||
buf *bufio.Writer | ||
} | ||
|
||
type ColdSetReader struct { | ||
file *os.File | ||
buf *bufio.Reader | ||
} | ||
|
||
func NewColdSetWriter(path string) (*ColdSetWriter, error) { | ||
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) | ||
if err != nil { | ||
return nil, xerrors.Errorf("error creating coldset: %w", err) | ||
} | ||
buf := bufio.NewWriter(file) | ||
|
||
return &ColdSetWriter{ | ||
file: file, | ||
buf: buf, | ||
}, nil | ||
} | ||
|
||
func NewColdSetReader(path string) (*ColdSetReader, error) { | ||
file, err := os.Open(path) | ||
if err != nil { | ||
return nil, xerrors.Errorf("error opening coldset: %w", err) | ||
} | ||
buf := bufio.NewReader(file) | ||
|
||
return &ColdSetReader{ | ||
file: file, | ||
buf: buf, | ||
}, nil | ||
} | ||
|
||
func (s *ColdSetWriter) Write(c cid.Cid) error { | ||
return writeRawCid(s.buf, c, false) | ||
} | ||
|
||
func (s *ColdSetWriter) Close() error { | ||
if s.file == nil { | ||
return nil | ||
} | ||
|
||
err1 := s.buf.Flush() | ||
err2 := s.file.Close() | ||
s.buf = nil | ||
s.file = nil | ||
|
||
if err1 != nil { | ||
return err1 | ||
} | ||
return err2 | ||
} | ||
|
||
func (s *ColdSetReader) ForEach(f func(cid.Cid) error) error { | ||
hbuf := make([]byte, 256) | ||
for { | ||
next, err := readRawCid(s.buf, hbuf) | ||
if err != nil { | ||
if err == io.EOF { | ||
return nil | ||
} | ||
|
||
return xerrors.Errorf("error reading coldset: %w", err) | ||
} | ||
|
||
if err := f(next); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
func (s *ColdSetReader) Reset() error { | ||
_, err := s.file.Seek(0, io.SeekStart) | ||
return err | ||
} | ||
|
||
func (s *ColdSetReader) Close() error { | ||
if s.file == nil { | ||
return nil | ||
} | ||
|
||
err := s.file.Close() | ||
s.file = nil | ||
s.buf = nil | ||
|
||
return err | ||
} |
Oops, something went wrong.