What is Proto-Cache?
I’ve been working internally at Google to open source several libraries including cl-protobufs and a series of utility libraries we call “ace”. I wrote several blog posts making an HTTP server that takes in either protocol buffers or JSON strings and responds in kind. I think I have worked enough on Mortgage Server and wish to work on a different project.
Proto-cache will grow up to be a pub-sub system that takes in google.protobuf:any protos and send them to users over http requests. I’m developing it to showcase the ace.core library and the Any proto well-known-type. In this post we create a cache system which stores google.protobuf.any messages in a hash-table keyed off of a symbol.
The current incarnation of Proto Cache:
The code can be found here: https://github.com/Slids/proto-cache
This is remarkable in-as-much as cl-protobufs isn’t required for the defsystem! It’s not required at all, but we do require the cl-protobufs.google.protobuf:any protocol buffer message object. Right now we are only adding and getting it from the cache. This allows us to store a protocol buffer message object that any user system can parse by calling unpack-any. We never have to understand the message inside.
The actual implementation. We give three different functions:
We also have a:
- fast-read mutex
Note: The ace.core library can be found at: https://github.com/cybersurf/ace.core
Fast-read mutex (fr-mutex):
The first interesting thing to note is the fast-read mutex. This can be found in the ace.core.thread package included in the ace.core utility library. This allows for mutex free reads of a protected region of code. One has to call:
- (with-frmutex-read (fr-mutex) body)
- (with-frmutex-write (fr-mutex) body)
If the body of with-frmutex-read is finished with nobody calling with-frmutex-write then the value is returned. If someone calls with-frmutex-write while another thread is in with-frmutex-read then the body of with-frmutex-read has to be re-run. One should be careful to not modify state in the with-frmutex-read body.
Discussion About the Individual Functions
(acd:defun* get-from-cache (key) "Get the any message from cache with KEY." (declare (acd:self (symbol) google:any)) (act:with-frmutex-read (cache-mutex) (gethash key cache)))
This function uses the defun* form from ace.core.defun. It looks the same as a standard defun except has a new declare statement. The declare statement takes the form
(declare (acd:self (lambda-list-type-declarations) output-declaration))
In this function we state that the input KEY must be a symbol and the return value is going to be a google:any protobuf message. The output declaration is optional. For all of the options please see the macro definition for ace.core.defun:defun*.
The with-fr-mutex-read macro is also being used.
Note in the macro’s body we only do a simple accessor call into a hash-table. Safety is not guaranteed, only consistency.
(acd:defun* set-in-cache (key any) "Set the ANY message in cache with KEY." (declare (acd:self (symbol google:any) google:any)) (act:with-frmutex-write (cache-mutex) (setf (gethash key cache) any)))
We see that the new defun* call is used. In this case we have two inputs, KEY will be a symbol ANY will be a google:any proto message. We also see that we will return a google:any proto message.
The with-frmutex-write macro is being used. The only thing that is done in the body is setting a cache value. If we try to get a message from the cache and set a message into the cache, it is possible a reader will have to read multiple times. In systems where readers are more common than writers fr-mutexes and spinlocking are much faster than having readers lock a mutex for every read..
We omit this function in this write-up for brevity.
Fast-read mutexes like the one found in ace.core.thread are incredibly useful tools. Having to access a mutex can be slow even in cases where that mutex is never locked. I believe this is one of the more useful additions in the ace.core library.
The new defun* macro found in ace.core.defun for creating function definitions is more mixed. I find a lack of clarity in mapping the lambda list s-expression in the defun statement to the s-expression in the declaration. Others may find it provides nicer syntax and the clarity is more obvious.
Future posts will show the use of the any protocol buffer message.
As usual Carl Gay gave copious edits and suggestions.