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
  • 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?