Compare commits

..
2 Commits
Author SHA1 Message Date
lukaszraczylo 42170f9b8e fixup! Revamp the query compilation process. 2024-05-14 11:30:42 +01:00
lukaszraczylo 4f438b7eac Revamp the query compilation process. 2024-05-14 11:18:31 +01:00
4 changed files with 121 additions and 26 deletions
+3 -3
View File
@@ -14,11 +14,11 @@ type CacheEntry struct {
}
type Cache struct {
entries sync.Map
globalTTL time.Duration
compressPool sync.Pool
decompressPool sync.Pool
sync.RWMutex // Reintroduced to provide lock methods
entries sync.Map
globalTTL time.Duration
sync.RWMutex
}
func New(globalTTL time.Duration) *Cache {
+22 -5
View File
@@ -7,14 +7,32 @@ import (
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/avast/retry-go/v4"
"github.com/goccy/go-json"
)
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func (qe *QueryExecutor) executeQuery() ([]byte, error) {
httpRequest, err := http.NewRequest(http.MethodPost, qe.endpoint, bytes.NewBuffer(qe.Query))
// Reuse buffer from pool to avoid allocations
buf := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buf)
buf.Reset()
_, err := buf.Write(qe.Query)
if err != nil {
qe.Logger.Error("Can't write to buffer", map[string]interface{}{"error": err})
return nil, err
}
httpRequest, err := http.NewRequest(http.MethodPost, qe.endpoint, buf)
if err != nil {
qe.Logger.Error("Can't create HTTP request", map[string]interface{}{"error": err})
return nil, err
@@ -54,15 +72,14 @@ func (qe *QueryExecutor) executeQuery() ([]byte, error) {
var reader io.ReadCloser
encoding := httpResponse.Header.Get("Content-Encoding")
switch encoding {
case "gzip":
if encoding == "gzip" {
reader, err = gzip.NewReader(httpResponse.Body)
if err != nil {
qe.Logger.Debug("Error creating gzip reader", map[string]interface{}{"error": err.Error()})
return fmt.Errorf("error creating gzip reader: %w", err)
}
defer reader.Close()
default:
} else {
reader = httpResponse.Body
}
@@ -81,7 +98,7 @@ func (qe *QueryExecutor) executeQuery() ([]byte, error) {
return nil
},
retry.OnRetry(func(n uint, err error) {
qe.Logger.Warning("Retrying query", map[string]interface{}{"error": err.Error(), "attempt": n})
qe.Logger.Warning("Retrying query", map[string]interface{}{"error": err.Error(), "attempt": n + 1})
}),
retry.Attempts(uint(retriesMax)),
retry.DelayType(retry.BackOffDelay),
+34 -15
View File
@@ -2,37 +2,56 @@ package gql
import (
"fmt"
"sync"
"github.com/goccy/go-json"
"github.com/gookit/goutil"
"github.com/gookit/goutil/strutil"
)
var jsonBufferPool = sync.Pool{
New: func() interface{} {
return new([]byte)
},
}
func (b *BaseClient) convertToJSON(v any) []byte {
jsonBuffer := jsonBufferPool.Get().(*[]byte)
defer jsonBufferPool.Put(jsonBuffer)
*jsonBuffer = (*jsonBuffer)[:0]
jsonData, err := json.Marshal(v)
if err != nil {
b.Logger.Error("Can't convert to JSON", map[string]interface{}{"error": err.Error()})
return nil
}
return jsonData
*jsonBuffer = append(*jsonBuffer, jsonData...)
return *jsonBuffer
}
func (b *BaseClient) compileQuery(queryPartials ...any) *Query {
q := &Query{}
var query string
var variables map[string]interface{}
for _, partial := range queryPartials {
switch val := partial.(type) {
case string:
q.Query = val
query = val
case map[string]interface{}:
q.Variables = val
variables = val
}
}
if q.Query == "" {
if query == "" {
b.Logger.Error("Can't compile query", map[string]interface{}{"error": "query is empty"})
return nil
}
q.JsonQuery = b.convertToJSON(q)
return q
jsonQuery := b.convertToJSON(&Query{Query: query, Variables: variables})
return &Query{
Query: query,
Variables: variables,
JsonQuery: jsonQuery,
}
}
func (b *BaseClient) Query(query string, variables map[string]interface{}, headers map[string]interface{}) (any, error) {
@@ -52,8 +71,7 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade
if (enableCache || b.cache_global) && strutil.HasPrefix(compiledQuery.Query, "query") {
b.Logger.Debug("Cache enabled", nil)
queryHash = calculateHash(compiledQuery)
cachedValue := b.cacheLookup(queryHash)
if cachedValue != nil {
if cachedValue := b.cacheLookup(queryHash); cachedValue != nil {
b.Logger.Debug("Cache hit", map[string]interface{}{"query": compiledQuery})
return cachedValue, nil
}
@@ -76,7 +94,6 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade
}(),
Retries: enableRetries || b.retries_enable,
}
defer func() { q = nil }()
rv, err := q.executeQuery()
if err != nil {
@@ -84,8 +101,7 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade
return nil, err
}
returnedValue, err := q.decodeResponse(rv)
return returnedValue, err
return q.decodeResponse(rv)
}
func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enableCache, enableRetries, recompileRequired bool) {
@@ -97,9 +113,12 @@ func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enable
varEnableRetries, _ := goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries"))
enableCache = enableCache || varEnableCache
enableRetries = enableRetries || varEnableRetries
delete(q.Variables, "gqlcache")
delete(q.Variables, "gqlretries")
recompileRequired = true
if varEnableCache || varEnableRetries {
delete(q.Variables, "gqlcache")
delete(q.Variables, "gqlretries")
recompileRequired = true
}
}
return enableCache, enableRetries, recompileRequired
+62 -3
View File
@@ -1,6 +1,8 @@
package gql
import "testing"
import (
"testing"
)
func Benchmark_compileQuery(bn *testing.B) {
b := NewConnection()
@@ -12,12 +14,69 @@ func Benchmark_compileQuery(bn *testing.B) {
}
}
func Benchmark_convertToJson(bn *testing.B) {
func Benchmark_compileQueryWithVariables(bn *testing.B) {
b := NewConnection()
variables := map[string]interface{}{
"var1": "value1",
}
bn.ResetTimer()
for i := 0; i < bn.N; i++ {
b.convertToJSON(
b.compileQuery(
`query { viewer { login } }`,
variables,
)
}
}
func Benchmark_convertToJson(bn *testing.B) {
b := NewConnection()
query := `query { viewer { login } }`
bn.ResetTimer()
for i := 0; i < bn.N; i++ {
b.convertToJSON(query)
}
}
func Benchmark_Query(bn *testing.B) {
b := NewConnection()
query := `query { viewer { login } }`
variables := map[string]interface{}{
"var1": "value1",
}
headers := map[string]interface{}{
"Authorization": "Bearer token",
"gqlcache": "true",
}
bn.ResetTimer()
for i := 0; i < bn.N; i++ {
_, _ = b.Query(query, variables, headers)
}
}
func Benchmark_QueryNoCacheNoRetry(bn *testing.B) {
b := NewConnection()
query := `query { viewer { login } }`
variables := map[string]interface{}{
"var1": "value1",
}
headers := map[string]interface{}{
"Authorization": "Bearer token",
}
bn.ResetTimer()
for i := 0; i < bn.N; i++ {
_, _ = b.Query(query, variables, headers)
}
}
func Benchmark_QueryWithEmptyVariables(bn *testing.B) {
b := NewConnection()
query := `query { viewer { login } }`
headers := map[string]interface{}{
"Authorization": "Bearer token",
"gqlcache": "true",
}
bn.ResetTimer()
for i := 0; i < bn.N; i++ {
_, _ = b.Query(query, nil, headers)
}
}