├── topology.png ├── dbserver ├── .DS_Store └── dbserver.go ├── api └── api.go ├── .gitignore ├── slowdb └── slowdb.go ├── client └── client.go ├── README.markdown ├── cli └── cli.go └── frontend └── frontend.go /topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capotej/groupcache-db-experiment/HEAD/topology.png -------------------------------------------------------------------------------- /dbserver/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capotej/groupcache-db-experiment/HEAD/dbserver/.DS_Store -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | //a "Load" rpc instruction 4 | type Load struct { 5 | Key string 6 | } 7 | 8 | //a "Store" rpc instruction 9 | type Store struct { 10 | Key string 11 | Value string 12 | } 13 | 14 | type NullResult int 15 | 16 | type ValueResult struct { 17 | Value string 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | groupcachedb 25 | cli/cli 26 | dbserver/dbserver 27 | frontend/frontend 28 | -------------------------------------------------------------------------------- /slowdb/slowdb.go: -------------------------------------------------------------------------------- 1 | package slowdb 2 | 3 | // A purposefully slow key/value database 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | type SlowDB struct { 11 | data map[string]string 12 | } 13 | 14 | func (db *SlowDB) Get(key string) string { 15 | time.Sleep(time.Duration(300) * time.Millisecond) 16 | fmt.Printf("getting %s\n", key) 17 | return db.data[key] 18 | } 19 | 20 | func (db *SlowDB) Set(key string, value string) { 21 | fmt.Printf("setting %s to %s\n", key, value) 22 | db.data[key] = value 23 | } 24 | 25 | func NewSlowDB() *SlowDB { 26 | ndb := new(SlowDB) 27 | ndb.data = make(map[string]string) 28 | return ndb 29 | } 30 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // Client for dbserver/slowdb 4 | 5 | import ( 6 | "fmt" 7 | "github.com/capotej/groupcache-db-experiment/api" 8 | "net/rpc" 9 | ) 10 | 11 | type Client struct{} 12 | 13 | func (c *Client) Get(key string) string { 14 | client, err := rpc.DialHTTP("tcp", "localhost:8080") 15 | if err != nil { 16 | fmt.Printf("error %s", err) 17 | } 18 | args := &api.Load{key} 19 | var reply api.ValueResult 20 | err = client.Call("Server.Get", args, &reply) 21 | if err != nil { 22 | fmt.Printf("error %s", err) 23 | } 24 | return string(reply.Value) 25 | } 26 | 27 | func (c *Client) Set(key string, value string) { 28 | client, err := rpc.DialHTTP("tcp", "localhost:8080") 29 | if err != nil { 30 | fmt.Printf("error %s", err) 31 | } 32 | args := &api.Store{key, value} 33 | var reply int 34 | err = client.Call("Server.Set", args, &reply) 35 | if err != nil { 36 | fmt.Printf("error %s", err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /dbserver/dbserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // This is the rpc server that fronts slowdb 4 | 5 | import ( 6 | "fmt" 7 | "github.com/capotej/groupcache-db-experiment/api" 8 | "github.com/capotej/groupcache-db-experiment/slowdb" 9 | "net" 10 | "net/http" 11 | "net/rpc" 12 | ) 13 | 14 | type Server struct { 15 | db *slowdb.SlowDB 16 | } 17 | 18 | func (s *Server) Get(args *api.Load, reply *api.ValueResult) error { 19 | data := s.db.Get(args.Key) 20 | reply.Value = string(data) 21 | return nil 22 | } 23 | 24 | func (s *Server) Set(args *api.Store, reply *api.NullResult) error { 25 | s.db.Set(args.Key, args.Value) 26 | *reply = 0 27 | return nil 28 | } 29 | 30 | func NewServer(db *slowdb.SlowDB) *Server { 31 | server := new(Server) 32 | server.db = db 33 | return server 34 | } 35 | 36 | func (s *Server) Start(port string) { 37 | 38 | rpc.Register(s) 39 | 40 | rpc.HandleHTTP() 41 | l, e := net.Listen("tcp", port) 42 | if e != nil { 43 | fmt.Println("fatal") 44 | } 45 | 46 | http.Serve(l, nil) 47 | } 48 | 49 | func main() { 50 | db := slowdb.NewSlowDB() 51 | server := NewServer(db) 52 | fmt.Println("dbserver starting on localhost:8080") 53 | server.Start(":8080") 54 | } 55 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # groupcache-db-experiment 2 | This project simulates a scenario wherein a few frontends running [groupcache](http://github.com/golang/groupcache) are fronting a slow database. See my [blog post](http://www.capotej.com/blog/2013/07/28/playing-with-groupcache/) about it for more details. 3 | 4 | # Getting it running 5 | The following commands will set up this topology: 6 | ![groupcache topology](https://raw.github.com/capotej/groupcache-db-experiment/master/topology.png) 7 | 8 | ### Build everything 9 | 10 | 1. ```git clone git@github.com:capotej/groupcache-db-experiment``` 11 | 2. ```sh build.sh``` 12 | 13 | ### Start DB server 14 | 15 | 1. ```cd dbserver && ./dbserver``` 16 | 17 | This starts a delibrately slow k/v datastore on :8080 18 | 19 | ### Start Multiple Frontends 20 | 21 | 1. ```cd frontend``` 22 | 2. ```./frontend -port 8001``` 23 | 3. ```./frontend -port 8002``` 24 | 4. ```./frontend -port 8003``` 25 | 26 | ### Use the CLI to set/get values 27 | 28 | 1. ```cd cli``` 29 | 2. ```./cli -set -key foo -value bar``` 30 | 3. ```./cli -get -key foo``` should see bar in 300 ms 31 | 4. ```./cli -cget -key foo``` should see bar in 300ms (cache is loaded) 32 | 5. ```./cli -cget -key foo``` should see bar instantly 33 | -------------------------------------------------------------------------------- /cli/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/capotej/groupcache-db-experiment/api" 7 | "github.com/capotej/groupcache-db-experiment/client" 8 | "net/rpc" 9 | ) 10 | 11 | func main() { 12 | 13 | var port = flag.String("port", "9001", "frontend port") 14 | var set = flag.Bool("set", false, "doing a set?") 15 | var get = flag.Bool("get", false, "doing a get?") 16 | var cget = flag.Bool("cget", false, "doing a get?") 17 | var key = flag.String("key", "foo", "key to get") 18 | var value = flag.String("value", "bar", "value to set") 19 | flag.Parse() 20 | 21 | client := new(client.Client) 22 | if *cget { 23 | client, err := rpc.DialHTTP("tcp", "localhost:"+*port) 24 | if err != nil { 25 | fmt.Printf("error %s", err) 26 | } 27 | args := &api.Load{*key} 28 | var reply api.ValueResult 29 | err = client.Call("Frontend.Get", args, &reply) 30 | if err != nil { 31 | fmt.Printf("error %s", err) 32 | } 33 | fmt.Println(string(reply.Value)) 34 | return 35 | } 36 | 37 | if *get { 38 | var reply = client.Get(*key) 39 | fmt.Println(reply) 40 | return 41 | } 42 | 43 | if *set { 44 | client.Set(*key, *value) 45 | return 46 | } 47 | 48 | flag.PrintDefaults() 49 | return 50 | 51 | } 52 | -------------------------------------------------------------------------------- /frontend/frontend.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // This represents a cache front end server, that front slowdb/slowserver requests 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "github.com/capotej/groupcache-db-experiment/api" 9 | "github.com/capotej/groupcache-db-experiment/client" 10 | "github.com/golang/groupcache" 11 | "net" 12 | "net/http" 13 | "net/rpc" 14 | "os" 15 | "strconv" 16 | ) 17 | 18 | type Frontend struct { 19 | cacheGroup *groupcache.Group 20 | } 21 | 22 | func (s *Frontend) Get(args *api.Load, reply *api.ValueResult) error { 23 | var data []byte 24 | fmt.Printf("cli asked for %s from groupcache\n", args.Key) 25 | err := s.cacheGroup.Get(nil, args.Key, 26 | groupcache.AllocatingByteSliceSink(&data)) 27 | 28 | reply.Value = string(data) 29 | return err 30 | } 31 | 32 | func NewServer(cacheGroup *groupcache.Group) *Frontend { 33 | server := new(Frontend) 34 | server.cacheGroup = cacheGroup 35 | return server 36 | } 37 | 38 | func (s *Frontend) Start(port string) { 39 | 40 | rpc.Register(s) 41 | 42 | rpc.HandleHTTP() 43 | l, e := net.Listen("tcp", port) 44 | if e != nil { 45 | fmt.Println("fatal") 46 | } 47 | 48 | http.Serve(l, nil) 49 | } 50 | 51 | func main() { 52 | 53 | var port = flag.String("port", "8001", "groupcache port") 54 | flag.Parse() 55 | 56 | peers := groupcache.NewHTTPPool("http://localhost:" + *port) 57 | 58 | client := new(client.Client) 59 | 60 | var stringcache = groupcache.NewGroup("SlowDBCache", 64<<20, groupcache.GetterFunc( 61 | func(ctx groupcache.Context, key string, dest groupcache.Sink) error { 62 | result := client.Get(key) 63 | fmt.Printf("asking for %s from dbserver\n", key) 64 | dest.SetBytes([]byte(result)) 65 | return nil 66 | })) 67 | 68 | peers.Set("http://localhost:8001", "http://localhost:8002", "http://localhost:8003") 69 | 70 | frontendServer := NewServer(stringcache) 71 | 72 | i, err := strconv.Atoi(*port) 73 | if err != nil { 74 | // handle error 75 | fmt.Println(err) 76 | os.Exit(2) 77 | } 78 | var frontEndport = ":" + strconv.Itoa(i+1000) 79 | go frontendServer.Start(frontEndport) 80 | 81 | fmt.Println(stringcache) 82 | fmt.Println("cachegroup slave starting on " + *port) 83 | fmt.Println("frontend starting on " + frontEndport) 84 | http.ListenAndServe("127.0.0.1:"+*port, http.HandlerFunc(peers.ServeHTTP)) 85 | 86 | } 87 | --------------------------------------------------------------------------------