satellite/metainfo/metabase: optimize ConvertAliasesToPieces

old time/op    new time/op    delta
    7.56µs ± 5%    4.93µs ± 2%  -34.75%  (p=0.000 n=5+15)

  old alloc/op   new alloc/op   delta
    6.86kB ± 0%    3.85kB ± 0%  -43.87%  (p=0.000 n=5+18)

  old allocs/op  new allocs/op  delta
      19.0 ± 0%      17.0 ± 0%  -10.53%  (p=0.000 n=5+18)

Change-Id: Iedf24087766b3bd90934f2daa7ac186c3503a341
This commit is contained in:
Egon Elbre 2021-03-09 14:36:28 +02:00
parent 67e26aafcd
commit 5e954ad487
2 changed files with 109 additions and 15 deletions

View File

@ -163,18 +163,36 @@ func (cache *NodeAliasCache) ConvertPiecesToAliases(ctx context.Context, pieces
func (cache *NodeAliasCache) ConvertAliasesToPieces(ctx context.Context, aliasPieces AliasPieces) (_ Pieces, err error) {
defer mon.Task()(&ctx)(&err)
aliases := make([]NodeAlias, len(aliasPieces))
latest := cache.getLatest()
pieces := make(Pieces, len(aliasPieces))
var missing []NodeAlias
for i, aliasPiece := range aliasPieces {
pieces[i].Number, aliases[i] = aliasPiece.Number, aliasPiece.Alias
node, ok := latest.Node(aliasPiece.Alias)
if !ok {
missing = append(missing, aliasPiece.Alias)
continue
}
pieces[i].Number = aliasPiece.Number
pieces[i].StorageNode = node
}
nodes, err := cache.Nodes(ctx, aliases)
if len(missing) > 0 {
var err error
latest, err = cache.refresh(ctx, nil, missing)
if err != nil {
return nil, Error.Wrap(err)
return nil, Error.New("failed to refresh node alias db: %w", err)
}
for i, aliasPiece := range aliasPieces {
node, ok := latest.Node(aliasPiece.Alias)
if !ok {
return nil, Error.New("aliases missing in database: %v", missing)
}
pieces[i].Number = aliasPiece.Number
pieces[i].StorageNode = node
}
for i, n := range nodes {
pieces[i].StorageNode = n
}
return pieces, nil
@ -182,38 +200,57 @@ func (cache *NodeAliasCache) ConvertAliasesToPieces(ctx context.Context, aliasPi
// NodeAliasMap contains bidirectional mapping between node ID and a NodeAlias.
type NodeAliasMap struct {
node map[NodeAlias]storj.NodeID
node []storj.NodeID
alias map[storj.NodeID]NodeAlias
}
// NewNodeAliasMap creates a new alias map from the given entries.
func NewNodeAliasMap(entries []NodeAliasEntry) *NodeAliasMap {
m := &NodeAliasMap{
node: make(map[NodeAlias]storj.NodeID, len(entries)),
node: make([]storj.NodeID, len(entries)),
alias: make(map[storj.NodeID]NodeAlias, len(entries)),
}
for _, e := range entries {
m.node[e.Alias] = e.ID
m.setNode(e.Alias, e.ID)
m.alias[e.ID] = e.Alias
}
return m
}
// setNode sets a value in `m.node` and increases the size when necessary.
func (m *NodeAliasMap) setNode(alias NodeAlias, value storj.NodeID) {
if int(alias) >= len(m.node) {
m.node = append(m.node, make([]storj.NodeID, int(alias)-len(m.node)+1)...)
}
m.node[alias] = value
}
// Merge merges the other map into m.
func (m *NodeAliasMap) Merge(other *NodeAliasMap) {
for k, v := range other.node {
m.node[k] = v
if !v.IsZero() {
m.setNode(NodeAlias(k), v)
}
}
for k, v := range other.alias {
m.alias[k] = v
}
}
// Node returns NodeID for the given alias.
func (m *NodeAliasMap) Node(alias NodeAlias) (x storj.NodeID, ok bool) {
if int(alias) >= len(m.node) {
return storj.NodeID{}, false
}
v := m.node[alias]
return v, !v.IsZero()
}
// Nodes returns NodeID-s for the given aliases and aliases that are not in this map.
func (m *NodeAliasMap) Nodes(aliases []NodeAlias) (xs []storj.NodeID, missing []NodeAlias) {
xs = make([]storj.NodeID, 0, len(aliases))
for _, p := range aliases {
if x, ok := m.node[p]; ok {
if x, ok := m.Node(p); ok {
xs = append(xs, x)
} else {
missing = append(missing, p)
@ -243,7 +280,7 @@ func (m *NodeAliasMap) ContainsAll(nodeIDs []storj.NodeID, nodeAliases []NodeAli
}
}
for _, alias := range nodeAliases {
if _, ok := m.node[alias]; !ok {
if _, ok := m.Node(alias); !ok {
return false
}
}
@ -255,5 +292,5 @@ func (m *NodeAliasMap) Size() int {
if m == nil {
return 0
}
return len(m.node)
return len(m.alias)
}

View File

@ -6,6 +6,7 @@ package metabase_test
import (
"context"
"errors"
"runtime"
"sync"
"sync/atomic"
"testing"
@ -188,6 +189,32 @@ func TestNodeAliasMap(t *testing.T) {
require.Equal(t, []storj.NodeID{n1, n2, n3}, missing)
}
{
aggregate := metabase.NewNodeAliasMap(nil)
alpha := metabase.NewNodeAliasMap([]metabase.NodeAliasEntry{
{ID: n1, Alias: 1},
{ID: n2, Alias: 2},
})
aggregate.Merge(alpha)
beta := metabase.NewNodeAliasMap([]metabase.NodeAliasEntry{
{ID: n3, Alias: 5},
})
aggregate.Merge(beta)
aliases, missing := aggregate.Aliases([]storj.NodeID{n1, n2, n3})
require.Empty(t, missing)
require.Equal(t, []metabase.NodeAlias{1, 2, 5}, aliases)
nodes2, missing2 := aggregate.Nodes([]metabase.NodeAlias{1, 2, 5})
require.Empty(t, missing2)
require.Equal(t, []storj.NodeID{n1, n2, n3}, nodes2)
nodes3, missing3 := aggregate.Nodes([]metabase.NodeAlias{3, 4})
require.Empty(t, nodes3)
require.Equal(t, []metabase.NodeAlias{3, 4}, missing3)
}
m := metabase.NewNodeAliasMap([]metabase.NodeAliasEntry{
{n1, 1},
{n2, 2},
@ -264,6 +291,36 @@ func TestNodeAliasMap(t *testing.T) {
}
}
func BenchmarkNodeAliasCache_ConvertAliasesToPieces(b *testing.B) {
ctx := context.Background()
aliasDB := &NodeAliasDB{}
cache := metabase.NewNodeAliasCache(aliasDB)
nodeIDs := make([]storj.NodeID, 80)
for i := range nodeIDs {
nodeIDs[i] = testrand.NodeID()
}
aliases, err := cache.Aliases(ctx, nodeIDs)
if err != nil {
b.Fatal(err)
}
aliasPieces := make([]metabase.AliasPiece, len(aliases))
for i, alias := range aliases {
aliasPieces[i] = metabase.AliasPiece{Number: uint16(i), Alias: alias}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
pieces, err := cache.ConvertAliasesToPieces(ctx, aliasPieces)
if err != nil {
b.Fatal(err)
}
runtime.KeepAlive(pieces)
}
}
var _ metabase.NodeAliasDB = (*NodeAliasDB)(nil)
// NodeAliasDB is an inmemory alias database for testing.