mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-26 06:04:05 -08:00
Improve chunk and chunkDesc loading.
Also, clean up some things in the code (especially introduction of the chunkLenWithHeader constant to avoid the same expression all over the place). Benchmark results: BEFORE BenchmarkLoadChunksSequentially 5000 283580 ns/op 152143 B/op 312 allocs/op BenchmarkLoadChunksRandomly 20000 82936 ns/op 39310 B/op 99 allocs/op BenchmarkLoadChunkDescs 10000 110833 ns/op 15092 B/op 345 allocs/op AFTER BenchmarkLoadChunksSequentially 10000 146785 ns/op 152285 B/op 315 allocs/op BenchmarkLoadChunksRandomly 20000 67598 ns/op 39438 B/op 103 allocs/op BenchmarkLoadChunkDescs 20000 99631 ns/op 12636 B/op 192 allocs/op Note that everything is obviously loaded from the page cache (as the benchmark runs thousands of times with very small series files). In a real-world scenario, I expect a larger impact, as the disk operations will more often actually hit the disk. To load ~50 sequential chunks, this reduces the iops from 100 seeks and 100 reads to 1 seek and 1 read.
This commit is contained in:
parent
c563398c68
commit
b02d900e61
|
@ -192,6 +192,7 @@ type chunk interface {
|
||||||
newIterator() chunkIterator
|
newIterator() chunkIterator
|
||||||
marshal(io.Writer) error
|
marshal(io.Writer) error
|
||||||
unmarshal(io.Reader) error
|
unmarshal(io.Reader) error
|
||||||
|
unmarshalFromBuf([]byte)
|
||||||
encoding() chunkEncoding
|
encoding() chunkEncoding
|
||||||
// values returns a channel, from which all sample values in the chunk
|
// values returns a channel, from which all sample values in the chunk
|
||||||
// can be received in order. The channel is closed after the last
|
// can be received in order. The channel is closed after the last
|
||||||
|
|
|
@ -191,8 +191,8 @@ func (p *persistence) sanitizeSeries(
|
||||||
return fp, false
|
return fp, false
|
||||||
}
|
}
|
||||||
|
|
||||||
bytesToTrim := fi.Size() % int64(chunkLen+chunkHeaderLen)
|
bytesToTrim := fi.Size() % int64(chunkLenWithHeader)
|
||||||
chunksInFile := int(fi.Size()) / (chunkLen + chunkHeaderLen)
|
chunksInFile := int(fi.Size()) / chunkLenWithHeader
|
||||||
modTime := fi.ModTime()
|
modTime := fi.ModTime()
|
||||||
if bytesToTrim != 0 {
|
if bytesToTrim != 0 {
|
||||||
glog.Warningf(
|
glog.Warningf(
|
||||||
|
|
|
@ -223,18 +223,20 @@ func (c deltaEncodedChunk) marshal(w io.Writer) error {
|
||||||
// unmarshal implements chunk.
|
// unmarshal implements chunk.
|
||||||
func (c *deltaEncodedChunk) unmarshal(r io.Reader) error {
|
func (c *deltaEncodedChunk) unmarshal(r io.Reader) error {
|
||||||
*c = (*c)[:cap(*c)]
|
*c = (*c)[:cap(*c)]
|
||||||
readBytes := 0
|
if _, err := io.ReadFull(r, *c); err != nil {
|
||||||
for readBytes < len(*c) {
|
|
||||||
n, err := r.Read((*c)[readBytes:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
readBytes += n
|
|
||||||
}
|
|
||||||
*c = (*c)[:binary.LittleEndian.Uint16((*c)[deltaHeaderBufLenOffset:])]
|
*c = (*c)[:binary.LittleEndian.Uint16((*c)[deltaHeaderBufLenOffset:])]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unmarshalFromBuf implements chunk.
|
||||||
|
func (c *deltaEncodedChunk) unmarshalFromBuf(buf []byte) {
|
||||||
|
*c = (*c)[:cap(*c)]
|
||||||
|
copy(*c, buf)
|
||||||
|
*c = (*c)[:binary.LittleEndian.Uint16((*c)[deltaHeaderBufLenOffset:])]
|
||||||
|
}
|
||||||
|
|
||||||
// values implements chunk.
|
// values implements chunk.
|
||||||
func (c deltaEncodedChunk) values() <-chan *metric.SamplePair {
|
func (c deltaEncodedChunk) values() <-chan *metric.SamplePair {
|
||||||
n := c.len()
|
n := c.len()
|
||||||
|
|
|
@ -231,18 +231,20 @@ func (c doubleDeltaEncodedChunk) marshal(w io.Writer) error {
|
||||||
// unmarshal implements chunk.
|
// unmarshal implements chunk.
|
||||||
func (c *doubleDeltaEncodedChunk) unmarshal(r io.Reader) error {
|
func (c *doubleDeltaEncodedChunk) unmarshal(r io.Reader) error {
|
||||||
*c = (*c)[:cap(*c)]
|
*c = (*c)[:cap(*c)]
|
||||||
readBytes := 0
|
if _, err := io.ReadFull(r, *c); err != nil {
|
||||||
for readBytes < len(*c) {
|
|
||||||
n, err := r.Read((*c)[readBytes:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
readBytes += n
|
|
||||||
}
|
|
||||||
*c = (*c)[:binary.LittleEndian.Uint16((*c)[doubleDeltaHeaderBufLenOffset:])]
|
*c = (*c)[:binary.LittleEndian.Uint16((*c)[doubleDeltaHeaderBufLenOffset:])]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unmarshalFromBuf implements chunk.
|
||||||
|
func (c *doubleDeltaEncodedChunk) unmarshalFromBuf(buf []byte) {
|
||||||
|
*c = (*c)[:cap(*c)]
|
||||||
|
copy(*c, buf)
|
||||||
|
*c = (*c)[:binary.LittleEndian.Uint16((*c)[doubleDeltaHeaderBufLenOffset:])]
|
||||||
|
}
|
||||||
|
|
||||||
// values implements chunk.
|
// values implements chunk.
|
||||||
func (c doubleDeltaEncodedChunk) values() <-chan *metric.SamplePair {
|
func (c doubleDeltaEncodedChunk) values() <-chan *metric.SamplePair {
|
||||||
n := c.len()
|
n := c.len()
|
||||||
|
|
|
@ -63,13 +63,19 @@ const (
|
||||||
chunkHeaderTypeOffset = 0
|
chunkHeaderTypeOffset = 0
|
||||||
chunkHeaderFirstTimeOffset = 1
|
chunkHeaderFirstTimeOffset = 1
|
||||||
chunkHeaderLastTimeOffset = 9
|
chunkHeaderLastTimeOffset = 9
|
||||||
|
chunkLenWithHeader = chunkLen + chunkHeaderLen
|
||||||
|
chunkMaxBatchSize = 64 // How many chunks to load at most in one batch.
|
||||||
|
|
||||||
indexingMaxBatchSize = 1024 * 1024
|
indexingMaxBatchSize = 1024 * 1024
|
||||||
indexingBatchTimeout = 500 * time.Millisecond // Commit batch when idle for that long.
|
indexingBatchTimeout = 500 * time.Millisecond // Commit batch when idle for that long.
|
||||||
indexingQueueCapacity = 1024 * 16
|
indexingQueueCapacity = 1024 * 16
|
||||||
)
|
)
|
||||||
|
|
||||||
var fpLen = len(clientmodel.Fingerprint(0).String()) // Length of a fingerprint as string.
|
var (
|
||||||
|
fpLen = len(clientmodel.Fingerprint(0).String()) // Length of a fingerprint as string.
|
||||||
|
|
||||||
|
byteBufPool = sync.Pool{New: func() interface{} { return make([]byte, 0, 3*chunkLenWithHeader) }}
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
flagHeadChunkPersisted byte = 1 << iota
|
flagHeadChunkPersisted byte = 1 << iota
|
||||||
|
@ -377,29 +383,38 @@ func (p *persistence) loadChunks(fp clientmodel.Fingerprint, indexes []int, inde
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
chunks := make([]chunk, 0, len(indexes))
|
chunks := make([]chunk, 0, len(indexes))
|
||||||
typeBuf := make([]byte, 1)
|
buf := byteBufPool.Get().([]byte)
|
||||||
for _, idx := range indexes {
|
defer func() {
|
||||||
_, err := f.Seek(offsetForChunkIndex(idx+indexOffset), os.SEEK_SET)
|
byteBufPool.Put(buf)
|
||||||
if err != nil {
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < len(indexes); i++ {
|
||||||
|
// This loads chunks in batches. A batch is a streak of
|
||||||
|
// consecutive chunks, read from disk in one go.
|
||||||
|
batchSize := 1
|
||||||
|
if _, err := f.Seek(offsetForChunkIndex(indexes[i]+indexOffset), os.SEEK_SET); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := f.Read(typeBuf)
|
for ; batchSize < chunkMaxBatchSize &&
|
||||||
if err != nil {
|
i+1 < len(indexes) &&
|
||||||
return nil, err
|
indexes[i]+1 == indexes[i+1]; i, batchSize = i+1, batchSize+1 {
|
||||||
}
|
}
|
||||||
if n != 1 {
|
readSize := batchSize * chunkLenWithHeader
|
||||||
panic("read returned != 1 bytes")
|
if cap(buf) < readSize {
|
||||||
|
buf = make([]byte, readSize)
|
||||||
}
|
}
|
||||||
|
buf = buf[:readSize]
|
||||||
|
|
||||||
_, err = f.Seek(chunkHeaderLen-1, os.SEEK_CUR)
|
if _, err := io.ReadFull(f, buf); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
chunk := newChunkForEncoding(chunkEncoding(typeBuf[0]))
|
for c := 0; c < batchSize; c++ {
|
||||||
chunk.unmarshal(f)
|
chunk := newChunkForEncoding(chunkEncoding(buf[c*chunkLenWithHeader+chunkHeaderTypeOffset]))
|
||||||
|
chunk.unmarshalFromBuf(buf[c*chunkLenWithHeader+chunkHeaderLen:])
|
||||||
chunks = append(chunks, chunk)
|
chunks = append(chunks, chunk)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
chunkOps.WithLabelValues(load).Add(float64(len(chunks)))
|
chunkOps.WithLabelValues(load).Add(float64(len(chunks)))
|
||||||
atomic.AddInt64(&numMemChunks, int64(len(chunks)))
|
atomic.AddInt64(&numMemChunks, int64(len(chunks)))
|
||||||
return chunks, nil
|
return chunks, nil
|
||||||
|
@ -422,24 +437,23 @@ func (p *persistence) loadChunkDescs(fp clientmodel.Fingerprint, beforeTime clie
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
totalChunkLen := chunkHeaderLen + chunkLen
|
if fi.Size()%int64(chunkLenWithHeader) != 0 {
|
||||||
if fi.Size()%int64(totalChunkLen) != 0 {
|
|
||||||
p.setDirty(true)
|
p.setDirty(true)
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"size of series file for fingerprint %v is %d, which is not a multiple of the chunk length %d",
|
"size of series file for fingerprint %v is %d, which is not a multiple of the chunk length %d",
|
||||||
fp, fi.Size(), totalChunkLen,
|
fp, fi.Size(), chunkLenWithHeader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
numChunks := int(fi.Size()) / totalChunkLen
|
numChunks := int(fi.Size()) / chunkLenWithHeader
|
||||||
cds := make([]*chunkDesc, 0, numChunks)
|
cds := make([]*chunkDesc, 0, numChunks)
|
||||||
|
chunkTimesBuf := make([]byte, 16)
|
||||||
for i := 0; i < numChunks; i++ {
|
for i := 0; i < numChunks; i++ {
|
||||||
_, err := f.Seek(offsetForChunkIndex(i)+chunkHeaderFirstTimeOffset, os.SEEK_SET)
|
_, err := f.Seek(offsetForChunkIndex(i)+chunkHeaderFirstTimeOffset, os.SEEK_SET)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkTimesBuf := make([]byte, 16)
|
|
||||||
_, err = io.ReadAtLeast(f, chunkTimesBuf, 16)
|
_, err = io.ReadAtLeast(f, chunkTimesBuf, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -799,7 +813,7 @@ func (p *persistence) loadSeriesMapAndHeads() (sm *seriesMap, chunksToPersist in
|
||||||
}
|
}
|
||||||
chunk := newChunkForEncoding(chunkEncoding(encoding))
|
chunk := newChunkForEncoding(chunkEncoding(encoding))
|
||||||
if err := chunk.unmarshal(r); err != nil {
|
if err := chunk.unmarshal(r); err != nil {
|
||||||
glog.Warning("Could not decode chunk type:", err)
|
glog.Warning("Could not decode chunk:", err)
|
||||||
p.dirty = true
|
p.dirty = true
|
||||||
return sm, chunksToPersist, nil
|
return sm, chunksToPersist, nil
|
||||||
}
|
}
|
||||||
|
@ -900,7 +914,7 @@ func (p *persistence) dropAndPersistChunks(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
headerBuf := make([]byte, chunkHeaderLen)
|
headerBuf := make([]byte, chunkHeaderLen)
|
||||||
_, err = io.ReadAtLeast(f, headerBuf, chunkHeaderLen)
|
_, err = io.ReadFull(f, headerBuf)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
// We ran into the end of the file without finding any chunks that should
|
// We ran into the end of the file without finding any chunks that should
|
||||||
// be kept. Remove the whole file.
|
// be kept. Remove the whole file.
|
||||||
|
@ -960,7 +974,7 @@ func (p *persistence) dropAndPersistChunks(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
offset = int(written / (chunkHeaderLen + chunkLen))
|
offset = int(written / chunkLenWithHeader)
|
||||||
|
|
||||||
if len(chunks) > 0 {
|
if len(chunks) > 0 {
|
||||||
if err = writeChunks(temp, chunks); err != nil {
|
if err = writeChunks(temp, chunks); err != nil {
|
||||||
|
@ -983,7 +997,7 @@ func (p *persistence) deleteSeriesFile(fp clientmodel.Fingerprint) (int, error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
numChunks := int(fi.Size() / (chunkHeaderLen + chunkLen))
|
numChunks := int(fi.Size() / chunkLenWithHeader)
|
||||||
if err := os.Remove(fname); err != nil {
|
if err := os.Remove(fname); err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
@ -1366,17 +1380,17 @@ loop:
|
||||||
}
|
}
|
||||||
|
|
||||||
func offsetForChunkIndex(i int) int64 {
|
func offsetForChunkIndex(i int) int64 {
|
||||||
return int64(i * (chunkHeaderLen + chunkLen))
|
return int64(i * chunkLenWithHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func chunkIndexForOffset(offset int64) (int, error) {
|
func chunkIndexForOffset(offset int64) (int, error) {
|
||||||
if int(offset)%(chunkHeaderLen+chunkLen) != 0 {
|
if int(offset)%chunkLenWithHeader != 0 {
|
||||||
return -1, fmt.Errorf(
|
return -1, fmt.Errorf(
|
||||||
"offset %d is not a multiple of on-disk chunk length %d",
|
"offset %d is not a multiple of on-disk chunk length %d",
|
||||||
offset, chunkHeaderLen+chunkLen,
|
offset, chunkLenWithHeader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return int(offset) / (chunkHeaderLen + chunkLen), nil
|
return int(offset) / chunkLenWithHeader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeChunkHeader(w io.Writer, c chunk) error {
|
func writeChunkHeader(w io.Writer, c chunk) error {
|
||||||
|
@ -1389,7 +1403,7 @@ func writeChunkHeader(w io.Writer, c chunk) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeChunks(w io.Writer, chunks []chunk) error {
|
func writeChunks(w io.Writer, chunks []chunk) error {
|
||||||
b := bufio.NewWriterSize(w, len(chunks)*(chunkHeaderLen+chunkLen))
|
b := bufio.NewWriterSize(w, len(chunks)*chunkLenWithHeader)
|
||||||
for _, chunk := range chunks {
|
for _, chunk := range chunks {
|
||||||
if err := writeChunkHeader(b, chunk); err != nil {
|
if err := writeChunkHeader(b, chunk); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in a new issue