Skip to content

Commit

Permalink
Add query cache
Browse files Browse the repository at this point in the history
This commit adds a query cache with a configurable maximum size.
Past this size, queries are evicted from the cache on an LRU basis.

The default cache size is 1000, chosen fairly arbitrarily. If the size
is configured with a non-positive value, then the cache is disabled.

Also ran `dep ensure` to add the new dependency to `Gopkg.lock`.
  • Loading branch information
edsrzf committed Aug 26, 2018
1 parent 2dcb2dd commit 78c5707
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 11 deletions.
26 changes: 19 additions & 7 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 37 additions & 4 deletions handler/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/99designs/gqlgen/graphql"
"github.com/gorilla/websocket"
"github.com/hashicorp/golang-lru"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
"github.com/vektah/gqlparser/gqlerror"
Expand All @@ -23,6 +24,7 @@ type params struct {
}

type Config struct {
cacheSize int
upgrader websocket.Upgrader
recover graphql.RecoverFunc
errorPresenter graphql.ErrorPresenterFunc
Expand Down Expand Up @@ -110,8 +112,19 @@ func RequestMiddleware(middleware graphql.RequestMiddleware) Option {
}
}

// CacheSize sets the maximum size of the query cache.
// If size is less than or equal to 0, the cache is disabled.
func CacheSize(size int) Option {
return func(cfg *Config) {
cfg.cacheSize = size
}
}

const DefaultCacheSize = 1000

func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc {
cfg := Config{
cacheSize: DefaultCacheSize,
upgrader: websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
Expand All @@ -122,6 +135,13 @@ func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc
option(&cfg)
}

var cache *lru.Cache
if cfg.cacheSize > 0 {
// An error is only returned for non-positive cache size
// and we already checked for that.
cache, _ = lru.New(DefaultCacheSize)
}

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions {
w.Header().Set("Allow", "OPTIONS, GET, POST")
Expand Down Expand Up @@ -157,10 +177,23 @@ func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc
}
w.Header().Set("Content-Type", "application/json")

doc, qErr := gqlparser.LoadQuery(exec.Schema(), reqParams.Query)
if len(qErr) > 0 {
sendError(w, http.StatusUnprocessableEntity, qErr...)
return
var doc *ast.QueryDocument
if cache != nil {
val, ok := cache.Get(reqParams.Query)
if ok {
doc = val.(*ast.QueryDocument)
}
}
if doc == nil {
var qErr gqlerror.List
doc, qErr = gqlparser.LoadQuery(exec.Schema(), reqParams.Query)
if len(qErr) > 0 {
sendError(w, http.StatusUnprocessableEntity, qErr...)
return
}
if cache != nil {
cache.Add(reqParams.Query, doc)
}
}

op := doc.Operations.ForName(reqParams.OperationName)
Expand Down

0 comments on commit 78c5707

Please sign in to comment.