Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,27 @@ func BenchmarkDB_WriteTxn_100_SecondaryIndex(b *testing.B) {
b.ReportMetric(float64(b.N)/b.Elapsed().Seconds(), "objects/sec")
}

func BenchmarkDB_WriteTxn_CommitOnly_100Tables(b *testing.B) {
db := New(WithMetrics(&NopMetrics{}))
for i := range 99 {
newTestObjectTable(b, db, fmt.Sprintf("other%d", i))
}
table := newTestObjectTable(b, db, "test", tagsIndex)

for b.Loop() {
db.WriteTxn(table).Commit()
}
}

func BenchmarkDB_WriteTxn_CommitOnly_1Table(b *testing.B) {
db := New(WithMetrics(&NopMetrics{}))
table := newTestObjectTable(b, db, "test", tagsIndex)

for b.Loop() {
db.WriteTxn(table).Commit()
}
}

func BenchmarkDB_NewWriteTxn(b *testing.B) {
db, table := newTestDBWithMetrics(b, &NopMetrics{}, tagsIndex)
for b.Loop() {
Expand All @@ -108,7 +129,6 @@ func BenchmarkDB_WriteTxnCommit100(b *testing.B) {
for i := range len(tables) {
tables[i], _ = NewTable(db, fmt.Sprintf("test%d", i), idIndex)
}
b.ResetTimer()

for b.Loop() {
db.WriteTxn(tables[len(tables)-1]).Commit()
Expand Down Expand Up @@ -450,7 +470,6 @@ func BenchmarkDB_FullIteration_All(b *testing.B) {
require.NoError(b, err)
}
wtxn.Commit()
b.ResetTimer()

for b.Loop() {
txn := db.ReadTxn()
Expand All @@ -476,7 +495,6 @@ func BenchmarkDB_FullIteration_Prefix(b *testing.B) {
require.NoError(b, err)
}
wtxn.Commit()
b.ResetTimer()

query := Query[*testObject]{index: idIndex.indexName()}

Expand Down Expand Up @@ -506,7 +524,6 @@ func BenchmarkDB_FullIteration_Get(b *testing.B) {
require.NoError(b, err)
}
wtxn.Commit()
b.ResetTimer()

txn := db.ReadTxn()
for b.Loop() {
Expand All @@ -531,7 +548,6 @@ func BenchmarkDB_FullIteration_Get_Secondary(b *testing.B) {
require.NoError(b, err)
}
wtxn.Commit()
b.ResetTimer()

txn := db.ReadTxn()
for b.Loop() {
Expand All @@ -557,7 +573,6 @@ func BenchmarkDB_FullIteration_ReadTxnGet(b *testing.B) {
require.NoError(b, err)
}
wtxn.Commit()
b.ResetTimer()

for b.Loop() {
for _, q := range queries {
Expand Down
12 changes: 6 additions & 6 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ type dbState struct {
writeTxnPool sync.Pool
}

type dbRoot = []tableEntry
type dbRoot = []*tableEntry

type Option func(*opts)

Expand Down Expand Up @@ -145,7 +145,7 @@ func (db *DB) updateWriteTxnPoolLocked(numTables int) {
func() any {
return &writeTxnState{
db: db,
tableEntries: make([]tableEntry, 0, numTables),
tableEntries: make([]*tableEntry, 0, numTables),
smus: make(internal.SortableMutexes, 0, defaultNumTables),
tableNames: make([]string, 0, defaultNumTables),
}
Expand Down Expand Up @@ -212,10 +212,10 @@ func (db *DB) WriteTxn(tables ...TableMeta) WriteTxn {
txn.tableNames = reuseSlice(txn.tableNames, len(tables))
for i, table := range tables {
pos := table.tablePos()
tableEntry := &txn.tableEntries[pos]
tableEntry.indexes = slices.Clone(tableEntry.indexes)
tableEntry.locked = true

tableEntryCopy := *txn.tableEntries[pos]
tableEntryCopy.indexes = slices.Clone(tableEntryCopy.indexes)
tableEntryCopy.locked = true
txn.tableEntries[pos] = &tableEntryCopy
name := table.Name()
txn.tableNames[i] = name

Expand Down
5 changes: 3 additions & 2 deletions deletetracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ func (dt *deleteTracker[Obj]) close() {
txn := wtxn.unwrap()
dt.db = nil
db := txn.db
table := &txn.tableEntries[dt.table.tablePos()]
table := txn.tableEntries[dt.table.tablePos()]
if !table.locked {
panic("BUG: Table not locked")
}
_, _, table.deleteTrackers = table.deleteTrackers.Delete([]byte(dt.trackerName))
_, _, updated := table.deleteTrackers.Delete([]byte(dt.trackerName))
table.deleteTrackers = &updated
wtxn.Commit()

db.metrics.DeleteTrackerCount(dt.table.Name(), table.deleteTrackers.Len())
Expand Down
2 changes: 1 addition & 1 deletion iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ type changeIterator[Obj any] struct {
}

func (it *changeIterator[Obj]) refresh(txn ReadTxn) {
tableEntry := &txn.root()[it.table.tablePos()]
tableEntry := txn.root()[it.table.tablePos()]
if it.iter != nil && tableEntry.locked {
var obj Obj
panic(fmt.Sprintf("Table[%T].Changes().Next() called with the target table locked. This is not supported.", obj))
Expand Down
36 changes: 22 additions & 14 deletions part/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import (
// keys that are not []byte.
type Map[K, V any] struct {
bytesFromKeyFunc func(K) []byte
tree *Tree[mapKVPair[K, V]]
singleton *mapKVPair[K, V]
tree Tree[mapKVPair[K, V]]
hasTree bool
}

type mapKVPair[K, V any] struct {
Expand Down Expand Up @@ -59,8 +60,9 @@ func FromMap[K comparable, V any](m Map[K, V], hm map[K]V) Map[K, V] {
// it is. The whole nil tree thing is to make sure that creating
// an empty map does not allocate anything.
func (m *Map[K, V]) ensureTree() {
if m.tree == nil {
if !m.hasTree {
m.tree = New[mapKVPair[K, V]](RootOnlyWatch)
m.hasTree = true
}
}

Expand All @@ -70,7 +72,7 @@ func (m Map[K, V]) Get(key K) (value V, found bool) {
return m.singleton.Value, true
}

if m.tree == nil {
if !m.hasTree {
return
}
kv, _, found := m.tree.Get(m.keyToBytes(key))
Expand All @@ -81,7 +83,7 @@ func (m Map[K, V]) Get(key K) (value V, found bool) {
// Original map is unchanged.
func (m Map[K, V]) Set(key K, value V) Map[K, V] {
keyBytes := m.keyToBytes(key)
if m.tree == nil && m.singleton == nil || m.singleton != nil && bytes.Equal(keyBytes, m.keyToBytes(m.singleton.Key)) {
if !m.hasTree && m.singleton == nil || m.singleton != nil && bytes.Equal(keyBytes, m.keyToBytes(m.singleton.Key)) {
m.singleton = &mapKVPair[K, V]{key, value}
return m
}
Expand Down Expand Up @@ -113,16 +115,18 @@ func (m Map[K, V]) Delete(key K) Map[K, V] {
}
return m
}
if m.tree != nil {
if m.hasTree {
txn := m.tree.Txn()
txn.Delete(m.keyToBytes(key))
switch txn.Len() {
case 0:
m.tree = nil
m.tree = Tree[mapKVPair[K, V]]{}
m.hasTree = false
case 1:
for _, v := range txn.Iterator().All {
m.singleton = &v
m.tree = nil
m.tree = Tree[mapKVPair[K, V]]{}
m.hasTree = false
}
default:
m.tree = txn.Commit()
Expand All @@ -149,7 +153,7 @@ func (m Map[K, V]) LowerBound(from K) iter.Seq2[K, V] {
return m.singletonIter()
}
}
if m.tree == nil {
if !m.hasTree {
return toSeq2[K, V](Iterator[mapKVPair[K, V]]{})
}
return toSeq2(m.tree.LowerBound(m.keyToBytes(from)))
Expand All @@ -171,7 +175,7 @@ func (m Map[K, V]) Prefix(prefix K) iter.Seq2[K, V] {
return m.singletonIter()
}
}
if m.tree == nil {
if !m.hasTree {
return toSeq2[K, V](Iterator[mapKVPair[K, V]]{})
}
iter, _ := m.tree.Prefix(m.keyToBytes(prefix))
Expand All @@ -185,7 +189,7 @@ func (m Map[K, V]) All() iter.Seq2[K, V] {
if m.singleton != nil {
return m.singletonIter()
}
if m.tree == nil {
if !m.hasTree {
return toSeq2[K, V](Iterator[mapKVPair[K, V]]{})
}
return toSeq2(m.tree.Iterator())
Expand All @@ -198,7 +202,7 @@ func (m Map[K, V]) EqualKeys(other Map[K, V]) bool {
return false
case m.singleton != nil && other.singleton != nil:
return bytes.Equal(m.keyToBytes(m.singleton.Key), other.keyToBytes(other.singleton.Key))
case m.tree == nil && other.tree == nil:
case !m.hasTree && !other.hasTree:
return true
default:
iter1 := m.tree.Iterator()
Expand Down Expand Up @@ -228,7 +232,7 @@ func (m Map[K, V]) SlowEqual(other Map[K, V]) bool {
case m.singleton != nil && other.singleton != nil:
return bytes.Equal(m.keyToBytes(m.singleton.Key), other.keyToBytes(other.singleton.Key)) &&
reflect.DeepEqual(m.singleton.Value, other.singleton.Value)
case m.tree == nil && other.tree == nil:
case !m.hasTree && !other.hasTree:
return true
default:
iter1 := m.tree.Iterator()
Expand All @@ -253,14 +257,14 @@ func (m Map[K, V]) Len() int {
if m.singleton != nil {
return 1
}
if m.tree == nil {
if !m.hasTree {
return 0
}
return m.tree.size
}

func (m Map[K, V]) MarshalJSON() ([]byte, error) {
if m.tree == nil && m.singleton == nil {
if !m.hasTree && m.singleton == nil {
return []byte("[]"), nil
}

Expand Down Expand Up @@ -416,6 +420,10 @@ func (txn MapTxn[K, V]) Commit() (m Map[K, V]) {
m.singleton = &kv
default:
m.tree = txn.txn.Commit()
m.hasTree = true
}
if m.singleton != nil {
m.hasTree = false
}
return
}
Expand Down
16 changes: 8 additions & 8 deletions part/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,18 @@ func TestSingletonMap(t *testing.T) {
switch m.Len() {
case 0:
require.Nil(t, m.singleton)
require.Nil(t, m.tree)
require.False(t, m.hasTree)
case 1:
require.NotNil(t, m.singleton)
require.Nil(t, m.tree)
require.False(t, m.hasTree)
default:
require.Nil(t, m.singleton)
require.NotNil(t, m.tree)
require.True(t, m.hasTree)
}
if m.singleton != nil {
require.Nil(t, m.tree, "Tree should not be set if singleton set")
require.False(t, m.hasTree, "Tree should not be set if singleton set")
}
if m.tree != nil {
if m.hasTree {
require.Nil(t, m.singleton, "Singleton should not be set if tree set")
}
}
Expand Down Expand Up @@ -290,7 +290,7 @@ func TestMapTxn(t *testing.T) {

tree := txn.Commit()
assert.Equal(t, 0, tree.Len())
assert.Nil(t, tree.tree)
assert.False(t, tree.hasTree)
assert.Nil(t, tree.singleton)

// Add foo=>42
Expand All @@ -306,7 +306,7 @@ func TestMapTxn(t *testing.T) {

tree = txn.Commit()
assert.Equal(t, 1, tree.Len())
assert.Nil(t, tree.tree)
assert.False(t, tree.hasTree)
assert.NotNil(t, tree.singleton)
v, found = tree.Get("foo")
assert.True(t, found)
Expand Down Expand Up @@ -350,7 +350,7 @@ func TestMapTxn(t *testing.T) {

tree = txn.Commit()
assert.Equal(t, 2, tree.Len())
assert.NotNil(t, tree.tree)
assert.True(t, tree.hasTree)
assert.Nil(t, tree.singleton)
mp = maps.Collect(tree.All())
assert.Len(t, mp, 2)
Expand Down
12 changes: 6 additions & 6 deletions part/quick_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var quickConfig = &quick.Config{
func TestQuick_InsertGetPrefix(t *testing.T) {
t.Parallel()

var tree *Tree[string]
var tree Tree[string]
insert := func(key, value string) any {
watchChannels := []<-chan struct{}{}
// Add all possible watch channels for prefixes of the key
Expand Down Expand Up @@ -260,18 +260,18 @@ func TestQuick_Map(t *testing.T) {
switch m.Len() {
case 0:
require.Nil(t, m.singleton)
require.Nil(t, m.tree)
require.False(t, m.hasTree)
case 1:
require.NotNil(t, m.singleton)
require.Nil(t, m.tree)
require.False(t, m.hasTree)
default:
require.Nil(t, m.singleton)
require.NotNil(t, m.tree)
require.True(t, m.hasTree)
}
if m.singleton != nil {
require.Nil(t, m.tree, "Tree should not be set if singleton set")
require.False(t, m.hasTree, "Tree should not be set if singleton set")
}
if m.tree != nil {
if m.hasTree {
require.Nil(t, m.singleton, "Singleton should not be set if tree set")
}
}
Expand Down
Loading