Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dbnode] Get rid of excessive locking when adding new namespaces #3765

Merged
merged 29 commits into from
Oct 6, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d5a12ad
Set `Bootstrapping` state when waiting for fileOps to be disabled com…
soundvibe Sep 21, 2021
3e0773b
Release database lock before enqueueing bootstrap because it could ta…
soundvibe Sep 22, 2021
0c4bd0b
Rollback bootstrapping state change.
soundvibe Sep 22, 2021
faef9d7
disable/enable fileOps when adding a new namespace.
soundvibe Sep 22, 2021
fe6a112
removed comments added for testing.
soundvibe Sep 22, 2021
0ea2556
fixed linter issues.
soundvibe Sep 22, 2021
9f6fb20
fixed potential issue when competing bootstraps could be started and …
soundvibe Sep 22, 2021
f2cf79c
fixed typo in comment.
soundvibe Sep 22, 2021
c35dd26
implemented similar enqueue bootstrap logics to `assignShardSet` as w…
soundvibe Sep 23, 2021
ff93b1c
Disable both file ops first and then wait for them to stop (should re…
soundvibe Sep 23, 2021
b8d552c
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
soundvibe Sep 23, 2021
194ac64
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
soundvibe Sep 23, 2021
f3fcdb4
New namespace adds can now be enqueued and be non-blocking.
soundvibe Sep 24, 2021
d7383aa
small cleanup
soundvibe Sep 24, 2021
08f755c
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
soundvibe Sep 24, 2021
0737c60
reverse commit
soundvibe Sep 24, 2021
b2ac042
We can enqueue bootstrap with lock to avoid potential race now becaus…
soundvibe Sep 24, 2021
e537318
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
soundvibe Sep 24, 2021
db80e1e
unlock right after `namespaceDeltaWithLock()`
soundvibe Oct 4, 2021
ff41d46
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
soundvibe Oct 4, 2021
d8301c9
Simplify callback logic by having the mediator own the lifecycle of t…
robskillington Oct 5, 2021
56a5027
Added test when bootstrap is enqueued in case new namespaces are added.
soundvibe Oct 5, 2021
c5758a3
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
soundvibe Oct 5, 2021
1886a24
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
robskillington Oct 6, 2021
5b5978a
Simplify some code paths further and use lock/defer unlock in more pl…
robskillington Oct 6, 2021
e09cc8b
More test coverage.
soundvibe Oct 6, 2021
52b554e
Fixed possible race in unit test assertion.
soundvibe Oct 6, 2021
44a7226
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
soundvibe Oct 6, 2021
15fa0cd
Merge branch 'master' into linasn/bootstrap-after-fileops-wait
soundvibe Oct 6, 2021
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
2 changes: 2 additions & 0 deletions src/dbnode/storage/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,10 @@ func (m *bootstrapManager) startBootstrap(asyncResult *BootstrapAsyncResult) (Bo
m.Unlock()
// NB(xichen): disable filesystem manager before we bootstrap to minimize
// the impact of file operations on bootstrapping performance
m.instrumentation.log.Info("disable fileOps and wait")
m.mediator.DisableFileOpsAndWait()
defer m.mediator.EnableFileOps()
m.instrumentation.log.Info("fileOps disabled")

var result BootstrapResult
asyncResult.bootstrapStarted.Done()
Expand Down
71 changes: 51 additions & 20 deletions src/dbnode/storage/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,22 +292,15 @@ func (d *db) UpdateOwnedNamespaces(newNamespaces namespace.Map) error {
}
}

d.Lock()
defer d.Unlock()

d.RLock()
removes, adds, updates := d.namespaceDeltaWithLock(newNamespaces)
if err := d.logNamespaceUpdate(removes, adds, updates); err != nil {
enrichedErr := fmt.Errorf("unable to log namespace updates: %v", err)
d.log.Error(enrichedErr.Error())
d.RUnlock()
soundvibe marked this conversation as resolved.
Show resolved Hide resolved
return enrichedErr
}

// add any namespaces marked for addition
if err := d.addNamespacesWithLock(adds); err != nil {
enrichedErr := fmt.Errorf("unable to add namespaces: %v", err)
d.log.Error(enrichedErr.Error())
return err
}
d.RUnlock()

// log that updates and removals are skipped
if len(removes) > 0 || len(updates) > 0 {
Expand All @@ -317,14 +310,40 @@ func (d *db) UpdateOwnedNamespaces(newNamespaces namespace.Map) error {
"restart the process if you want changes to take effect")
}

// enqueue bootstraps if new namespaces
if len(adds) > 0 {
d.queueBootstrapWithLock()
soundvibe marked this conversation as resolved.
Show resolved Hide resolved
// NB: need to disable fileOps and wait for all the background processes to complete
// so that we could update namespaces safely. Otherwise, there is a high change in getting
soundvibe marked this conversation as resolved.
Show resolved Hide resolved
// invariant violation panic because cold/warm flush will receive new namespaces
// in the middle of their operations.
d.disableFileOpsAndWait()
d.Lock()
// add any namespaces marked for addition
if err := d.addNamespacesWithLock(adds); err != nil {
enrichedErr := fmt.Errorf("unable to add namespaces: %w", err)
d.log.Error(enrichedErr.Error())
d.Unlock()
d.enableFileOps()
return err
}
d.Unlock()
// enqueue bootstrap and enable file ops when bootstrap is completed
d.enqueueBootstrap(d.enableFileOps)
}

return nil
}

func (d *db) disableFileOpsAndWait() {
if mediator := d.mediator; mediator != nil && mediator.IsOpen() {
mediator.DisableFileOpsAndWait()
}
}

func (d *db) enableFileOps() {
if mediator := d.mediator; mediator != nil && mediator.IsOpen() {
mediator.EnableFileOps()
}
}

func (d *db) namespaceDeltaWithLock(newNamespaces namespace.Map) ([]ident.ID, []namespace.Metadata, []namespace.Metadata) {
var (
existing = d.namespaces
Expand Down Expand Up @@ -481,7 +500,6 @@ func (d *db) AssignShardSet(shardSet sharding.ShardSet) {

func (d *db) assignShardSet(shardSet sharding.ShardSet) {
d.Lock()
defer d.Unlock()

d.log.Info("assigning shards", zap.Uint32s("shards", shardSet.AllIDs()))

Expand All @@ -493,14 +511,18 @@ func (d *db) assignShardSet(shardSet sharding.ShardSet) {
ns.AssignShardSet(shardSet)
}

d.Unlock()

if receivedNewShards {
// Only trigger a bootstrap if the node received new shards otherwise
// the nodes will perform lots of small bootstraps (that accomplish nothing)
// during topology changes as other nodes mark their shards as available.
//
// These small bootstraps can significantly delay topology changes as they prevent
// the nodes from marking themselves as bootstrapped and durable, for example.
d.queueBootstrapWithLock()
d.enqueueBootstrap(func() {
d.log.Info("bootstrap completed after receiving new shards")
})
}
}

Expand Down Expand Up @@ -533,20 +555,29 @@ func (d *db) ShardSet() sharding.ShardSet {
return shardSet
}

func (d *db) queueBootstrapWithLock() {
func (d *db) enqueueBootstrap(onCompleteFn func()) {
// Only perform a bootstrap if at least one bootstrap has already occurred. This enables
// the ability to open the clustered database and assign shardsets to the non-clustered
// database when it receives an initial topology (as well as topology changes) without
// triggering a bootstrap until an external call initiates a bootstrap with an initial
// call to Bootstrap(). After that initial bootstrap, the clustered database will keep
// the non-clustered database bootstrapped by assigning it shardsets which will trigger new
// bootstraps since d.bootstraps > 0 will be true.
if d.bootstraps > 0 {
d.RLock()
shouldBootstrap := d.bootstraps > 0
d.RUnlock()
if shouldBootstrap {
soundvibe marked this conversation as resolved.
Show resolved Hide resolved
d.log.Info("enqueuing bootstrap")
bootstrapAsyncResult := d.mediator.BootstrapEnqueue()
// NB(linasn): We need to wait for the bootstrap to start and set it's state to Bootstrapping in order
// to safely run fileOps in mediator later.
bootstrapAsyncResult.WaitForStart()
// NB(linasn): We need to wait for the bootstrap to complete and call onCompleteFn.
go func() {
bootstrapAsyncResult.WaitForComplete()
onCompleteFn()
}()
return
}

onCompleteFn()
}

func (d *db) Namespace(id ident.ID) (Namespace, bool) {
Expand Down
5 changes: 0 additions & 5 deletions src/dbnode/storage/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ import (
xtime "github.com/m3db/m3/src/x/time"
)

const (
maxUint64 = ^uint64(0)
maxInt64 = int64(maxUint64 >> 1)
)

// IndexWriter accepts index inserts.
type IndexWriter interface {
// WritePending indexes the provided pending entries.
Expand Down
5 changes: 5 additions & 0 deletions src/dbnode/storage/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,11 @@ func (b *BootstrapAsyncResult) WaitForStart() {
b.bootstrapStarted.Wait()
}

// WaitForComplete waits until bootstrap has been completed.
func (b *BootstrapAsyncResult) WaitForComplete() {
b.bootstrapCompleted.Wait()
}

// databaseFlushManager manages flushing in-memory data to persistent storage.
type databaseFlushManager interface {
// Flush flushes in-memory data to persistent storage.
Expand Down