mirror of
https://github.com/prometheus/prometheus
synced 2025-03-01 03:02:26 +00:00
remote/read_handler: pool input to Marshal() (#11357)
* remote/read_handler: pool input to Marshal() Use a sync.Pool to reuse byte slices between calls to Marshal() in the remote read handler. Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> * remote: add microbenchmark for remote read handler Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com>
This commit is contained in:
parent
8553a98267
commit
d1d2566055
@ -13,5 +13,22 @@
|
|||||||
|
|
||||||
package prompb
|
package prompb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
func (m Sample) T() int64 { return m.Timestamp }
|
func (m Sample) T() int64 { return m.Timestamp }
|
||||||
func (m Sample) V() float64 { return m.Value }
|
func (m Sample) V() float64 { return m.Value }
|
||||||
|
|
||||||
|
func (r *ChunkedReadResponse) PooledMarshal(p *sync.Pool) ([]byte, error) {
|
||||||
|
size := r.Size()
|
||||||
|
data, ok := p.Get().(*[]byte)
|
||||||
|
if ok && cap(*data) >= size {
|
||||||
|
n, err := r.MarshalToSizedBuffer((*data)[:size])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return (*data)[:n], nil
|
||||||
|
}
|
||||||
|
return r.Marshal()
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/golang/snappy"
|
"github.com/golang/snappy"
|
||||||
@ -193,6 +194,7 @@ func StreamChunkedReadResponses(
|
|||||||
ss storage.ChunkSeriesSet,
|
ss storage.ChunkSeriesSet,
|
||||||
sortedExternalLabels []prompb.Label,
|
sortedExternalLabels []prompb.Label,
|
||||||
maxBytesInFrame int,
|
maxBytesInFrame int,
|
||||||
|
marshalPool *sync.Pool,
|
||||||
) (storage.Warnings, error) {
|
) (storage.Warnings, error) {
|
||||||
var (
|
var (
|
||||||
chks []prompb.Chunk
|
chks []prompb.Chunk
|
||||||
@ -234,12 +236,14 @@ func StreamChunkedReadResponses(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := proto.Marshal(&prompb.ChunkedReadResponse{
|
resp := &prompb.ChunkedReadResponse{
|
||||||
ChunkedSeries: []*prompb.ChunkedSeries{
|
ChunkedSeries: []*prompb.ChunkedSeries{
|
||||||
{Labels: lbls, Chunks: chks},
|
{Labels: lbls, Chunks: chks},
|
||||||
},
|
},
|
||||||
QueryIndex: queryIndex,
|
QueryIndex: queryIndex,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
b, err := resp.PooledMarshal(marshalPool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ss.Warnings(), fmt.Errorf("marshal ChunkedReadResponse: %w", err)
|
return ss.Warnings(), fmt.Errorf("marshal ChunkedReadResponse: %w", err)
|
||||||
}
|
}
|
||||||
@ -247,6 +251,9 @@ func StreamChunkedReadResponses(
|
|||||||
if _, err := stream.Write(b); err != nil {
|
if _, err := stream.Write(b); err != nil {
|
||||||
return ss.Warnings(), fmt.Errorf("write to stream: %w", err)
|
return ss.Warnings(), fmt.Errorf("write to stream: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We immediately flush the Write() so it is safe to return to the pool.
|
||||||
|
marshalPool.Put(&b)
|
||||||
chks = chks[:0]
|
chks = chks[:0]
|
||||||
}
|
}
|
||||||
if err := iter.Err(); err != nil {
|
if err := iter.Err(); err != nil {
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/go-kit/log/level"
|
"github.com/go-kit/log/level"
|
||||||
@ -37,6 +38,7 @@ type readHandler struct {
|
|||||||
remoteReadMaxBytesInFrame int
|
remoteReadMaxBytesInFrame int
|
||||||
remoteReadGate *gate.Gate
|
remoteReadGate *gate.Gate
|
||||||
queries prometheus.Gauge
|
queries prometheus.Gauge
|
||||||
|
marshalPool *sync.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReadHandler creates a http.Handler that accepts remote read requests and
|
// NewReadHandler creates a http.Handler that accepts remote read requests and
|
||||||
@ -49,6 +51,7 @@ func NewReadHandler(logger log.Logger, r prometheus.Registerer, queryable storag
|
|||||||
remoteReadSampleLimit: remoteReadSampleLimit,
|
remoteReadSampleLimit: remoteReadSampleLimit,
|
||||||
remoteReadGate: gate.New(remoteReadConcurrencyLimit),
|
remoteReadGate: gate.New(remoteReadConcurrencyLimit),
|
||||||
remoteReadMaxBytesInFrame: remoteReadMaxBytesInFrame,
|
remoteReadMaxBytesInFrame: remoteReadMaxBytesInFrame,
|
||||||
|
marshalPool: &sync.Pool{},
|
||||||
|
|
||||||
queries: prometheus.NewGauge(prometheus.GaugeOpts{
|
queries: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
Namespace: "prometheus",
|
Namespace: "prometheus",
|
||||||
@ -225,6 +228,7 @@ func (h *readHandler) remoteReadStreamedXORChunks(ctx context.Context, w http.Re
|
|||||||
querier.Select(true, hints, filteredMatchers...),
|
querier.Select(true, hints, filteredMatchers...),
|
||||||
sortedExternalLabels,
|
sortedExternalLabels,
|
||||||
h.remoteReadMaxBytesInFrame,
|
h.remoteReadMaxBytesInFrame,
|
||||||
|
h.marshalPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -107,6 +107,73 @@ func TestSampledReadEndpoint(t *testing.T) {
|
|||||||
}, resp.Results[0])
|
}, resp.Results[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkStreamReadEndpoint(b *testing.B) {
|
||||||
|
suite, err := promql.NewTest(b, `
|
||||||
|
load 1m
|
||||||
|
test_metric1{foo="bar1",baz="qux"} 0+100x119
|
||||||
|
test_metric1{foo="bar2",baz="qux"} 0+100x120
|
||||||
|
test_metric1{foo="bar3",baz="qux"} 0+100x240
|
||||||
|
`)
|
||||||
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
defer suite.Close()
|
||||||
|
|
||||||
|
require.NoError(b, suite.Run())
|
||||||
|
|
||||||
|
api := NewReadHandler(nil, nil, suite.Storage(), func() config.Config {
|
||||||
|
return config.Config{}
|
||||||
|
},
|
||||||
|
0, 1, 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
matcher, err := labels.NewMatcher(labels.MatchEqual, "__name__", "test_metric1")
|
||||||
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
query, err := ToQuery(0, 14400001, []*labels.Matcher{matcher}, &storage.SelectHints{
|
||||||
|
Step: 1,
|
||||||
|
Func: "sum",
|
||||||
|
Start: 0,
|
||||||
|
End: 14400001,
|
||||||
|
})
|
||||||
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
req := &prompb.ReadRequest{
|
||||||
|
Queries: []*prompb.Query{query},
|
||||||
|
AcceptedResponseTypes: []prompb.ReadRequest_ResponseType{prompb.ReadRequest_STREAMED_XOR_CHUNKS},
|
||||||
|
}
|
||||||
|
data, err := proto.Marshal(req)
|
||||||
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
compressed := snappy.Encode(nil, data)
|
||||||
|
request, err := http.NewRequest("POST", "", bytes.NewBuffer(compressed))
|
||||||
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
api.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
require.Equal(b, 2, recorder.Code/100)
|
||||||
|
|
||||||
|
var results []*prompb.ChunkedReadResponse
|
||||||
|
stream := NewChunkedReader(recorder.Result().Body, DefaultChunkedReadLimit, nil)
|
||||||
|
|
||||||
|
for {
|
||||||
|
res := &prompb.ChunkedReadResponse{}
|
||||||
|
err := stream.NextProto(res)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
require.NoError(b, err)
|
||||||
|
results = append(results, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(b, 6, len(results), "Expected 6 results.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStreamReadEndpoint(t *testing.T) {
|
func TestStreamReadEndpoint(t *testing.T) {
|
||||||
// First with 120 samples. We expect 1 frame with 1 chunk.
|
// First with 120 samples. We expect 1 frame with 1 chunk.
|
||||||
// Second with 121 samples, We expect 1 frame with 2 chunks.
|
// Second with 121 samples, We expect 1 frame with 2 chunks.
|
||||||
|
Loading…
Reference in New Issue
Block a user