Raft Project 1 Review/Discussion (Winter 2022)
Click here for .cc file containing examples.
Class Design
Small project, so not many opportunities for deep classes.
- Communication
- Persistence
- Raft server state machine
- Client-side communication
- State machine (shell command execution)
Most common problems:
- Too many classes (shallow)
- Specialization: API/implementation tailored to Raft in ways that limit its usage for other things
- 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
- Must allow independent operation of each socket
- 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:
- Constructor
run
method
- Decomposition choice #1: separate code for each state:
- One method or class per state
- Decomposition choice #2: separate code for each message type
- Threading alternatives:
- Match network module (e.g. execute commands on per-connection threads)
- Hybrid (many threads in networking, only one thread in Raft server)
- Keep synchronization simple!
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 Closures1-2)
Spacing and indentation affect readability (example Spacing1)
Adding layers can obscure information that is important:
using
(example Using1)
Documentation
- Most projects did a pretty good job.
- Several projects didn't have enough:
- Particularly: instance variables and parameters
- Common error: implementation information in interface documentation (examples Doc1-2)
- It is possible to overdo documentation (example Doc3)
Miscellaneous
Specialization is sometimes hidden in the implementation (Specialization1-2)
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 distribute the solution to a problem (information leakage); solve the whole problem in one place
Think about solving big problems, not little ones
- Design top-down?