webapp
Cache-aside behind a load balancer (api, lb, app, cache, db)
What to watch
Hit rate warming up; what happens when the cache crashes and there is no timeout
Seed: 9. The program asks a question at the interesting moment; with Predict on, the run stops there so you can answer before you see what happens.
Run it
Edit any file and press Run. The tabs hold the program and the packages it uses.
# package main
use github.com/synodlang/examples/webapp/api
use github.com/synodlang/examples/webapp/app
use github.com/synodlang/examples/webapp/cache
use github.com/synodlang/examples/webapp/db
use github.com/synodlang/examples/webapp/lb
use std/lesson
use std/lists
use std/random
use std/sim
use std/time
define main
sim.Seed 9
set balancer to (sim.Spawn lb)
set servers to (sim.Cluster of 3 running app)
set store to (sim.Spawn cache)
set database to (sim.Spawn db)
lb.Balance servers on balancer
each s in servers
app.Wire s to store and database
set keys to {"home", "cart", "profile", "search", "help"}
each i in (lists.Range 1 to 150)
after (i times 20ms)
deliver (message api.Get with Key (random.Pick keys), Id i, Start time.Now) to balancer
lesson.Note "Misses go to the database and fill the cache; hits come back fast."
after 2s
sim.Crash store
lesson.Ask "The cache just crashed. What happens to new requests?" choosing {"They go straight to the database instead", "They hang: nothing answers and nothing times out", "The load balancer notices and retries on another cache", "They are served, only more slowly"}
sim.StopAt 4s
# package api
Get has
set Key to ""
set Id to 0
set Start to 0s
Lookup has
set Key to ""
set Id to 0
CacheReply has
set Key to ""
set Id to 0
set Value to ""
set Found to false
Query has
set Key to ""
set Id to 0
Rows has
set Key to ""
set Id to 0
set Value to ""
Fill has
set Key to ""
set Value to ""
Served has
set Id to 0
set Hit to false
set Latency to 0s
# package app
use github.com/synodlang/examples/webapp/api
use std/maps
use std/net
use std/sim
use std/time
Node has
set Cache to sim.Nobody
set Db to sim.Nobody
set pending to {}
define Wire [n: Node] to [c: node] and [d: node]
set n.Cache to c
set n.Db to d
define [n: Node] Finish [r: api.Get] hit [h: bool]
set n.pending to (maps.Without r.Id from n.pending)
emit (message api.Served with Id r.Id, Hit h, Latency (time.Now minus r.Start)) by n
# Cache-aside: ask the cache first
when [n: Node] (net.Received api.Get from balancer)
set item api.Get.Id of n.pending to api.Get
net.Send (message api.Lookup with Key api.Get.Key, Id api.Get.Id) from n to n.Cache
# On a miss, query the database
when [n: Node] (net.Received api.CacheReply from cache)
set request to (item api.CacheReply.Id of n.pending)
if api.CacheReply.Found
n.Finish request hit true
else
net.Send (message api.Query with Key request.Key, Id request.Id) from n to n.Db
# Fill the cache with what the database returned
when [n: Node] (net.Received api.Rows from db)
set request to (item api.Rows.Id of n.pending)
net.Send (message api.Fill with Key api.Rows.Key, Value api.Rows.Value) from n to n.Cache
n.Finish request hit false
# package cache
use github.com/synodlang/examples/webapp/api
use std/net
Node has
set Entries to {}
when [n: Node] (net.Received api.Lookup from asker)
set k to api.Lookup.Key
if (k is in n.Entries)
net.Send (message api.CacheReply with Key k, Id api.Lookup.Id, Value (item k of n.Entries), Found true) from n to asker
else
net.Send (message api.CacheReply with Key k, Id api.Lookup.Id) from n to asker
when [n: Node] (net.Received api.Fill from filler)
set item api.Fill.Key of n.Entries to api.Fill.Value
# package db
use github.com/synodlang/examples/webapp/api
use std/net
use std/text
Node has
set Queries to 0
# The database is slow: every query takes 40ms of work
when [n: Node] (net.Received api.Query from asker)
set n.Queries to (n.Queries plus 1)
after 40ms
net.Send (message api.Rows with Key api.Query.Key, Id api.Query.Id, Value (text.Join {"row for ", api.Query.Key})) from n to asker
# package lb
use github.com/synodlang/examples/webapp/api
use std/lists
use std/net
use std/sim
Node has
set Backends to {}
set next to 0
define Balance [servers: list of node] on [n: Node]
set n.Backends to servers
# Round robin over the application servers
when [n: Node] (sim.Delivered api.Get)
net.Send api.Get from n to (item n.next of n.Backends)
set n.next to (n.next plus 1)
if (n.next is (lists.Size n.Backends))
set n.next to 0
On your own machine: synod run ./webapp. Source on GitHub · all examples