Assignment 4: Scaling Up

Your PHB has gotten the wild notion that the timeserver is going to be so amazingly successful that mutiple machines will be required to satisfy the demand.

Unfortunately, if you run two timeservers (on different machines or even on a single machine with different ports), the login information is not shared between the two servers.

Since credential processing takes so much less time than getting the current time1, the system architect desides on a centralized backend authentication ("auth") server. The timeserver will continue to read and (if necessary) set the user cookie2 but will make calls to the authserver as needed to get the name associated with the cookie.

You will need to create a new main program. Now is a good time to create a directory in your source tree, command, with subdirectories for each main program (timeserver) and (authserver).

You will also need to create a module for the auth client library that will communicate with the auth server.

If you've done Assignment 3 "right", the only change you would need to make to the existing timeserver code will be in your cookie module. If you've inlined the cookie management into the handlers, you will need to do a bit of refactoring.

If you haven't already factored out the code that protects concurrent access to the dictionary mapping cookie to user name, now is a good time. I call my protected dictionary concurrent_map and import it into client modules with the alias cmap3. You might be able to reuse it in the auth server.

Now is also a good time to factor out the configuration code because both the timeserver and authserver must share a common flag --authport. The timeserver will require an additional flag, --authhost specifying the hostname of the auth server.

Flag processing can be performed by the init function4 of the config module5.

Communication Protocol

The auth server takes two requests:

  1. /get?cookie=uuid: returns status 200 and the name associated with cookie, or status 400 if the request is bad. If there is no associated name, an empty string is returned (with the 200 status).
  2. /set?cooke=cookie&name=name, returns 200 if the request was processed, or 400 if the request is malformed.

Any other requests should return 404.

The timeserver should take a --authtimeout-ms flag so it doesn't wait indefinitely for a response that may never come. Stylistically, it's easier to remember that the timeout is in milliseconds when the unit is coded into the flag name.

If the authserver fails, the timeserver should treat the user as logged out and clear the cookie.

Note: to get the values of the parameters after the question mark, you have to call ParseForm on the request object to fill in the fields, then call Get on the request's Form field.

Load Simulation

Some additional changes to the timeserver are required to simulate slightly more realistic behavior.

Response Delay

Since getting the current time really doesn't really take so much computational resources, add two flags --avg-response-ms and --deviation-ms. The time request handler will generate a random delay based on average reponse time (in milliseconds) and the standard deviation, also in milliseconds. Use a Gaussian distribution to generate the random number. Clip any negative delays. Check out Golang's random number generator package. Note that the top-level functions of rand are thread-safe6

Maximum Number of Concurrent Requests

Add another command-line flag, --max-inflight that give the maximum number of in-flight time requests the server can handle. If unspecified or set to 0, the server will handle as many as it can, until it falls over.

If the --max-inflight flag is given, the server will keep track of the maximum number of requests it's currently handling. Once it reaches max-inflight concurrent requests, it will respond to additional requests with status Internal Server Error

Note that the current in-flight count will be accessed concurrently by the request handlers, so the count must be protected.

NOTE: your browser may limit the number of concurrent request that it makes. Test max-inflight with a short script that invokes multiple instances of curl in the background.

Backing Store

There's a problem with the system: whenever you restart the authserver, all the login information is lost and all users have to log back in. Your tech lead asks you to modify the authserver so that it will periodically dump the login state as a JSON-encoded file.

Add as flags --dumpfile and --checkpoint-interval.

If --dumpfile is specified, when authserver starts up, it will load the dictionary from dumpfile if the dumpfile is present.

If --checkpoint-interval is specified, every checkpoint-interval seconds, the dumpfile, if it exists, will be renamed by adding the extension .bak. Then, concurrent dictionary will be copied to a non-concurrent dictionary (to minimise the time you're holding the mutex), JSON-encoded, and written to dumpfile. The dumpfile will be read back, and verified. If the read-back is successful, the backup file should be deleted.

Hint: you're loading in the dictionary at two different points in the program. How many times should you need to code it?

Consider making use of these standard library packages, json, os, filepath, strings, and any other packages you may find useful.

Hand-In

Submit a tarball of your cleaned workspace, plus README file and any build/run scripts.

Git is no longer required, although it is recommended that you continue using it.

Footnotes