Documentation
¶
Overview ¶
Package ring provides a way to distribute replicas of partitioned items to nodes.
An example would be a distributed storage system, storing duplicate copies of each file on different drives, servers, or even data centers based on the assignments given by the Ring.
See https://github.com/gholt/ring/blob/master/BASIC_HASH_RING.md for a introduction to consistent hashing and hashing rings.
There is a lower-level package github.com/gholt/ring/lowring that underpins this package. It is provided in case you don't need the extra layer this package provides or you would like to create your own extra layer.
Terms Used With This Package ¶
Node: A single unit within a distributed system. For example, a server or a single drive within a server.
Partition: A numeric value from a range of values. Replicas of these partitions are assigned to nodes to indicate each node's responsibilities, such as which data to store or which requests to process. Mapping these data items or requests to partitions is usually done by hashing the name or some other identifier to obtain a number and then using the modulus operator with the overall partition count.
Replica: A copy of a partition. Object storage systems often use 3 replicas, for example.
Ring: Stores the assignments of replicas of partitions to nodes.
Builder: A program to build and maintain a ring.
Capacity: The relative size of a node as compared to other nodes. For example, the amount of disk space available on the node.
Desire: The number of additional, or fewer, partitions a node would like to have assigned in order to reach a balance with the rest of the nodes in a ring.
Tier: Represents the relationship of nodes to one another. For example, a geographic tier might have two values, east and west, and each node would be associated with one of those regions. There can be multiple levels of tiers, such as disk, server, zone, datacenter, region, etc. See the Example (Tiers) for a more in-depth code discussion.
Last Moved: The record of when a given replica of a partition was last reassigned to a different node. The builder uses this information restrict future movements of that replica and of the other replicas for that partition. For example, it might only move 2 of 5 replicas of a partition within an hour, unless absolutely necessary, such as a failed node.
What About Other Distributed Ring Algorithms ¶
Each has their strengths and weaknesses. This package uses more memory than many other ring implementations and it is slower to create and modify the ring. But, it is usually much faster to use once loaded and provides precise node placements. https://github.com/gholt/ring/blob/master/PARTITION_RING_VS_HASH_RING.md has more discussion on the trade offs.
Other interesting ideas in this space:
Amazon's original 2007 Dynamo paper - http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf
Jump consistent hashing - http://arxiv.org/abs/1406.2294 - dgryski implementation https://github.com/dgryski/go-jump - also dgryski shared key-value store https://github.com/dgryski/go-shardedkv
Multi-probe consistent hashing http://arxiv.org/pdf/1505.00062.pdf - dgryski implementation https://github.com/dgryski/go-mpchash
GreenCHT replication scheme http://storageconference.us/2015/Papers/16.Zhao.pdf
Examples For This Package ¶
Below are examples for overall usage of this package, and more in-depth discussions as well. If you're directly reading the code, all the examples are in files named *example_test.go
Example (FromBasicHashRingDocument) ¶
package main import ( "fmt" "hash/fnv" "math/rand" "time" "github.com/gholt/ring/lowring" ) func main() { hash := func(x int) uint64 { hasher := fnv.New64a() hasher.Write([]byte(fmt.Sprintf("%d", x))) return hasher.Sum64() } randIntn := rand.New(rand.NewSource(0)).Intn const ITEMS = 1000000 const NODES = 100 r := lowring.New(1) for n := 0; n < NODES; n++ { r.AddNode(1, 0) } r.Rebalance(randIntn) // Copy the essential ring data ring1 := make([][]lowring.Node, len(r.ReplicaToPartitionToNode)) for replica, partitionToNode := range r.ReplicaToPartitionToNode { ring1[replica] = make([]lowring.Node, len(partitionToNode)) copy(ring1[replica], partitionToNode) } partitionCount1 := uint64(len(ring1[0])) countPerNode := make([]int, NODES) for i := 0; i < ITEMS; i++ { n := ring1[0][hash(i)%partitionCount1] countPerNode[n]++ } min := ITEMS max := 0 for n := 0; n < NODES; n++ { if countPerNode[n] < min { min = countPerNode[n] } if countPerNode[n] > max { max = countPerNode[n] } } t := ITEMS / NODES fmt.Printf("%d to %d assignments per node, target was %d.\n", min, max, t) fmt.Printf("That's %.02f%% under and %.02f%% over.\n", float64(t-min)/float64(t)*100, float64(max-t)/float64(t)*100) r.AddNode(1, 0) // Reset wait time restrictions r.Rebalanced = r.Rebalanced.Add(-(time.Duration(r.ReassignmentWait) * time.Minute)) r.Rebalance(randIntn) // Copy the essential ring data ring2 := make([][]lowring.Node, len(r.ReplicaToPartitionToNode)) for replica, partitionToNode := range r.ReplicaToPartitionToNode { ring2[replica] = make([]lowring.Node, len(partitionToNode)) copy(ring2[replica], partitionToNode) } partitionCount2 := uint64(len(ring2[0])) moved := 0 for i := 0; i < ITEMS; i++ { h := hash(i) n1 := ring1[0][h%partitionCount1] n2 := ring2[0][h%partitionCount2] if n1 != n2 { moved++ } } fmt.Printf("%d items moved, %.02f%%.\n", moved, float64(moved)/float64(ITEMS)*100) }
Output: 9554 to 10289 assignments per node, target was 10000. That's 4.46% under and 2.89% over. 9815 items moved, 0.98%.
Example (GroupTiers) ¶
package main import ( "fmt" "github.com/gholt/ring" ) func main() { fmt.Println("Group tiers can be confusing, so let's work with a detailed example.") fmt.Println("We are going to have two servers, each with two disk drives.") fmt.Println("The disk drives are going to be represented by the nodes themselves.") fmt.Println("The servers will be represented by groups.") fmt.Println("By defining groups, we can tell the Builder to build rings with replicas assigned as far apart as possible, while keeping the whole Ring in balance.") fmt.Println("So let's define and build our ring; we'll use two replicas to start with...") builder := ring.NewBuilder(2) builder.SetMaxPartitionCount(2) // Just to keep the output simpler for _, server := range []string{"ServerA", "ServerB"} { group := builder.AddGroup(server, nil) for _, disk := range []string{"1stDisk", "2ndDisk"} { builder.AddNode(disk, 1, group) } } builder.Rebalance() rring := builder.Ring() printRing := func(rring *ring.Ring) { fmt.Println("Here are the ring assignments: partitions horizontally, replicas vertically:") fmt.Print("` ") for partition := 0; partition < rring.PartitionCount(); partition++ { fmt.Print(fmt.Sprintf(" -------P%d------", partition)) } fmt.Println() for replica := 0; replica < rring.ReplicaCount(); replica++ { fmt.Print(fmt.Sprintf("R%d", replica)) for partition := 0; partition < rring.PartitionCount(); partition++ { node := rring.KeyNodes(partition)[replica] fmt.Print(" " + node.Group().Info() + ":" + node.Info()) } fmt.Println() } } printRing(rring) // ` -------P0------ -------P1------ // R0 ServerA:1stDisk ServerB:2ndDisk // R1 ServerB:1stDisk ServerA:2ndDisk fmt.Println("Note that it assigned each replica of a partition to a distinct server.") fmt.Println("The node info (disk names) happened to be the same but it doesn't matter since they are on different servers.") fmt.Println() fmt.Println("Let's up the replica count to 3, where we know it will have to assign multiple replicas to a single server...") builder = ring.NewBuilder(3) builder.SetMaxPartitionCount(4) // Just to keep the output simpler for _, server := range []string{"ServerA", "ServerB"} { group := builder.AddGroup(server, nil) for _, disk := range []string{"1stDisk", "2ndDisk"} { builder.AddNode(disk, 1, group) } } builder.Rebalance() rring = builder.Ring() printRing(rring) // ` -------P0------ -------P1------ -------P2------ -------P3------ // R0 ServerA:1stDisk ServerB:2ndDisk ServerA:2ndDisk ServerB:1stDisk // R1 ServerB:1stDisk ServerA:1stDisk ServerB:2ndDisk ServerA:2ndDisk // R2 ServerA:2ndDisk ServerB:1stDisk ServerA:1stDisk ServerB:2ndDisk fmt.Println("So now it ended up using servers twice within the same partition, but note that it made sure to pick distinct drives for each replica at least.") fmt.Println() fmt.Println("Let's get more complicated and define another tier of groups; we'll call it the region tier.") fmt.Println("To do this, we simply create the new region groups and set them as the parents of the server groups.") fmt.Println("We'll assign our first two servers to the East region, and add two more servers in the Cent region, and even two more servers in the West region.") builder = ring.NewBuilder(3) builder.SetMaxPartitionCount(4) // Just to keep the output simpler for _, region := range []string{"East", "Cent", "West"} { regionGroup := builder.AddGroup(region, nil) for _, server := range []string{"ServerA", "ServerB"} { serverGroup := builder.AddGroup(server, regionGroup) for _, disk := range []string{"1stDisk", "2ndDisk"} { builder.AddNode(disk, 1, serverGroup) } } } builder.Rebalance() rring = builder.Ring() fmt.Println("Here are the ring assignments: partitions horizontally, replicas vertically:") // ` ---------P0--------- ---------P1--------- ---------P2--------- ---------P3--------- // R0 East:ServerA:1stDisk Cent:ServerB:2ndDisk West:ServerB:2ndDisk East:ServerB:1stDisk // R1 Cent:ServerA:1stDisk East:ServerA:2ndDisk Cent:ServerA:2ndDisk West:ServerB:1stDisk // R2 West:ServerA:1stDisk West:ServerA:2ndDisk East:ServerB:2ndDisk Cent:ServerB:1stDisk fmt.Print("` ") for partition := 0; partition < rring.PartitionCount(); partition++ { fmt.Print(fmt.Sprintf(" ---------P%d---------", partition)) } fmt.Println() for replica := 0; replica < rring.ReplicaCount(); replica++ { fmt.Print(fmt.Sprintf("R%d", replica)) for partition := 0; partition < rring.PartitionCount(); partition++ { node := rring.KeyNodes(partition)[replica] fmt.Print(" " + node.Group().Parent().Info() + ":" + node.Group().Info() + ":" + node.Info()) } fmt.Println() } fmt.Println("So now you can see it assigned replicas in distinct regions before worrying about the lower tiers.") }
Output: Group tiers can be confusing, so let's work with a detailed example. We are going to have two servers, each with two disk drives. The disk drives are going to be represented by the nodes themselves. The servers will be represented by groups. By defining groups, we can tell the Builder to build rings with replicas assigned as far apart as possible, while keeping the whole Ring in balance. So let's define and build our ring; we'll use two replicas to start with... Here are the ring assignments: partitions horizontally, replicas vertically: ` -------P0------ -------P1------ R0 ServerA:1stDisk ServerB:2ndDisk R1 ServerB:1stDisk ServerA:2ndDisk Note that it assigned each replica of a partition to a distinct server. The node info (disk names) happened to be the same but it doesn't matter since they are on different servers. Let's up the replica count to 3, where we know it will have to assign multiple replicas to a single server... Here are the ring assignments: partitions horizontally, replicas vertically: ` -------P0------ -------P1------ -------P2------ -------P3------ R0 ServerA:1stDisk ServerB:2ndDisk ServerA:2ndDisk ServerB:1stDisk R1 ServerB:1stDisk ServerA:1stDisk ServerB:2ndDisk ServerA:2ndDisk R2 ServerA:2ndDisk ServerB:1stDisk ServerA:1stDisk ServerB:2ndDisk So now it ended up using servers twice within the same partition, but note that it made sure to pick distinct drives for each replica at least. Let's get more complicated and define another tier of groups; we'll call it the region tier. To do this, we simply create the new region groups and set them as the parents of the server groups. We'll assign our first two servers to the East region, and add two more servers in the Cent region, and even two more servers in the West region. Here are the ring assignments: partitions horizontally, replicas vertically: ` ---------P0--------- ---------P1--------- ---------P2--------- ---------P3--------- R0 East:ServerA:1stDisk Cent:ServerB:2ndDisk West:ServerB:2ndDisk East:ServerB:1stDisk R1 Cent:ServerA:1stDisk East:ServerA:2ndDisk Cent:ServerA:2ndDisk West:ServerB:1stDisk R2 West:ServerA:1stDisk West:ServerA:2ndDisk East:ServerB:2ndDisk Cent:ServerB:1stDisk So now you can see it assigned replicas in distinct regions before worrying about the lower tiers.
Example (Overview) ¶
package main import ( "fmt" "github.com/gholt/ring" ) func main() { // We'll create a new builder for a ring with three replicas: builder := ring.NewBuilder(3) // And we'll add four nodes we'll label ABCD: for n := 'A'; n <= 'D'; n++ { builder.AddNode(string(n), 1, nil) } // Generate the ring: builder.Rebalance() rring := builder.Ring() // Print out the ring assignments: partitions horizontally, replicas vertically: // ` P0 P1 P2 P3 // R0 A B C D // R1 B A D C // R2 D C A B fmt.Print("` ") for partition := 0; partition < rring.PartitionCount(); partition++ { fmt.Print(fmt.Sprintf(" P%d", partition)) } fmt.Println() for replica := 0; replica < rring.ReplicaCount(); replica++ { fmt.Print(fmt.Sprintf("R%d", replica)) for partition := 0; partition < rring.PartitionCount(); partition++ { fmt.Print(" " + rring.KeyNodes(partition)[replica].Info()) } fmt.Println() } }
Output: ` P0 P1 P2 P3 R0 A B C D R1 B A D C R2 D C A B
Index ¶
- type Builder
- func (b *Builder) AddGroup(info string, parent *BuilderGroup) *BuilderGroup
- func (b *Builder) AddNode(info string, capacity int, group *BuilderGroup) *BuilderNode
- func (b *Builder) Assign(replica, partition int, node *BuilderNode)
- func (b *Builder) AssignmentCount() int
- func (b *Builder) Groups() []*BuilderGroup
- func (b *Builder) IsMoving(replica, partition int) bool
- func (b *Builder) KeyNodes(key int) []*BuilderNode
- func (b *Builder) Marshal(w io.Writer) error
- func (b *Builder) MaxPartitionCount() int
- func (b *Builder) MaxReplicaReassignableCount() int
- func (b *Builder) MovingAssignmentCount() int
- func (b *Builder) Nodes() []*BuilderNode
- func (b *Builder) PartitionCount() int
- func (b *Builder) PretendElapsed(d time.Duration)
- func (b *Builder) ReassignmentWait() time.Duration
- func (b *Builder) Rebalance()
- func (b *Builder) Rebalanced() time.Time
- func (b *Builder) ReplicaCount() int
- func (b *Builder) ReplicaPartitionNode(replica, partition int) *BuilderNode
- func (b *Builder) Ring() *Ring
- func (b *Builder) SetMaxPartitionCount(v int)
- func (b *Builder) SetMaxReplicaReassignableCount(v int)
- func (b *Builder) SetReassignmentWait(v time.Duration)
- func (b *Builder) SetReplicaCount(v int)
- type BuilderGroup
- func (g *BuilderGroup) AddGroup(info string) *BuilderGroup
- func (g *BuilderGroup) AddNode(info string, capacity int) *BuilderNode
- func (g *BuilderGroup) Groups() []*BuilderGroup
- func (g *BuilderGroup) Info() string
- func (g *BuilderGroup) Nodes() []*BuilderNode
- func (g *BuilderGroup) Parent() *BuilderGroup
- func (g *BuilderGroup) SetInfo(v string)
- func (g *BuilderGroup) SetParent(v *BuilderGroup)
- type BuilderNode
- func (n *BuilderNode) Assign(replica, partition int)
- func (n *BuilderNode) Capacity() int
- func (n *BuilderNode) Group() *BuilderGroup
- func (n *BuilderNode) Info() string
- func (n *BuilderNode) Partitions() []int
- func (n *BuilderNode) ReplicaPartitions(replica int) []int
- func (n *BuilderNode) Responsible(key int) int
- func (n *BuilderNode) ResponsibleForReplicaPartition(replica, partition int) bool
- func (n *BuilderNode) SetCapacity(v int)
- func (n *BuilderNode) SetGroup(group *BuilderGroup)
- func (n *BuilderNode) SetInfo(v string)
- type Group
- type Node
- type Ring
- func (r *Ring) AssignmentCount() int
- func (r *Ring) Groups() []*Group
- func (r *Ring) KeyNodes(key int) []*Node
- func (r *Ring) Marshal(w io.Writer) error
- func (r *Ring) Nodes() []*Node
- func (r *Ring) PartitionCount() int
- func (r *Ring) Rebalanced() time.Time
- func (r *Ring) ReplicaCount() int
- func (r *Ring) ResponsibleForReplicaPartition(replica, partition int) *Node
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder will create and maintain rings.
func NewBuilder ¶
NewBuilder creates a new builder with the initial replica count given.
func UnmarshalBuilder ¶
UnmarshalBuilder returns a builder based on the JSON+binary encoded information read from the io.Reader, presumably previously written by the builder's Marshal method.
Example ¶
package main import ( "bytes" "fmt" "github.com/gholt/ring" ) func main() { // Build something to marshal... b1 := ring.NewBuilder(2) b1.AddNode("Node One", 1, nil) b1.AddNode("Node Two", 1, nil) b1.Rebalance() // And marshal the builder... var buf bytes.Buffer if err := b1.Marshal(&buf); err != nil { panic(err) } // So we can show how to unmarshal it... b2, err := ring.UnmarshalBuilder(&buf) if err != nil { panic(err) } // And just do some checks to show they're the same... if !b1.Rebalanced().Equal(b2.Rebalanced()) { panic("") } if b1.ReplicaCount() != b2.ReplicaCount() { panic(fmt.Sprintln(b1.ReplicaCount(), b2.ReplicaCount())) } if b1.PartitionCount() != b2.PartitionCount() { panic("") } if b1.ReassignmentWait() != b2.ReassignmentWait() { panic("") } if b1.MaxReplicaReassignableCount() != b2.MaxReplicaReassignableCount() { panic("") } if b1.MaxPartitionCount() != b2.MaxPartitionCount() { panic("") } ns1 := b1.Nodes() ns2 := b2.Nodes() if len(ns1) != len(ns2) { panic("") } for i := 0; i < len(ns1); i++ { if ns1[i].Capacity() != ns2[i].Capacity() { panic("") } if ns1[i].Info() != ns2[i].Info() { panic("") } } // And compare their rings for equality... r1 := b1.Ring() r2 := b2.Ring() if !r1.Rebalanced().Equal(r2.Rebalanced()) { panic("") } if r1.ReplicaCount() != r2.ReplicaCount() { panic("") } if r1.PartitionCount() != r2.PartitionCount() { panic("") } compareNodeSlices := func(ns1, ns2 []*ring.Node) { if len(ns1) != len(ns2) { panic("") } for i := 0; i < len(ns1); i++ { if ns1[i].Capacity() != ns2[i].Capacity() { panic("") } if ns1[i].Info() != ns2[i].Info() { panic("") } } } compareNodeSlices(r1.Nodes(), r2.Nodes()) for partition := 0; partition < r1.PartitionCount(); partition++ { compareNodeSlices(r1.KeyNodes(partition), r2.KeyNodes(partition)) } fmt.Println("They look the same!") }
Output: They look the same!
func (*Builder) AddGroup ¶
func (b *Builder) AddGroup(info string, parent *BuilderGroup) *BuilderGroup
AddGroup adds another group to the builder. Info is a user-defined string and is not used directly by the builder. The parent group offers a way to tier groups; the builder will do its best to keep similar assignments in dissimilar groups at each tier level. The parent may be nil. Cycles are not allowed, where the parent of a group would end up being a child of the same group.
func (*Builder) AddNode ¶
func (b *Builder) AddNode(info string, capacity int, group *BuilderGroup) *BuilderNode
AddNode adds another node to the builder. Info is a user-defined string and is not used directly by the builder. Capacity specifies, relative to other nodes, how many assignments the node should have. The group indicates which group the node is in; the builder will do its best to keep similar assignments in dissimilar groups. The group may be nil.
func (*Builder) Assign ¶
func (b *Builder) Assign(replica, partition int, node *BuilderNode)
Assign will override the current builder's assignment and set a specific replica of a partition to a specific node. This is mostly just useful for testing, as future calls to Rebalance may move this assignment.
func (*Builder) AssignmentCount ¶
AssignmentCount returns the number of assignments this builder will make; it is the replica count * the partition count.
func (*Builder) Groups ¶
func (b *Builder) Groups() []*BuilderGroup
Groups returns a slice of all the groups in use by the builder.
func (*Builder) IsMoving ¶
IsMoving returns true if the specific replica of the partition is currently in "reassignment wait", where a recent rebalance had reassigned the replica to a different node and so is giving time for the data to be moved.
func (*Builder) KeyNodes ¶
func (b *Builder) KeyNodes(key int) []*BuilderNode
KeyNodes returns the nodes responsible for the key given. There will be on node for each replica; in other words: len(b.KeyNodes(k)) == b.ReplicaCount().
func (*Builder) Marshal ¶
Marshal will write a JSON+binary encoded version of its contents to the io.Writer. You can use UnmarshalBuilder to read it back later.
Example ¶
package main import ( "bytes" "fmt" "github.com/gholt/ring" ) func main() { // Build something to marshal... b := ring.NewBuilder(2) b.AddNode("Node One", 1, nil) b.AddNode("Node Two", 1, nil) b.Rebalance() // And marshal it... var buf bytes.Buffer if err := b.Marshal(&buf); err != nil { panic(err) } fmt.Println(len(buf.Bytes()), "bytes written") fmt.Println(string(buf.Bytes()[:241]), "...") // Note that even though the beginning is just JSON, there is trailing // binary for the larger data sets that JSON just isn't suited for. }
Output: 333 bytes written {"MarshalVersion":0,"NodeType":16,"ReplicaCount":2,"PartitionCount":2,"Nodes":[{"Info":"Node One","Capacity":1,"Group":0},{"Info":"Node Two","Capacity":1,"Group":0}],"Groups":[{"Info":"","Parent":0}],"MaxPartitionCount":8388608,"Rebalanced": ...
func (*Builder) MaxPartitionCount ¶
MaxPartitionCount returns the maximum partition count the builder will auto-grow to.
func (*Builder) MaxReplicaReassignableCount ¶
MaxReplicaReassignableCount returns the maximum number of replicas of a partition the builder will set "in motion" during the reassignment wait period.
func (*Builder) MovingAssignmentCount ¶
MovingAssignmentCount returns the number of assignments that are currently "in motion". If you were to call IsMoving for every replica of every partition and count the true responses, that would equal the MovingAssignmentCount.
func (*Builder) Nodes ¶
func (b *Builder) Nodes() []*BuilderNode
Nodes returns a slice of all the nodes in the builder.
func (*Builder) PartitionCount ¶
PartitionCount returns the current partition count for the builder.
func (*Builder) PretendElapsed ¶
PretendElapsed is mostly used for testing and will make the builder pretend the time duration has elapsed, usually freeing up time based reassignment restrictions.
func (*Builder) ReassignmentWait ¶
ReassignmentWait is the time duration the builder will wait after making an assignment before considering reassigning that same data.
func (*Builder) Rebalance ¶
func (b *Builder) Rebalance()
Rebalance analyzes all information and makes assignments to nodes as best as it can.
func (*Builder) Rebalanced ¶
Rebalanced returns the time the builder last had Rebalance called.
func (*Builder) ReplicaCount ¶
ReplicaCount returns the current replica count of the builder.
func (*Builder) ReplicaPartitionNode ¶
func (b *Builder) ReplicaPartitionNode(replica, partition int) *BuilderNode
ReplicaPartitionNode returns the node responsible for a specific replica of a partition.
func (*Builder) Ring ¶
Ring returns an immutable copy of the assignment information contained in the builder.
func (*Builder) SetMaxPartitionCount ¶
SetMaxPartitionCount sets the maximum partition count the builder will auto-grow to.
func (*Builder) SetMaxReplicaReassignableCount ¶
SetMaxReplicaReassignableCount sets the maximum number of replicas of a partition the builder will set "in motion" during the reassignment wait period.
For a full replica use case, you probably want to set this to no more than one less of the majority of replicas so that a majority are always in place at any given time.
For an erasure coding use case, you probably want to set this to no more than the number of parity shards so that there are always enough shards in place at any given time.
func (*Builder) SetReassignmentWait ¶
SetReassignmentWait sets the time duration the builder will wait after making an assignment before considering reassigning that same data.
func (*Builder) SetReplicaCount ¶
SetReplicaCount changes the replica count of the builder. Lowering the replica count will simply discard the higher replicas. Raising the replica count will create new higher replicas that will be completely unassigned and will require a call to Rebalance to become assigned.
type BuilderGroup ¶
type BuilderGroup struct {
// contains filtered or unexported fields
}
BuilderGroup is a group within a builder; a group is a collection of nodes, and perhaps other groups.
func (*BuilderGroup) AddGroup ¶
func (g *BuilderGroup) AddGroup(info string) *BuilderGroup
AddGroup adds a new group to the associated builder with this group set as the new group's parent. Info is a user-defined string and is not used directly by the builder.
func (*BuilderGroup) AddNode ¶
func (g *BuilderGroup) AddNode(info string, capacity int) *BuilderNode
AddNode will create a new node in the associated builder with this group set as the node's parent. Info is a user-defined string and is not used directly by the builder. Capacity specifies, relative to other nodes, how many assignments the node should have.
func (*BuilderGroup) Groups ¶
func (g *BuilderGroup) Groups() []*BuilderGroup
Groups returns the slice of groups that are the direct children of this group.
func (*BuilderGroup) Info ¶
func (g *BuilderGroup) Info() string
Info returns the associated info string for the group; this is user-defined and not used directly by the builder.
func (*BuilderGroup) Nodes ¶
func (g *BuilderGroup) Nodes() []*BuilderNode
Nodes returns the slice of nodes within this group and all its child groups.
func (*BuilderGroup) Parent ¶
func (g *BuilderGroup) Parent() *BuilderGroup
Parent returns the parent group, or nil if there is no parent, of the group.
func (*BuilderGroup) SetInfo ¶
func (g *BuilderGroup) SetInfo(v string)
SetInfo sets the associated info string for the group; this is user-defined and not used directly by the builder.
func (*BuilderGroup) SetParent ¶
func (g *BuilderGroup) SetParent(v *BuilderGroup)
SetParent sets the parent group of this group; it may be nil to indicate this group is a top-level group.
type BuilderNode ¶
type BuilderNode struct {
// contains filtered or unexported fields
}
BuilderNode is a node within a builder; a node represents a single assignment target of replicas of partitions, such as a single disk in a distributed storage system.
func (*BuilderNode) Assign ¶
func (n *BuilderNode) Assign(replica, partition int)
Assign will override the current builder's assignment and set a specific replica of a partition to this specific node. This is mostly just useful for testing, as future calls to Rebalance may move this assignment.
func (*BuilderNode) Capacity ¶
func (n *BuilderNode) Capacity() int
Capacity specifies, relative to other nodes, how many assignments the node should have.
func (*BuilderNode) Group ¶
func (n *BuilderNode) Group() *BuilderGroup
Group returns the parent group of the node; it may return nil if there is no parent group.
func (*BuilderNode) Info ¶
func (n *BuilderNode) Info() string
Info returns the user-defined info string; this info is not used directly by the builder.
func (*BuilderNode) Partitions ¶
func (n *BuilderNode) Partitions() []int
Partitions returns the list of partitions assigned to this node; the list will be in ascending order with no duplicates.
func (*BuilderNode) ReplicaPartitions ¶
func (n *BuilderNode) ReplicaPartitions(replica int) []int
ReplicaPartitions returns the list of partitions assigned to this node for the given replica; the list will be in ascending order with no duplicates.
func (*BuilderNode) Responsible ¶
func (n *BuilderNode) Responsible(key int) int
Responsible returns the replica number this node is responsible for with respect to the key given; will return -1 if this node is not responsible for any replica for the key.
func (*BuilderNode) ResponsibleForReplicaPartition ¶
func (n *BuilderNode) ResponsibleForReplicaPartition(replica, partition int) bool
ResponsibleForReplicaPartition returns true if this node is reponsible for the specific replica and partition given.
func (*BuilderNode) SetCapacity ¶
func (n *BuilderNode) SetCapacity(v int)
SetCapacity specifies, relative to other nodes, how many assignments the node should have.
func (*BuilderNode) SetGroup ¶
func (n *BuilderNode) SetGroup(group *BuilderGroup)
SetGroup sets the parent group of the node; it may be set to nil to have no parent group.
func (*BuilderNode) SetInfo ¶
func (n *BuilderNode) SetInfo(v string)
SetInfo sets the user-defined info string; this info is not used directly by the builder.
type Node ¶
type Node struct {
// contains filtered or unexported fields
}
func (*Node) Partitions ¶
func (*Node) PartitionsForReplica ¶
func (*Node) Responsible ¶
func (*Node) ResponsibleForReplicaPartition ¶
type Ring ¶
type Ring struct {
// contains filtered or unexported fields
}
func Unmarshal ¶
Example ¶
package main import ( "bytes" "fmt" "github.com/gholt/ring" ) func main() { // Build a ring to marshal... b := ring.NewBuilder(2) b.AddNode("Node One", 1, nil) b.AddNode("Node Two", 1, nil) b.Rebalance() r1 := b.Ring() // And marshal it... var buf bytes.Buffer if err := r1.Marshal(&buf); err != nil { panic(err) } // So we can show how to unmarshal it... r2, err := ring.Unmarshal(&buf) if err != nil { panic(err) } // And just do some checks to show they're the same... if !r1.Rebalanced().Equal(r2.Rebalanced()) { panic("") } if r1.ReplicaCount() != r2.ReplicaCount() { panic(fmt.Sprint(r1.ReplicaCount(), r2.ReplicaCount())) } if r1.PartitionCount() != r2.PartitionCount() { panic("") } compareNodeSlices := func(ns1, ns2 []*ring.Node) { if len(ns1) != len(ns2) { panic("") } for i := 0; i < len(ns1); i++ { if ns1[i].Capacity() != ns2[i].Capacity() { panic("") } if ns1[i].Info() != ns2[i].Info() { panic("") } } } compareNodeSlices(r1.Nodes(), r2.Nodes()) for partition := 0; partition < r1.PartitionCount(); partition++ { compareNodeSlices(r1.KeyNodes(partition), r2.KeyNodes(partition)) } fmt.Println("They look the same!") }
Output: They look the same!
func (*Ring) AssignmentCount ¶
func (*Ring) Marshal ¶
Example ¶
package main import ( "bytes" "fmt" "github.com/gholt/ring" ) func main() { // Build a ring to marshal... b := ring.NewBuilder(2) b.AddNode("Node One", 1, nil) b.AddNode("Node Two", 1, nil) b.Rebalance() r := b.Ring() // And marshal it... var buf bytes.Buffer if err := r.Marshal(&buf); err != nil { panic(err) } fmt.Println(len(buf.Bytes()), "bytes written") fmt.Println(string(buf.Bytes()[:213]), "...") // Note that even though the beginning is just JSON, there is trailing // binary for the larger data sets that JSON just isn't suited for. }
Output: 243 bytes written {"MarshalVersion":0,"NodeType":16,"ReplicaCount":2,"PartitionCount":2,"Nodes":[{"Info":"Node One","Capacity":1,"Group":0},{"Info":"Node Two","Capacity":1,"Group":0}],"Groups":[{"Info":"","Parent":0}],"Rebalanced": ...