-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: flush idle dataobjects after X seconds #16348
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,10 @@ type partitionProcessor struct { | |
bucket objstore.Bucket | ||
bufPool *sync.Pool | ||
|
||
// Idle stream handling | ||
idleFlushTimout time.Duration | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: Timout -> Timeout |
||
lastFlush time.Time | ||
|
||
// Metrics | ||
metrics *partitionOffsetMetrics | ||
|
||
|
@@ -50,7 +54,21 @@ type partitionProcessor struct { | |
logger log.Logger | ||
} | ||
|
||
func newPartitionProcessor(ctx context.Context, client *kgo.Client, builderCfg dataobj.BuilderConfig, uploaderCfg uploader.Config, bucket objstore.Bucket, tenantID string, virtualShard int32, topic string, partition int32, logger log.Logger, reg prometheus.Registerer, bufPool *sync.Pool) *partitionProcessor { | ||
func newPartitionProcessor( | ||
ctx context.Context, | ||
client *kgo.Client, | ||
builderCfg dataobj.BuilderConfig, | ||
uploaderCfg uploader.Config, | ||
bucket objstore.Bucket, | ||
tenantID string, | ||
virtualShard int32, | ||
topic string, | ||
partition int32, | ||
logger log.Logger, | ||
reg prometheus.Registerer, | ||
bufPool *sync.Pool, | ||
idleFlushTimeout time.Duration, | ||
) *partitionProcessor { | ||
ctx, cancel := context.WithCancel(ctx) | ||
decoder, err := kafka.NewDecoder() | ||
if err != nil { | ||
|
@@ -95,6 +113,8 @@ func newPartitionProcessor(ctx context.Context, client *kgo.Client, builderCfg d | |
uploader: uploader, | ||
metastoreManager: metastoreManager, | ||
bufPool: bufPool, | ||
idleFlushTimout: idleFlushTimeout, | ||
lastFlush: time.Now(), | ||
} | ||
} | ||
|
||
|
@@ -115,6 +135,9 @@ func (p *partitionProcessor) start() { | |
return | ||
} | ||
p.processRecord(record) | ||
|
||
case <-time.After(p.idleFlushTimout): | ||
p.idleFlush() | ||
} | ||
} | ||
}() | ||
|
@@ -163,6 +186,29 @@ func (p *partitionProcessor) initBuilder() error { | |
return initErr | ||
} | ||
|
||
func (p *partitionProcessor) flushStream(flushBuffer *bytes.Buffer) error { | ||
flushedDataobjStats, err := p.builder.Flush(flushBuffer) | ||
if err != nil { | ||
level.Error(p.logger).Log("msg", "failed to flush builder", "err", err) | ||
return err | ||
} | ||
|
||
objectPath, err := p.uploader.Upload(p.ctx, flushBuffer) | ||
if err != nil { | ||
level.Error(p.logger).Log("msg", "failed to upload object", "err", err) | ||
return err | ||
} | ||
|
||
if err := p.metastoreManager.UpdateMetastore(p.ctx, objectPath, flushedDataobjStats); err != nil { | ||
level.Error(p.logger).Log("msg", "failed to update metastore", "err", err) | ||
return err | ||
} | ||
|
||
p.lastFlush = time.Now() | ||
|
||
return nil | ||
} | ||
|
||
func (p *partitionProcessor) processRecord(record *kgo.Record) { | ||
// Update offset metric at the end of processing | ||
defer p.metrics.updateOffset(record.Offset) | ||
|
@@ -200,20 +246,8 @@ func (p *partitionProcessor) processRecord(record *kgo.Record) { | |
|
||
flushBuffer.Reset() | ||
|
||
flushedDataobjStats, err := p.builder.Flush(flushBuffer) | ||
if err != nil { | ||
level.Error(p.logger).Log("msg", "failed to flush builder", "err", err) | ||
return | ||
} | ||
|
||
objectPath, err := p.uploader.Upload(p.ctx, flushBuffer) | ||
if err != nil { | ||
level.Error(p.logger).Log("msg", "failed to upload object", "err", err) | ||
return | ||
} | ||
|
||
if err := p.metastoreManager.UpdateMetastore(p.ctx, objectPath, flushedDataobjStats); err != nil { | ||
level.Error(p.logger).Log("msg", "failed to update metastore", "err", err) | ||
if err := p.flushStream(flushBuffer); err != nil { | ||
level.Error(p.logger).Log("msg", "failed to flush stream", "err", err) | ||
return | ||
} | ||
}() | ||
|
@@ -251,3 +285,31 @@ func (p *partitionProcessor) commitRecords(record *kgo.Record) error { | |
} | ||
return lastErr | ||
} | ||
|
||
// idleFlush flushes the file if it has been idle for too long. | ||
// This is used to avoid holding on to memory for too long. | ||
// We compare the current time with the last flush time to determine if the builder has been idle. | ||
func (p *partitionProcessor) idleFlush() { | ||
if p.builder == nil { | ||
return | ||
} | ||
|
||
now := time.Now() | ||
if now.Sub(p.lastFlush) < p.idleFlushTimout { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could also use time.Since here: |
||
return // Avoid checking too frequently | ||
} | ||
|
||
func() { | ||
flushBuffer := p.bufPool.Get().(*bytes.Buffer) | ||
defer p.bufPool.Put(flushBuffer) | ||
|
||
flushBuffer.Reset() | ||
|
||
if err := p.flushStream(flushBuffer); err != nil { | ||
level.Error(p.logger).Log("msg", "failed to flush stream", "err", err) | ||
return | ||
} | ||
|
||
p.lastFlush = now | ||
}() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can set the default value as the 3rd argument in the flag definition, if you wish