Raft Project 1 Review/Discussion (Winter 2021)
Lecture Notes for CS 190
Winter 2021
John Ousterhout
Class Design
- Small project, so not many opportunities for deep classes.
- Communication
- Persistence
- Raft server state machine
- Client-side communication
- Shell command execution
- Most common problems:
- Specialization: API tailored in ways that restrict its usage for things
other than the current Raft implementation
- Fuzzy division of responsibility
- A class handles part of a problem, but not all of it.
- Or, multiple implementations of the same thing (e.g.,
for clients and servers)
- Classes for communication:
- Connection topology alternatives:
- Requester opens connections, responses sent back on the same
connection as request.
- Sender opens connections: all outgoing traffic (requests and
responses uses sender's connection).
- One connection between each pair of servers.
- Threading architecture:
- Must allow independent operation of each socket
- If one socket blocks, this must not prevent communication to/from
other sockets
- Choice #1: single thread:
- Simple and clean from synchronization standpoint
- But, must use nonblocking I/O:
- Reads may return only part of a message (must save it until
the rest arrives).
- Writes may send only part of the message (must save the remainder
to try again later).
- Choice #2: separate thread(s) for each socket
- Introduces synchronization issues
- If there are many connections, this becomes inefficient
- Does the multi-threaded approach increase server throughput?
- Observation: the sockets streaming API is awkward for RPCs
- Unifying client-client and client-server communication:
- What problems motivated the differences?
- Persistence:
- In most projects this was specialized for Raft:
- No class: persistence implemented by Raft state machine
- Separate class, but APIs reflect Raft details such as term and vote
- Raft state machine
- Collect all of this code into a single class
- Very simple API:
- Decomposition choice #1: separate code for each state:
- One method or class per state
- Decomposition choice #2: separate code for each message type
- Client-side communication:
- Implementations were too specialized (read from stdin, write to stdout)
- Executing client commands:
- A very small class, but has nothing in common with the Raft state machine.
Exception Handling
- Common problems:
- Not enough error checks
- Not enough info in log messages
- Exceptions not handled in the best way
- In general, unsafe to assume anything about information coming from
outside the process
- Contents of files holding persistent data (e.g. std::stoi).
- Message formats
- Logging is essential:
- Log as often as you can possibly afford
- Include as much information in the log message as possible
- Log at the scene of the crime, where the most information is
available (or, incorporate the info into an exception).
- Must check results of every kernel call
- What to do when an error occurs? First, think about how it is likely
to be handled.
- Don't exit in low-level methods
- Limits generality
- Bad for unit testing
- Instead, throw exception
- Define specific exception types: don't just use std::exception (consider likely usage)
- All threads should have top-level exception handlers: catch, log, exit
Writing Obvious Code
- C++ constructs to avoid:
- std::pair, std::tuple
- auto
- Use closures judiciously (examples Obvious1-2)
- C++ I/O APIs are clunky (example Obvious3)
- Spacing and indentation affect readability (example Obvious4)
Documentation
- Most projects did a pretty good job.
- A few projects didn't have enough:
- Particularly: instance variables and parameters
- Common error: implementation information in interface
documentation (example Doc1)
- It is possible to overdo documentation (example Doc2)
Miscellaneous
- Interface comments:
- In header file vs. code file
- Private vs. public methods
- Multiple headers (interface vs. implementation?)
- Bad variable names (Example Misc1-2)
- Using one variable for two different things (Misc3)
Thoughts for Project 2
- Avoid specializations and restrictions.
- Just because you know something doesn't mean you should use that information
- Delay specialization: push it up to the highest layers of the application
- Make classes general-purpose
- Don't solve just part of a problem such as RPC communication or
persistence; solve the whole problem in one place
- Think about solving big problems, not little ones