Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions pkg/document/change/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ func (c *Context) RegisterElementHasRemovedNodes(element crdt.GCElement) {
c.root.RegisterElementHasRemovedNodes(element)
}

// RegisterGCNodePairMapByID register the given GCNode pair to hash table.
func (c *Context) RegisterGCNodePairMapByID(key string, parent crdt.GCNode, child crdt.GCNode) {
c.root.RegisterGCNodePairMapByID(key, parent, child)
}

// LastTimeTicket returns the last time ticket issued by this context.
func (c *Context) LastTimeTicket() *time.Ticket {
return c.id.NewTimeTicket(c.delimiter)
Expand Down
2 changes: 1 addition & 1 deletion pkg/document/crdt/element_rht.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (rht *ElementRHT) Set(k string, v Element) Element {
if !ok || v.CreatedAt().After(node.elem.CreatedAt()) {
rht.nodeMapByKey[k] = newNode
}

// TODO(raararaara): Overwritten node does not need to be purged.
return removed
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/document/crdt/gc_node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package crdt

import "github.com/yorkie-team/yorkie/pkg/document/time"

// GCNode represents a common node with GC.
type GCNode interface {
// GetID returns the IDString of this node.
GetID() string

// GetRemovedAt returns the removal time of this node.
GetRemovedAt() *time.Ticket

// Purge physically purges the given child of this node.
Purge(node GCNode, ticket *time.Ticket) (int, error)
}
86 changes: 59 additions & 27 deletions pkg/document/crdt/rht.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ func newRHTNode(key, val string, updatedAt *time.Ticket, isRemoved bool) *RHTNod
}
}

// Remove removes this node. It marks RHTNode as removed and updates the value of `updatedAt` (tombstone).
func (n *RHTNode) Remove(removedAt *time.Ticket) bool {
if removedAt != nil && removedAt.After(n.UpdatedAt()) &&
!n.isRemoved || removedAt.After(n.UpdatedAt()) {
n.isRemoved = true
n.updatedAt = removedAt

return true
}
return false
}

// Key returns the key of this node.
func (n *RHTNode) Key() string {
return n.key
Expand All @@ -56,19 +68,38 @@ func (n *RHTNode) UpdatedAt() *time.Ticket {
return n.updatedAt
}

// GetID returns the IDString of this node.
func (n *RHTNode) GetID() string {
return n.updatedAt.Key() + ":" + n.key
}

// GetRemovedAt returns the removal time of this node.
func (n *RHTNode) GetRemovedAt() *time.Ticket {
if n.isRemoved {
return n.updatedAt
}
return nil
}

// Purge physically purges children of given node.
// NOTE(raararaara): This method was added only for consistency.
// It will not be used unless additional children of RHTNode are implemented.
func (n *RHTNode) Purge(_ GCNode, _ *time.Ticket) (int, error) {
return 0, nil
}

// RHT is a hashtable with logical clock(Replicated hashtable).
// For more details about RHT: http://csl.skku.edu/papers/jpdc11.pdf
// NOTE(justiceHui): RHT and ElementRHT has duplicated functions.
type RHT struct {
nodeMapByKey map[string]*RHTNode
numberOfRemovedElement int
// nodeMapByKey is a map with values of nodes by key.
nodeMapByKey map[string]*RHTNode
}

// NewRHT creates a new instance of RHT.
func NewRHT() *RHT {
return &RHT{
nodeMapByKey: make(map[string]*RHTNode),
numberOfRemovedElement: 0,
nodeMapByKey: make(map[string]*RHTNode),
}
}

Expand Down Expand Up @@ -96,39 +127,27 @@ func (rht *RHT) Has(key string) bool {
// Set sets the value of the given key.
func (rht *RHT) Set(k, v string, executedAt *time.Ticket) {
if node, ok := rht.nodeMapByKey[k]; !ok || executedAt.After(node.updatedAt) {
if node != nil && node.isRemoved {
rht.numberOfRemovedElement--
}
newNode := newRHTNode(k, v, executedAt, false)
rht.nodeMapByKey[k] = newNode
}
}

// Remove removes the Element of the given key.
func (rht *RHT) Remove(k string, executedAt *time.Ticket) string {
if node, ok := rht.nodeMapByKey[k]; !ok || executedAt.After(node.updatedAt) {
// NOTE(justiceHui): Even if key is not existed, we must set flag `isRemoved` for concurrency
if node == nil {
rht.numberOfRemovedElement++
func (rht *RHT) Remove(k string, executedAt *time.Ticket) *RHTNode {
if node, ok := rht.nodeMapByKey[k]; !ok || node.Remove(executedAt) {
if !ok {
newNode := newRHTNode(k, ``, executedAt, true)
rht.nodeMapByKey[k] = newNode
return ""
}

alreadyRemoved := node.isRemoved
if !alreadyRemoved {
rht.numberOfRemovedElement++
return newNode
}
newNode := newRHTNode(k, node.val, executedAt, true)
rht.nodeMapByKey[k] = newNode

if alreadyRemoved {
return ""
}
return node.val
return newNode
}

return ""
return nil
}

// Elements returns a map of elements because the map easy to use for loop.
Expand All @@ -144,22 +163,35 @@ func (rht *RHT) Elements() map[string]string {
return members
}

// Nodes returns a map of elements because the map easy to use for loop.
// Nodes returns a list of RHTNodes.
// TODO: If we encounter performance issues, we need to replace this with other solution.
func (rht *RHT) Nodes() []*RHTNode {
var nodes []*RHTNode
for _, node := range rht.nodeMapByKey {
if !node.isRemoved {
nodes = append(nodes, node)
}
nodes = append(nodes, node)
}

return nodes
}

func (rht *RHT) purge(key string, ticket *time.Ticket) (int, error) {
if node, ok := rht.nodeMapByKey[key]; ok && ticket.After(node.updatedAt) {
delete(rht.nodeMapByKey, key)
return 1, nil
}

return 0, nil
}

// Len returns the number of elements.
func (rht *RHT) Len() int {
return len(rht.nodeMapByKey) - rht.numberOfRemovedElement
count := 0
for _, node := range rht.Nodes() {
if !node.isRemoved {
count++
}
}
return count
}

// DeepCopy copies itself deeply.
Expand Down
165 changes: 93 additions & 72 deletions pkg/document/crdt/rht_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,84 +141,103 @@ func TestRHT_Remove(t *testing.T) {
key2, val2, val22 := `key2`, `value2`, `value22`

tests := []struct {
desc string
insertKey []string
insertVal []string
deleteKey []string
deleteVal []string
expectXML string
expectJSON string
expectSize int
desc string
insertKey []string
insertVal []string
deleteKey []string
deleteVal []string
expectXML string
expectJSON string
expectSize int
expectTotalSize int
}{
{
desc: `1. set elements`,
insertKey: []string{key1, key2},
insertVal: []string{val1, val2},
deleteKey: []string{},
deleteVal: []string{},
expectXML: `key1="value1" key2="value2"`,
expectJSON: `{"key1":"value1","key2":"value2"}`,
expectSize: 2,
desc: `1. set single element`,
insertKey: []string{key1},
insertVal: []string{val1},
deleteKey: []string{},
deleteVal: []string{},
expectXML: `key1="value1"`,
expectJSON: `{"key1":"value1"}`,
expectSize: 1,
expectTotalSize: 1,
},
{
desc: `2. remove element`,
insertKey: []string{},
insertVal: []string{},
deleteKey: []string{key1},
deleteVal: []string{val1},
expectXML: `key2="value2"`,
expectJSON: `{"key2":"value2"}`,
expectSize: 1,
desc: `2. set another element`,
insertKey: []string{key2},
insertVal: []string{val2},
deleteKey: []string{},
deleteVal: []string{},
expectXML: `key1="value1" key2="value2"`,
expectJSON: `{"key1":"value1","key2":"value2"}`,
expectSize: 2,
expectTotalSize: 2,
},
{
desc: `3. set after remove`,
insertKey: []string{key1},
insertVal: []string{val11},
deleteKey: []string{},
deleteVal: []string{},
expectXML: `key1="value11" key2="value2"`,
expectJSON: `{"key1":"value11","key2":"value2"}`,
expectSize: 2,
desc: `3. remove element`,
insertKey: []string{},
insertVal: []string{},
deleteKey: []string{key1},
deleteVal: []string{val1},
expectXML: `key2="value2"`,
expectJSON: `{"key2":"value2"}`,
expectSize: 1,
expectTotalSize: 2,
},
{
desc: `4. set after remove`,
insertKey: []string{key1},
insertVal: []string{val11},
deleteKey: []string{},
deleteVal: []string{},
expectXML: `key1="value11" key2="value2"`,
expectJSON: `{"key1":"value11","key2":"value2"}`,
expectSize: 2,
expectTotalSize: 2,
},
{
desc: `5. remove element`,
insertKey: []string{key2},
insertVal: []string{val22},
deleteKey: []string{key1},
deleteVal: []string{val11},
expectXML: `key2="value22"`,
expectJSON: `{"key2":"value22"}`,
expectSize: 1,
expectTotalSize: 2,
},
{
desc: `6. remove element again`,
insertKey: []string{},
insertVal: []string{},
deleteKey: []string{key1},
deleteVal: []string{val11},
expectXML: `key2="value22"`,
expectJSON: `{"key2":"value22"}`,
expectSize: 1,
expectTotalSize: 2,
},
{
desc: `7. remove element(cleared)`,
insertKey: []string{},
insertVal: []string{},
deleteKey: []string{key2},
deleteVal: []string{val22},
expectXML: ``,
expectJSON: `{}`,
expectSize: 0,
expectTotalSize: 2,
},
{
desc: `4. remove element`,
insertKey: []string{key2},
insertVal: []string{val22},
deleteKey: []string{key1},
deleteVal: []string{val11},
expectXML: `key2="value22"`,
expectJSON: `{"key2":"value22"}`,
expectSize: 1,
},
{
desc: `5. remove element again`,
insertKey: []string{},
insertVal: []string{},
deleteKey: []string{key1},
deleteVal: []string{``},
expectXML: `key2="value22"`,
expectJSON: `{"key2":"value22"}`,
expectSize: 1,
},
{
desc: `6. remove element(cleared)`,
insertKey: []string{},
insertVal: []string{},
deleteKey: []string{key2},
deleteVal: []string{val22},
expectXML: ``,
expectJSON: `{}`,
expectSize: 0,
},
{
desc: `7. remove not exist key`,
insertKey: []string{},
insertVal: []string{},
deleteKey: []string{`not-exist-key`},
deleteVal: []string{``},
expectXML: ``,
expectJSON: `{}`,
expectSize: 0,
desc: `8. remove not exist key`,
insertKey: []string{},
insertVal: []string{},
deleteKey: []string{`not-exist-key`},
deleteVal: []string{``},
expectXML: ``,
expectJSON: `{}`,
expectSize: 0,
expectTotalSize: 3,
},
}

Expand All @@ -234,13 +253,15 @@ func TestRHT_Remove(t *testing.T) {
}
for i, key := range tt.deleteKey {
removedElement := rht.Remove(key, ctx.IssueTimeTicket())
assert.Equal(t, tt.deleteVal[i], removedElement)
if removedElement != nil {
assert.Equal(t, tt.deleteVal[i], removedElement.Value())
}
}
assert.Equal(t, tt.expectXML, rht.ToXML())
assert.Equal(t, tt.expectJSON, rht.Marshal())
assert.Equal(t, tt.expectSize, rht.Len())
assert.Equal(t, tt.expectSize, len(rht.Nodes()))
assert.Equal(t, tt.expectSize, len(rht.Elements()))
assert.Equal(t, tt.expectTotalSize, len(rht.Nodes()))
})
}
}
Loading