mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
* topology: keep per-node volume state with the location it describes The read-only and oversized indexes were maps from volume id to a list of the nodes reporting that state -- the same key space the lookup index already holds, kept a second and third time. Nothing ever asked which nodes; both are only ever asked whether any node does. So the state rides on the location list as a bit per entry, and the two indexes go. Removing a location shifts the bits with it, and a node replacing another at the same address inherits its slot, since that is what happens to the location too. 800k volumes, 90% read-only: readonly index 40.0MB -> 0, lookup index 42.5MB -> 48.6MB for the bits, 33.9MB net. * topology: rebuild the location flags when stale entries are dropped Refresh rebuilds the location list, so leaving the flags alone left bits describing whoever moved into the dropped entries' place. * topology: assert the refreshed flag survived, not just that it moved Clearing the mask rather than rebuilding it would have passed: the check that the flag lands on the right location is done by clearing it, which an already empty mask satisfies.
121 lines
3.5 KiB
Go
121 lines
3.5 KiB
Go
package topology
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func flagNode(ip string) *DataNode {
|
|
dn := NewDataNode(ip)
|
|
dn.Ip, dn.Port = ip, 8080
|
|
return dn
|
|
}
|
|
|
|
func TestLocationFlagsFollowTheirNode(t *testing.T) {
|
|
a, b, c := flagNode("10.0.0.1"), flagNode("10.0.0.2"), flagNode("10.0.0.3")
|
|
list := NewVolumeLocationList()
|
|
for _, dn := range []*DataNode{a, b, c} {
|
|
list.Set(dn)
|
|
}
|
|
|
|
list.SetReadOnly(b, true)
|
|
list.SetOversized(c, true)
|
|
if !list.AnyReadOnly() || !list.AnyOversized() {
|
|
t.Fatal("flags did not register")
|
|
}
|
|
|
|
// Removing an earlier node shifts the rest down; the flags have to move with
|
|
// them or they end up describing the wrong server.
|
|
list.Remove(a)
|
|
list.SetReadOnly(b, false)
|
|
if list.AnyReadOnly() {
|
|
t.Error("clearing the flag on its node left it set, so it had shifted onto another")
|
|
}
|
|
if !list.AnyOversized() {
|
|
t.Error("removing an unrelated node dropped another node's flag")
|
|
}
|
|
list.SetOversized(c, false)
|
|
if list.AnyOversized() {
|
|
t.Error("clearing the flag on its node left it set")
|
|
}
|
|
}
|
|
|
|
func TestLocationFlagsClearedWithTheirNode(t *testing.T) {
|
|
a, b := flagNode("10.0.0.1"), flagNode("10.0.0.2")
|
|
list := NewVolumeLocationList()
|
|
list.Set(a)
|
|
list.Set(b)
|
|
list.SetReadOnly(a, true)
|
|
|
|
list.Remove(a)
|
|
if list.AnyReadOnly() {
|
|
t.Error("a departed node left its read-only marking behind")
|
|
}
|
|
}
|
|
|
|
// A node that shares an address replaces the entry, so it inherits the slot.
|
|
func TestLocationFlagsSurviveAReplacedNode(t *testing.T) {
|
|
a, replacement := flagNode("10.0.0.1"), flagNode("10.0.0.1")
|
|
list := NewVolumeLocationList()
|
|
list.Set(a)
|
|
list.SetReadOnly(a, true)
|
|
|
|
list.Set(replacement)
|
|
if !list.AnyReadOnly() {
|
|
t.Error("replacing the node at an address dropped what was known about the volume there")
|
|
}
|
|
list.SetReadOnly(replacement, false)
|
|
if list.AnyReadOnly() {
|
|
t.Error("the replacement could not clear the flag it inherited")
|
|
}
|
|
}
|
|
|
|
func TestLocationFlagsIgnoreUntrackableReplicaCounts(t *testing.T) {
|
|
list := NewVolumeLocationList()
|
|
nodes := make([]*DataNode, 0, maxTrackedLocations+2)
|
|
for i := 0; i < maxTrackedLocations+2; i++ {
|
|
dn := NewDataNode("n")
|
|
dn.Ip, dn.Port = "10.0.0.1", 9000+i
|
|
nodes = append(nodes, dn)
|
|
list.Set(dn)
|
|
}
|
|
|
|
// Past the tracked width the flag is not recorded rather than landing on
|
|
// some other node's slot.
|
|
list.SetReadOnly(nodes[maxTrackedLocations+1], true)
|
|
if list.AnyReadOnly() {
|
|
t.Error("a flag beyond the tracked width was recorded against another node")
|
|
}
|
|
list.SetReadOnly(nodes[0], true)
|
|
if !list.AnyReadOnly() {
|
|
t.Error("a flag within the tracked width was lost")
|
|
}
|
|
}
|
|
|
|
// Refresh drops stale locations, so it has to rebuild the flags with them.
|
|
func TestLocationFlagsRebuiltByRefresh(t *testing.T) {
|
|
stale, fresh, alsoFresh := flagNode("10.0.0.1"), flagNode("10.0.0.2"), flagNode("10.0.0.3")
|
|
stale.LastSeen, fresh.LastSeen, alsoFresh.LastSeen = 100, 500, 500
|
|
|
|
list := NewVolumeLocationList()
|
|
for _, dn := range []*DataNode{stale, fresh, alsoFresh} {
|
|
list.Set(dn)
|
|
}
|
|
list.SetReadOnly(alsoFresh, true)
|
|
|
|
list.Refresh(400)
|
|
|
|
if list.Length() != 2 {
|
|
t.Fatalf("expected the stale location to be dropped, got %d", list.Length())
|
|
}
|
|
// Both halves are needed: that the flag survived at all, and that it
|
|
// survived against the right location. Rebuilding the mask as empty would
|
|
// pass the second on its own.
|
|
if !list.AnyReadOnly() {
|
|
t.Fatal("the flag was dropped rather than carried across the rebuild")
|
|
}
|
|
list.SetReadOnly(alsoFresh, false)
|
|
if list.AnyReadOnly() {
|
|
t.Error("the flag did not move with its location, so it now describes another server")
|
|
}
|
|
}
|