Lecture 9 - RPC and Concurrency Control
Lecture 9 - RPC and Concurrency Control
Computing
FALL 2022
Lecture:10 - RPCs and Concurrency Control
Why RPCs
• RPC = Remote Procedure Call
• Proposed by Birrell and Nelson in 1984
• Important abstraction for processes to call functions in
other processes
• Allows code reuse
• Implemented and used in most distributed systems,
including cloud computing systems
• Counterpart in Object-based settings is called RMI
(Remote Method Invocation)
2
Local Procedure Call (LPC)
• Call from one function to another function
within the same process
– Uses stack to pass arguments and return values
– Accesses objects via pointers (e.g., C) or by
reference (e.g., Java)
• LPC has exactly-once semantics
– If process is alive, called function executed exactly
once
4
LPCs
P1 main() LPC
int f1()
int f2()
LPC
RPCs
P1 main() LPC
int f1()
int f2()
LPC
RPC
P2
int f2()
6
RPCs
RPC
P2
int f2()
RPCs
8
RPC Call Semantics
• Under failures, hard to guarantee exactly-once semantics
• Function may not be executed if
– Request (call) message is dropped
– Reply (return) message is dropped
– Called process fails before executing called function
– Called process fails after executing called function
– Hard for caller to distinguish these cases
• Function may be executed multiple times if
– Request (call) message is duplicated
10
Idempotent Operations
• Idempotent operations are those that can be repeated
multiple times, without any side effects
• Examples (x is server-side variable)
– x=1;
– x=(argument) y;
• Non-examples
– x=x+1;
– x=x*2
• Idempotent operations can be used with at-least-once
semantics
11
Implementing RPCs
int caller()
Client stub
P1 Communication module
(“client”)
Communication module
Dispatcher
Server stub
P2
(“server”) int callee()
12
RPC Components
int caller()
Client
Client stub
• Client stub: has same function signature as
callee()
P1 Communication module – Allows same caller() code to be used for LPC and
RPC
(“client”)
• Communication Module: Forwards requests
Communication module and replies to appropriate hosts
Dispatcher
Server
• Dispatcher: Selects which server stub to
Server stub forward request to
P2 • Server stub: calls callee(), allows it to return
(“server”) int callee() a value
13
Generating Code
• Programmer only writes code for caller function and callee
function
• Code for remaining components all generated automatically
from function signatures (or object interfaces in Object-based
languages)
– E.g., Sun RPC system: Sun XDR interface representation fed into rpcgen
compiler
• These components together part of a Middleware system
– E.g., CORBA (Common Object Request Brokerage Architecture)
– E.g., Sun RPC
– E.g., Java RMI
14
Marshalling
15
Marshalling (2)
• Middleware has a common data representation (CDR)
– Platform-independent
• Caller process converts arguments into CDR format
– Called “Marshalling”
• Callee process extracts arguments from message into its own
platform-dependent format
– Called “Unmarshalling”
• Return values are marshalled on callee process and
unmarshalled at caller process
16
Next
• Now that we know RPCs, we can use them as a
building block to understand transactions
17
Transaction
• Series of operations executed by client
• Each operation is an RPC to a server
• Transaction either
– completes and commits all its operations at server
• Commit = reflect updates on server-side objects
– Or aborts and has no effect on server
18
Example: Transaction
Client code:
int transaction_id = openTransaction();
x = server.getFlightAvailability(ABC, 123, date);
if (x > 0)
y = server.bookTicket(ABC, 123, date);
RPCs
server.putSeat(y, “aisle”);
// commit entire transaction or abort
closeTransaction(transaction_id);
19
Example: Transaction
Client code:
int transaction_id = openTransaction();
x = server.getFlightAvailability(ABC, 123, date);// read(ABC, 123, date)
if (x > 0)
y = server.bookTicket(ABC, 123, date); // write(ABC, 123, date)
RPCs
server.putSeat(y, “aisle”); // write(ABC, 123, date)
// commit entire transaction or abort
closeTransaction(transaction_id);
20
Atomicity and Isolation
• Atomicity: All or nothing principle: a transaction should either i)
complete successfully, so its effects are recorded in the server
objects; or ii) the transaction has no effect at all.
• Isolation: Need a transaction to be indivisible (atomic) from the
point of view of other transactions
– No access to intermediate results/states of other transactions
– Free from interference by operations of other transactions
• But…
• Clients and/or servers might crash
• Transactions could run concurrently, i.e., with multiple clients
• Transactions may be distributed, i.e., across multiple servers
21
22
Concurrency Control
23
24
1. Lost Update Problem
At Server: seats = 10
Transaction T1 Transaction T2
x = getSeats(ABC123);
// x = 10 x = getSeats(ABC123);T1’s or T2’s update was lost!
if(x > 1) // x = 10
if(x > 1)
x = x – 1; seats = 9
write(x, ABC123);
x = x – 1;
seats = 9
write(x, ABC123);
commit
commit
25
26
Next
• How to prevent transactions from affecting
each other
27
Concurrent Transactions
• To prevent transactions from affecting each other
– Could execute them one at a time at server
– But reduces number of concurrent transactions
– Transactions per second directly related to revenue of companies
• This metric needs to be maximized
28
Serial Equivalence
• An interleaving (say O) of transaction operations is serially equivalent iff
(if and only if):
– There is some ordering (O’) of those transactions, one at a time, which
– Gives the same end-result (for all objects and transactions) as the original
interleaving O
– Where the operations of each transaction occur consecutively (in a batch)
• Says: Cannot distinguish end-result of real operation O from (fake) serial
transaction order O’
29
30
Checking for Serial Equivalence (2)
• Two transactions are serially equivalent if and only if all pairs of
conflicting operations (pair containing one operation from each
transaction) are executed in the same order (transaction order) for all
objects (data) they both access.
– Take all pairs of conflict operations, one from T1 and one from T2
– If the T1 operation was reflected first on the server, mark the pair as “(T1, T2)”,
otherwise mark it as “(T2, T1)”
– All pairs should be marked as either “(T1, T2)” or all pairs should be marked as “(T2,
T1)”.
31
if(x > 1)
(T1, T2) seats = 9
x = x – 1;
write(x, ABC123);
(T1, T2)
x = x – 1;
seats = 9
write(x, ABC123);
commit
commit
32
2. Inconsistent Retrieval Problem – Caught!
At Server:
Transaction T1 Transaction T2 ABC123 = 10
ABC789 = 15
x = getSeats(ABC123);
y = getSeats(ABC789); T2’s sum is the wrong value!
Should have been “Total: 25”
write(x-5, ABC123); (T1, T2)
x = getSeats(ABC123);
y = getSeats(ABC789);
write(y+5, ABC789); (T2, T1) // x = 5, y = 15
print(“Total:” x+y);
commit // Prints “Total: 20”
commit
33
34
Can We do better?
35
Two Approaches
36
Pessimistic vs. Optimistic
37
38
Can we improve concurrency?
39
42
Why Two-phase Locking => Serial
Equivalence?
• Proof by contradiction
• Assume two phase locking system where serial equivalence is
violated for some two transactions T1, T2
• Two facts must then be true:
– (A) For some object O1, there were conflicting operations in T1 and T2 such
that the time ordering pair is (T1, T2)
– (B) For some object O2, the conflicting operation pair is (T2, T1)
• (A) => T1 released O1’s lock and T2 acquired it after that
=> T1’s shrinking phase is before or overlaps with T2’s growing phase
• Similarly, (B) => T2’s shrinking phase is before or overlaps with
T1’s growing phase
• But both these cannot be true!
43
Downside of Locking
• Deadlocks!
44
Downside of Locking – Deadlocks!
Transaction T1 Transaction T2 T1
Lock(ABC123);
Lock(ABC789); Wait for Wait for
x = write(10, ABC123);
Lock(ABC789); T2
// Blocks waiting for T2 y = write(15, ABC789);
Lock(ABC123);
… // Blocks…
waiting for T1
45
46
Combating Deadlocks
1. Lock timeout: abort transaction if lock cannot be
acquired within timeout
L Expensive; leads to wasted work
2. Deadlock Detection:
–keep track of Wait-for graph (e.g., via Global Snapshot
algorithm), and
–find cycles in it (e.g., periodically)
–If find cycle, there’s a deadlock => Abort one or more
transactions to break cycle
L Still allows deadlocks to occur
47
48
Next
49
50
First-cut Approach
• Most basic approach
– Write and read objects at will
– Check for serial equivalence at commit time
– If abort, roll back updates made
– An abort may result in other transactions that read
dirty data, also being aborted
• Any transactions that read from those transactions also
now need to be aborted
L Cascading aborts
51
52
Third Approach: Multi-version
Concurrency Control
• For each object
– A per-transaction version of the object is maintained
• Marked as tentative versions
– And a committed version
• Each tentative version has a timestamp
– Some systems maintain both a read timestamp and a write
timestamp
• On a read or write, find the “correct” tentative version to read or
write from
– “Correct” based on transaction id, and tries to make transactions
only read from “immediately previous” transactions
53
Eventual Consistency…
• …that you’ll see in key-value stores…
• … is a form of optimistic concurrency
control
– In Cassandra key-value store
– In DynamoDB key-value store
– In Riak key-value store
• But since non-transaction systems, the
optimistic approach looks different
54
Eventual Consistency in Cassandra
and DynamoDB
• Only one version of each data item (key-value pair)
• Last-write-wins (LWW)
– Timestamp, typically based on physical time, used to determine
whether to overwrite
if(new write’s timestamp > current object’s timestamp)
overwrite;
else
do nothing;
• With unsynchronized clocks
– If two writes are close by in time, older write might have a newer
timestamp, and might win
55
56
Summary
• RPCs and RMIs
• Transactions
• Serial Equivalence
– Detecting it via conflicting operations
• Pessimistic Concurrency Control:
locking
• Optimistic Concurrency Control
57