From 71d608e799f4f5674c8961e304719743d26066fc Mon Sep 17 00:00:00 2001 From: Aviral Goel Date: Sun, 18 Dec 2016 01:35:23 -0500 Subject: Wrote up description for PN Counter --- chapter/6/counters.md | 230 +++++++++++++-------- .../images/counters/decrement-operation.png | Bin 0 -> 1775 bytes .../images/counters/increment-operation.png | Bin 0 -> 1649 bytes 3 files changed, 140 insertions(+), 90 deletions(-) create mode 100644 chapter/6/resources/images/counters/decrement-operation.png create mode 100644 chapter/6/resources/images/counters/increment-operation.png diff --git a/chapter/6/counters.md b/chapter/6/counters.md index 523e717..faa7eb0 100644 --- a/chapter/6/counters.md +++ b/chapter/6/counters.md @@ -41,6 +41,15 @@ class Counter(CmRDT): ![Operation based increment only counter][operation-based-increment-only-counter] +The figure above shows three replicas (c1, c2 and c3) of an operation based increment only counter Coperation. The clients issue increment requests depicted as . In accordance with the specification, each increment request increments the counter by 1 unit and the operation is transmitted by the replica to all other replicas of the system. +There are a few interesting observations: + +* **Eventually consistent** - At *t16*, the replicas are inconsistent. Eventually, they all attain consistency. This is because the increment operation happens immediately at one replica but takes time to be transmitted over the network to other replicas. Within this duration, the replicas may be inconsistent. + +* **Reliable broadcast** - The increment operation has to be transmitted to all replicas. If the network fails to deliver the operation, the system will never be able to achieve consistency. + +* **Concurrent operations** - A replica may have to handle concurrent operations. For example, at *t19*, c1 encounters two increment operations. Since the increment operation commutes with itself, the order does not matter. The replica can decide to handle the increments in arbitrary order. + ### CvRDT: State based specification @@ -77,6 +86,19 @@ class Counter(CvRDT): #### Figure ![State based increment only counter (incorrect)][state-based-increment-only-counter-incorrect] +The figure above shows three replicas (c1, c2 and c3) of a state based increment only counter Cstate. The clients issue increment requests depicted as . In accordance with the specification, each increment request increments the counter by 1 unit. and the operation is transmitted by the replica to all other replicas of the system. +There are a few interesting observations: + +* **Eventual convergence** - At *t26*, the replicas are inconsistent. Eventually, they all converge. This is because the increment operation happens immediately at one replica but takes time to be transmitted over the network to other replicas. Within this duration, the replicas may be inconsistent. + +> Eventual convergence does not guarantee correctness. + +> Incorrect design may still converge eventually. + +* **Unreliable broadcast** - Messages may be lost increment operation has to be transmitted to all replicas. If the network fails to deliver the operation, the system will never be able to achieve consistency. + +* **Concurrent operations** - A replica may have to handle concurrent operations. For example, at *t19*, c1 encounters two increment operations. Since the increment operation commutes with itself, the order does not matter. The replica can decide to handle the increments in arbitrary order. + As it can be seen from the figure, the clients issue a total of 4 increment requests. However, the replicas finally converge to 2. Something is clearly amiss! When two replicas are incremented, they should together converge to 2. But the `merge` function computes the maximum of two states. @@ -123,150 +145,178 @@ class Counter(CvRDT): As it can be seen from the figure, the clients issue a total of 4 increment requests. However, the replicas finally converge to 2. Something is clearly amiss! When two replicas are incremented, they should together converge to 2. But the `merge` function computes the maximum of two states. - - - - ## PN-counter - Increment and Decrement counter +A PN-counter can be incremented and decremented. + ### CmRDT: Operation based specification -#### Specification +The code below provides the specification of an operation based increment and decrement counter. -```python +
 
-class CmRDT:
-    pass
+class Counter(CmRDT):
 
-class Counter(CmRDT):
+    def __init__(self):         # constructor function
+        self._count = 0
 
-    def __init__(self):         # constructor function
-        self._count = 0
+    def value(self):            # query function
+        return self._count
 
-    def value(self):            # query function
-        return self._count
+    def increment(self):        # update function
+        self._count += 1
+        for replica in self.replicas():
+            self.transmit("increment", replica)
 
-    def increment(self):        # update function
-        self._count += 1
-        for replica in self.replicas():
-            self.transmit("increment", replica)
+    def decrement(self):        # update function
+        self._count -= 1
+        for replica in self.replicas():
+            self.transmit("decrement", replica)
+
- def decrement(self): # update function - self._count -= 1 - for replica in self.replicas(): - self.transmit("decrement", replica) +Let's try to understand how it works through an example. The figure below shows three replicas (c1, c2 and c3) of an operation based increment and decrement counter Coperation. -``` +* ![increment operation][increment-operation] depicts increment request issued by the client. +* ![decrement operation][decrement-operation] depicts decrement request issued by the client. -#### Figure +In accordance with the specification, each increment request increments the counter locally by one unit and each decrement request decrements the counter locally by one unit. The corresponding operation is transmitted by the replica to all other replicas of the system. -![Operation based increment and decrement counter][operation-based-increment-and-decrement-counter] +
+ Operation based increment and decrement counter +
-### CvRDT: State based specification +One can make the the following observations: -#### Specification +* **Eventually consistent** - At *t26*, the replicas are inconsistent. Eventually, they all achieve consistency. This is because the operations happen immediately at one replica but take time to be transmitted over the network to other replicas. Within this duration, the replicas may be inconsistent. +* **Reliable broadcast** - The operations have to be transmitted to all replicas. If the network fails to deliver an operation, the system will never be able to achieve consistency. +* **Concurrent operations** - A replica may have to handle concurrent operations. For example, at *t30*, c1 and c2 encounter increment and decrement operations concurrently. Since the increment and decrement operations commute, the order in which they are executed by the replicas does not matter. Both replicas perform them in different orders and still achieve consistency, eventually. -```python +### CvRDT: State based specification -class CvRDT: - pass +The code below provides the specification of a state based increment and decrement counter. -class Counter(CvRDT): +
class Counter(CvRDT):
 
-    def __init__(self, counts = None):  # constructor function
-        if counts is None:
-            self._counts = [0] * length(self.replicas())
-        else:
-            self._counts = counts
+    def __init__(self, counts = None):  # constructor function
+        if counts is None:
+            self._counts = [0] * length(self.replicas())
+        else:
+            self._counts = counts
 
-    def value(self):                    # query function
-        return sum(self._counts)
+    def value(self):                    # query function
+        return sum(self._counts)
 
-    def counts(self):                   # query function
-        return list(self._counts)       # return a clone
+    def counts(self):                   # query function
+        return list(self._counts)       # return a clone
 
-    def increment(self):                # update function
-        self._counts[self.replicaId()] += 1
+    def increment(self):                # update function
+        self._counts[self.replicaId()] += 1
 
-    def decrement(self):                # update function
-        self._counts[self.replicaId()] -= 1
+    def decrement(self):                # update function
+        self._counts[self.replicaId()] -= 1
 
-    def compare(self, other):           # comparison function
-        return all(v1 <= v2 for (v1, v2) in
-                   zip(self.counts(),
+    def compare(self, other):           # comparison function
+        return all(v1 <= v2 for (v1, v2) in
+                   zip(self.counts(),
                        other.counts()))
 
-    def merge(self, other):             # merge function
-        return Counter(map(max, zip(self.counts(),
+    def merge(self, other):             # merge function
+        return Counter(map(max, zip(self.counts(),
                                     other.counts())))
 
-```
+
-#### Figure +Let's try to understand how it works through an example. The figure below shows three replicas (c1, c2 and c3) of a state based increment and decrement counter Cstate. -![State based increment and decrement counter (incorrect)][state-based-increment-and-decrement-counter-incorrect] +* ![increment operation][increment-operation] depicts increment request issued by the client. +* ![decrement operation][decrement-operation] depicts decrement request issued by the client. +In accordance with the specification, each increment request increments the counter by one unit and each decrement request decrements the counte by one unit. The replica transmits its state at random times to a random replica. +
+ State based increment and decrement counter (incorrect) +
-#### Specification +We can make the the following observations: -```python +* **Eventual convergence** - At *t26*, the replicas are inconsistent. Eventually, they all converge. This is because the increment operation happens immediately at one replica but takes time to be transmitted over the network to other replicas. Within this duration, the replicas may be inconsistent. +* **Correctness** - The clients issue a total of four increment requests and 2 decrement requests. But the eventually consistent value of replicas is 4. So, the design is **incorrect**. +* **Concurrent operations** - A replica may have to handle concurrent operations. For example, at *t27* c1 has to handle 2 merge operations concurrently. Since the state forms a partially ordered set and the merge operation computes its least upper bound, the order in which the merge operation happens does not affect the final outcome. Similarly, at *t21* and *t29*, two operations arrive concurrently at c3 and c2 respectively. +* **Unreliable broadcast** - Messages may be lost during transmission. Message sent by c3 at *t12* is lost. But eventually, message at *t25* reaches another replica. As long as messages eventually reach other replicas, directly or indirectly, the system will achieve consistency. A message can also be received out of order or duplicated. -class CvRDT: - pass +Let's try to figure out why the design is incorrect. At *t12*, c1 merges the state from c2. The merge operation computes the index wise maximum in accordance with the specification. In doing so, the *-1* value of *c2* due to the decrement operation at *t6* is lost. Its clear that we have violated the basic constraint, the decrement operation does not take the state higher up in the lattice. In other words, the decrement operation is non monotonic. This causes us to lose the decrements and we are eventually left with replicas which only count the total number of increment requests. +But we do gain two valuable insights from this design- -class Counter(CvRDT): +> Eventual convergence does not guarantee correctness. - def __init__(self, - increments = None, - decrements = None): # constructor function - if increments is None: - self._increments = [0] * length(replicas()) - else: - self._increments = increments - if decrements is None: - self._decrements = [0] * length(replicas()) - else: - self._decrements = decrements +> Incorrect designs may still converge eventually. - def increments(self): # query function - return list(self._increments) # return a clone - def decrements(self): # query function - return list(self._decrements) # return a clone +Let's try to correct this problem. The problem happens because the decrement operation is non-monotonic. So we need a way to count the decrement operations without losing monotonicity. One solution is to model this counter using two increment only counters. The first counter counts the increment operations and the second one counts the decrement operations. The value of the actual counter is the difference between the two corresponding counters. The specification below shows how this can be implemented. - def value(self): # query function - return (sum(self.increments()) - - sum(self.decrements())) +
class Counter(CvRDT):
 
-    def increment(self):                # update function
-        self._increments[self.replicaId()] += 1
+    def __init__(self,
+                 increments = None,
+                 decrements = None):   # constructor function
+        if increments is None:
+            self._increments = [0] * length(replicas())
+        else:
+            self._increments = increments
+        if decrements is None:
+            self._decrements = [0] * length(replicas())
+        else:
+            self._decrements = decrements
 
-    def decrement(self):                # update function
-        self._decrements[self.replicaId()] += 1
+    def increments(self):               # query function
+        return list(self._increments)   # return a clone
 
-    def compare(self, other):           # comparison function
-        return (all(v1 <= v2 for (v1, v2) in
-                    zip(self.increments(),
+    def decrements(self):               # query function
+        return list(self._decrements)   # return a clone
+
+    def value(self):                    # query function
+        return (sum(self.increments()) -
+                sum(self.decrements()))
+
+    def increment(self):                # update function
+        self._increments[self.replicaId()] += 1
+
+    def decrement(self):                # update function
+        self._decrements[self.replicaId()] += 1
+
+    def compare(self, other):           # comparison function
+        return (all(v1 <= v2 for (v1, v2) in
+                    zip(self.increments(),
                         other.increments()))
-                and
-                all(v1 <= v2 for (v1, v2) in
-                    zip(self.decrements(),
+                and
+                all(v1 <= v2 for (v1, v2) in
+                    zip(self.decrements(),
                         other.decrements())))
 
-    def merge(self, other):             # merge function
-        return Counter(increments = map(max, zip(self.increments(),
+    def merge(self, other):             # merge function
+        return Counter(increments = map(max, zip(self.increments(),
                                                  other.increments())),
-                       decrements = map(max, zip(self.decrements(),
+                       decrements = map(max, zip(self.decrements(),
                                                  other.decrements())))
 
-```
+
-#### Figure +The figure below shows an execution trace of three replicas (c1, c2 and c3) of this counter. + +* ![increment operation][increment-operation] depicts increment request issued by the client. +* ![decrement operation][decrement-operation] depicts decrement request issued by the client. + +The values in the top row in the replica state depict the increments and the bottom ones depict the decrements. In accordance with the specification, each increment request increments the value in the top row by one unit and each decrement request increments the value in the lower row by one unit. Once the operation materializes locally, the new state is transmitted by the replica to some other randomly chosen replica. + +
+ State based increment and decrement counter (correct) +
-![State based increment and decrement counter (correct)][state-based-increment-and-decrement-counter-correct] +This design converges to the correct value. +[increment-operation]: resources/images/counters/increment-operation.png +[decrement-operation]: resources/images/counters/decrement-operation.png [operation-based-increment-only-counter]: resources/images/counters/operation-based-increment-only-counter.png [state-based-increment-only-counter-incorrect]: resources/images/counters/state-based-increment-only-counter-incorrect.png [state-based-increment-only-counter-correct]: resources/images/counters/state-based-increment-only-counter-correct.png diff --git a/chapter/6/resources/images/counters/decrement-operation.png b/chapter/6/resources/images/counters/decrement-operation.png new file mode 100644 index 0000000..a2d3f1a Binary files /dev/null and b/chapter/6/resources/images/counters/decrement-operation.png differ diff --git a/chapter/6/resources/images/counters/increment-operation.png b/chapter/6/resources/images/counters/increment-operation.png new file mode 100644 index 0000000..f016c23 Binary files /dev/null and b/chapter/6/resources/images/counters/increment-operation.png differ -- cgit v1.2.3