lib/db, lib/model: Add sequence->deviceKey to db for sending indexes (#4906)
Instead of walking and unmarshalling the entire db and sorting the resulting file infos by sequence, add store device keys by sequence number in the database. Thus only the required file infos need be unmarshalled and are already sorted by index.
This commit is contained in:
@@ -35,10 +35,13 @@ type Instance struct {
|
||||
}
|
||||
|
||||
const (
|
||||
keyPrefixLen = 1
|
||||
keyFolderLen = 4 // indexed
|
||||
keyDeviceLen = 4 // indexed
|
||||
keyHashLen = 32
|
||||
keyPrefixLen = 1
|
||||
keyFolderLen = 4 // indexed
|
||||
keyDeviceLen = 4 // indexed
|
||||
keySequenceLen = 8
|
||||
keyHashLen = 32
|
||||
|
||||
maxInt64 int64 = 1<<63 - 1
|
||||
)
|
||||
|
||||
func Open(file string) (*Instance, error) {
|
||||
@@ -88,13 +91,21 @@ func (db *Instance) UpdateSchema() {
|
||||
miscDB := NewNamespacedKV(db, string(KeyTypeMiscData))
|
||||
prevVersion, _ := miscDB.Int64("dbVersion")
|
||||
|
||||
if prevVersion >= dbVersion {
|
||||
return
|
||||
}
|
||||
|
||||
l.Infof("Updating database schema version from %v to %v...", prevVersion, dbVersion)
|
||||
|
||||
if prevVersion == 0 {
|
||||
db.updateSchema0to1()
|
||||
}
|
||||
|
||||
if prevVersion != dbVersion {
|
||||
miscDB.PutInt64("dbVersion", dbVersion)
|
||||
if prevVersion <= 1 {
|
||||
db.updateSchema1to2()
|
||||
}
|
||||
|
||||
l.Infof("Finished updating database schema version from %v to %v", prevVersion, dbVersion)
|
||||
miscDB.PutInt64("dbVersion", dbVersion)
|
||||
}
|
||||
|
||||
// Committed returns the number of items committed to the database since startup
|
||||
@@ -112,9 +123,10 @@ func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, m
|
||||
defer t.close()
|
||||
|
||||
var fk []byte
|
||||
var gk []byte
|
||||
for _, f := range fs {
|
||||
name := []byte(f.Name)
|
||||
fk = db.deviceKeyInto(fk[:cap(fk)], folder, device, name)
|
||||
fk = db.deviceKeyInto(fk, folder, device, name)
|
||||
|
||||
// Get and unmarshal the file entry. If it doesn't exist or can't be
|
||||
// unmarshalled we'll add it as a new entry.
|
||||
@@ -135,8 +147,10 @@ func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, m
|
||||
}
|
||||
meta.addFile(devID, f)
|
||||
|
||||
t.insertFile(folder, device, f)
|
||||
t.updateGlobal(folder, device, f, meta)
|
||||
t.insertFile(fk, folder, device, f)
|
||||
|
||||
gk = db.globalKeyInto(gk, folder, name)
|
||||
t.updateGlobal(gk, folder, device, f, meta)
|
||||
|
||||
// Write out and reuse the batch every few records, to avoid the batch
|
||||
// growing too large and thus allocating unnecessarily much memory.
|
||||
@@ -144,6 +158,33 @@ func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, m
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Instance) addSequences(folder []byte, fs []protocol.FileInfo) {
|
||||
t := db.newReadWriteTransaction()
|
||||
defer t.close()
|
||||
|
||||
var sk []byte
|
||||
var dk []byte
|
||||
for _, f := range fs {
|
||||
sk = db.sequenceKeyInto(sk, folder, f.Sequence)
|
||||
dk = db.deviceKeyInto(dk, folder, protocol.LocalDeviceID[:], []byte(f.Name))
|
||||
t.Put(sk, dk)
|
||||
l.Debugf("adding sequence; folder=%q sequence=%v %v", folder, f.Sequence, f.Name)
|
||||
t.checkFlush()
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Instance) removeSequences(folder []byte, fs []protocol.FileInfo) {
|
||||
t := db.newReadWriteTransaction()
|
||||
defer t.close()
|
||||
|
||||
var sk []byte
|
||||
for _, f := range fs {
|
||||
t.Delete(db.sequenceKeyInto(sk, folder, f.Sequence))
|
||||
l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, f.Sequence, f.Name)
|
||||
t.checkFlush()
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Instance) withHave(folder, device, prefix []byte, truncate bool, fn Iterator) {
|
||||
t := db.newReadOnlyTransaction()
|
||||
defer t.close()
|
||||
@@ -171,7 +212,26 @@ func (db *Instance) withHave(folder, device, prefix []byte, truncate bool, fn It
|
||||
l.Debugln("unmarshal error:", err)
|
||||
continue
|
||||
}
|
||||
if cont := fn(f); !cont {
|
||||
if !fn(f) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Instance) withHaveSequence(folder []byte, startSeq int64, fn Iterator) {
|
||||
t := db.newReadOnlyTransaction()
|
||||
defer t.close()
|
||||
|
||||
dbi := t.NewIterator(&util.Range{Start: db.sequenceKey(folder, startSeq), Limit: db.sequenceKey(folder, maxInt64)}, nil)
|
||||
defer dbi.Release()
|
||||
|
||||
for dbi.Next() {
|
||||
f, ok := db.getFile(dbi.Value())
|
||||
if !ok {
|
||||
l.Debugln("missing file for sequence number", db.sequenceKeySequence(dbi.Key()))
|
||||
continue
|
||||
}
|
||||
if !fn(f) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -184,6 +244,8 @@ func (db *Instance) withAllFolderTruncated(folder []byte, fn func(device []byte,
|
||||
dbi := t.NewIterator(util.BytesPrefix(db.deviceKey(folder, nil, nil)[:keyPrefixLen+keyFolderLen]), nil)
|
||||
defer dbi.Release()
|
||||
|
||||
var gk []byte
|
||||
|
||||
for dbi.Next() {
|
||||
device := db.deviceKeyDevice(dbi.Key())
|
||||
var f FileInfoTruncated
|
||||
@@ -200,20 +262,37 @@ func (db *Instance) withAllFolderTruncated(folder []byte, fn func(device []byte,
|
||||
switch f.Name {
|
||||
case "", ".", "..", "/": // A few obviously invalid filenames
|
||||
l.Infof("Dropping invalid filename %q from database", f.Name)
|
||||
t.removeFromGlobal(folder, device, nil, nil)
|
||||
name := []byte(f.Name)
|
||||
gk = db.globalKeyInto(gk, folder, name)
|
||||
t.removeFromGlobal(gk, folder, device, name, nil)
|
||||
t.Delete(dbi.Key())
|
||||
t.checkFlush()
|
||||
continue
|
||||
}
|
||||
|
||||
if cont := fn(device, f); !cont {
|
||||
if !fn(device, f) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Instance) getFile(folder, device, file []byte) (protocol.FileInfo, bool) {
|
||||
return getFile(db, db.deviceKey(folder, device, file))
|
||||
func (db *Instance) getFile(key []byte) (protocol.FileInfo, bool) {
|
||||
bs, err := db.Get(key, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
return protocol.FileInfo{}, false
|
||||
}
|
||||
if err != nil {
|
||||
l.Debugln("surprise error:", err)
|
||||
return protocol.FileInfo{}, false
|
||||
}
|
||||
|
||||
var f protocol.FileInfo
|
||||
err = f.Unmarshal(bs)
|
||||
if err != nil {
|
||||
l.Debugln("unmarshal error:", err)
|
||||
return protocol.FileInfo{}, false
|
||||
}
|
||||
return f, true
|
||||
}
|
||||
|
||||
func (db *Instance) getGlobal(folder, file []byte, truncate bool) (FileIntf, bool) {
|
||||
@@ -286,7 +365,7 @@ func (db *Instance) withGlobal(folder, prefix []byte, truncate bool, fn Iterator
|
||||
return
|
||||
}
|
||||
|
||||
fk = db.deviceKeyInto(fk[:cap(fk)], folder, vl.Versions[0].Device, name)
|
||||
fk = db.deviceKeyInto(fk, folder, vl.Versions[0].Device, name)
|
||||
bs, err := t.Get(fk, nil)
|
||||
if err != nil {
|
||||
l.Debugln("surprise error:", err)
|
||||
@@ -299,7 +378,7 @@ func (db *Instance) withGlobal(folder, prefix []byte, truncate bool, fn Iterator
|
||||
continue
|
||||
}
|
||||
|
||||
if cont := fn(f); !cont {
|
||||
if !fn(f) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -392,7 +471,7 @@ func (db *Instance) withNeed(folder, device []byte, truncate bool, fn Iterator)
|
||||
continue
|
||||
}
|
||||
|
||||
fk = db.deviceKeyInto(fk[:cap(fk)], folder, vl.Versions[i].Device, name)
|
||||
fk = db.deviceKeyInto(fk, folder, vl.Versions[i].Device, name)
|
||||
bs, err := t.Get(fk, nil)
|
||||
if err != nil {
|
||||
l.Debugln("surprise error:", err)
|
||||
@@ -412,7 +491,7 @@ func (db *Instance) withNeed(folder, device []byte, truncate bool, fn Iterator)
|
||||
|
||||
l.Debugf("need folder=%q device=%v name=%q need=%v have=%v invalid=%v haveV=%v globalV=%v globalDev=%v", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveFileVersion.Invalid, haveFileVersion.Version, needVersion, needDevice)
|
||||
|
||||
if cont := fn(gf); !cont {
|
||||
if !fn(gf) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -478,10 +557,13 @@ func (db *Instance) dropDeviceFolder(device, folder []byte, meta *metadataTracke
|
||||
dbi := t.NewIterator(util.BytesPrefix(db.deviceKey(folder, device, nil)), nil)
|
||||
defer dbi.Release()
|
||||
|
||||
var gk []byte
|
||||
|
||||
for dbi.Next() {
|
||||
key := dbi.Key()
|
||||
name := db.deviceKeyName(key)
|
||||
t.removeFromGlobal(folder, device, name, meta)
|
||||
gk = db.globalKeyInto(gk, folder, name)
|
||||
t.removeFromGlobal(gk, folder, device, name, meta)
|
||||
t.Delete(key)
|
||||
t.checkFlush()
|
||||
}
|
||||
@@ -512,8 +594,7 @@ func (db *Instance) checkGlobals(folder []byte, meta *metadataTracker) {
|
||||
name := db.globalKeyName(gk)
|
||||
var newVL VersionList
|
||||
for i, version := range vl.Versions {
|
||||
fk = db.deviceKeyInto(fk[:cap(fk)], folder, version.Device, name)
|
||||
|
||||
fk = db.deviceKeyInto(fk, folder, version.Device, name)
|
||||
_, err := t.Get(fk, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
continue
|
||||
@@ -525,7 +606,7 @@ func (db *Instance) checkGlobals(folder []byte, meta *metadataTracker) {
|
||||
newVL.Versions = append(newVL.Versions, version)
|
||||
|
||||
if i == 0 {
|
||||
if fi, ok := t.getFile(folder, version.Device, name); ok {
|
||||
if fi, ok := db.getFile(fk); ok {
|
||||
meta.addFile(globalDeviceID, fi)
|
||||
}
|
||||
}
|
||||
@@ -550,18 +631,20 @@ func (db *Instance) updateSchema0to1() {
|
||||
changedFolders := make(map[string]struct{})
|
||||
ignAdded := 0
|
||||
meta := newMetadataTracker() // dummy metadata tracker
|
||||
var gk []byte
|
||||
|
||||
for dbi.Next() {
|
||||
folder := db.deviceKeyFolder(dbi.Key())
|
||||
device := db.deviceKeyDevice(dbi.Key())
|
||||
name := string(db.deviceKeyName(dbi.Key()))
|
||||
name := db.deviceKeyName(dbi.Key())
|
||||
|
||||
// Remove files with absolute path (see #4799)
|
||||
if strings.HasPrefix(name, "/") {
|
||||
if strings.HasPrefix(string(name), "/") {
|
||||
if _, ok := changedFolders[string(folder)]; !ok {
|
||||
changedFolders[string(folder)] = struct{}{}
|
||||
}
|
||||
t.removeFromGlobal(folder, device, nil, nil)
|
||||
gk = db.globalKeyInto(gk, folder, name)
|
||||
t.removeFromGlobal(gk, folder, device, nil, nil)
|
||||
t.Delete(dbi.Key())
|
||||
t.checkFlush()
|
||||
continue
|
||||
@@ -590,7 +673,8 @@ func (db *Instance) updateSchema0to1() {
|
||||
|
||||
// Add invalid files to global list
|
||||
if f.Invalid {
|
||||
if t.updateGlobal(folder, device, f, meta) {
|
||||
gk = db.globalKeyInto(gk, folder, name)
|
||||
if t.updateGlobal(gk, folder, device, f, meta) {
|
||||
if _, ok := changedFolders[string(folder)]; !ok {
|
||||
changedFolders[string(folder)] = struct{}{}
|
||||
}
|
||||
@@ -606,6 +690,25 @@ func (db *Instance) updateSchema0to1() {
|
||||
l.Infof("Updated symlink type for %d index entries and added %d invalid files to global list", symlinkConv, ignAdded)
|
||||
}
|
||||
|
||||
func (db *Instance) updateSchema1to2() {
|
||||
t := db.newReadWriteTransaction()
|
||||
defer t.close()
|
||||
|
||||
var sk []byte
|
||||
var dk []byte
|
||||
|
||||
for _, folderStr := range db.ListFolders() {
|
||||
folder := []byte(folderStr)
|
||||
db.withHave(folder, protocol.LocalDeviceID[:], nil, true, func(f FileIntf) bool {
|
||||
sk = db.sequenceKeyInto(sk, folder, f.SequenceNo())
|
||||
dk = db.deviceKeyInto(dk, folder, protocol.LocalDeviceID[:], []byte(f.FileName()))
|
||||
t.Put(sk, dk)
|
||||
t.checkFlush()
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// deviceKey returns a byte slice encoding the following information:
|
||||
// keyTypeDevice (1 byte)
|
||||
// folder (4 bytes)
|
||||
@@ -615,16 +718,14 @@ func (db *Instance) deviceKey(folder, device, file []byte) []byte {
|
||||
return db.deviceKeyInto(nil, folder, device, file)
|
||||
}
|
||||
|
||||
func (db *Instance) deviceKeyInto(k []byte, folder, device, file []byte) []byte {
|
||||
func (db *Instance) deviceKeyInto(k, folder, device, file []byte) []byte {
|
||||
reqLen := keyPrefixLen + keyFolderLen + keyDeviceLen + len(file)
|
||||
if len(k) < reqLen {
|
||||
k = make([]byte, reqLen)
|
||||
}
|
||||
k = resize(k, reqLen)
|
||||
k[0] = KeyTypeDevice
|
||||
binary.BigEndian.PutUint32(k[keyPrefixLen:], db.folderIdx.ID(folder))
|
||||
binary.BigEndian.PutUint32(k[keyPrefixLen+keyFolderLen:], db.deviceIdx.ID(device))
|
||||
copy(k[keyPrefixLen+keyFolderLen+keyDeviceLen:], file)
|
||||
return k[:reqLen]
|
||||
return k
|
||||
}
|
||||
|
||||
// deviceKeyName returns the device ID from the key
|
||||
@@ -655,11 +756,16 @@ func (db *Instance) deviceKeyDevice(key []byte) []byte {
|
||||
// folder (4 bytes)
|
||||
// name (variable size)
|
||||
func (db *Instance) globalKey(folder, file []byte) []byte {
|
||||
k := make([]byte, keyPrefixLen+keyFolderLen+len(file))
|
||||
k[0] = KeyTypeGlobal
|
||||
binary.BigEndian.PutUint32(k[keyPrefixLen:], db.folderIdx.ID(folder))
|
||||
copy(k[keyPrefixLen+keyFolderLen:], file)
|
||||
return k
|
||||
return db.globalKeyInto(nil, folder, file)
|
||||
}
|
||||
|
||||
func (db *Instance) globalKeyInto(gk, folder, file []byte) []byte {
|
||||
reqLen := keyPrefixLen + keyFolderLen + len(file)
|
||||
gk = resize(gk, reqLen)
|
||||
gk[0] = KeyTypeGlobal
|
||||
binary.BigEndian.PutUint32(gk[keyPrefixLen:], db.folderIdx.ID(folder))
|
||||
copy(gk[keyPrefixLen+keyFolderLen:], file)
|
||||
return gk[:reqLen]
|
||||
}
|
||||
|
||||
// globalKeyName returns the filename from the key
|
||||
@@ -672,6 +778,28 @@ func (db *Instance) globalKeyFolder(key []byte) ([]byte, bool) {
|
||||
return db.folderIdx.Val(binary.BigEndian.Uint32(key[keyPrefixLen:]))
|
||||
}
|
||||
|
||||
// sequenceKey returns a byte slice encoding the following information:
|
||||
// KeyTypeSequence (1 byte)
|
||||
// folder (4 bytes)
|
||||
// sequence number (8 bytes)
|
||||
func (db *Instance) sequenceKey(folder []byte, seq int64) []byte {
|
||||
return db.sequenceKeyInto(nil, folder, seq)
|
||||
}
|
||||
|
||||
func (db *Instance) sequenceKeyInto(k []byte, folder []byte, seq int64) []byte {
|
||||
reqLen := keyPrefixLen + keyFolderLen + keySequenceLen
|
||||
k = resize(k, reqLen)
|
||||
k[0] = KeyTypeSequence
|
||||
binary.BigEndian.PutUint32(k[keyPrefixLen:], db.folderIdx.ID(folder))
|
||||
binary.BigEndian.PutUint64(k[keyPrefixLen+keyFolderLen:], uint64(seq))
|
||||
return k[:reqLen]
|
||||
}
|
||||
|
||||
// sequenceKeySequence returns the sequence number from the key
|
||||
func (db *Instance) sequenceKeySequence(key []byte) int64 {
|
||||
return int64(binary.BigEndian.Uint64(key[keyPrefixLen+keyFolderLen:]))
|
||||
}
|
||||
|
||||
func (db *Instance) getIndexID(device, folder []byte) protocol.IndexID {
|
||||
key := db.indexIDKey(device, folder)
|
||||
cur, err := db.Get(key, nil)
|
||||
@@ -887,3 +1015,11 @@ func (i *smallIndex) Val(id uint32) ([]byte, bool) {
|
||||
|
||||
return []byte(val), true
|
||||
}
|
||||
|
||||
// resize returns a byte array of length reqLen, reusing k if possible
|
||||
func resize(k []byte, reqLen int) []byte {
|
||||
if cap(k) < reqLen {
|
||||
return make([]byte, reqLen)
|
||||
}
|
||||
return k[:reqLen]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user