Skip to content
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

fix: clear profile buffer at flush #3179

Closed
wants to merge 7 commits into from

Conversation

kolesnikovae
Copy link
Collaborator

@kolesnikovae kolesnikovae commented Apr 8, 2024

When writing a chunk of profiles to disk, we allocate a buffer to hold the affected profiles, which we reuse throughout the lifetime of a mutable block in memory. Each entry in this buffer refers to a profile, including its metadata and samples.

The problem is that we do not clear this buffer after use, preventing the written profiles from being garbage collected, and thus consuming memory unnecessarily.

To compound the problem, creating a new parquet row group may require a considerable amount of memory, as evidenced by the spike in the graph. This makes the ingesters susceptible to OOM kills, even in relatively small deployments.

Notable changes proposed in the PR:

  • Parquet writer is not retained in-between chunk flushes.
  • Iterators are not buffers when row groups are read from the disk at block flush.
  • Flush buffers are cleaned out explicitly.

image

No noticeable increase in CPU consumption was identified. From the OS view, the working set size of the process will likely not change substantially, because of the FS cache.

The impact of the change is expected to be larger in large-scale deployments, where the amount of erroneously retained object and buffers is larger.

@kolesnikovae kolesnikovae marked this pull request as ready for review April 9, 2024 06:27
@kolesnikovae kolesnikovae requested a review from a team as a code owner April 9, 2024 06:27
@@ -45,7 +41,7 @@ func NewMergeRowReader(readers []parquet.RowReader, maxValue parquet.Row, less f
}
its := make([]iter.Iterator[parquet.Row], len(readers))
for i := range readers {
its[i] = NewBufferedRowReaderIterator(readers[i], defaultRowBufferSize)
its[i] = NewBufferedRowReaderIterator(readers[i], 1)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Experiments show that buffering here is not helpful; we reference many column readers but could release them earlier, allowing the GC to free unused objects. Rows in our case are too large (approximately 10KB) for batching.

We may want to refactor this piece: a buffer of size 1 does not significantly harm performance, but it also does not provide any benefits.

@kolesnikovae
Copy link
Collaborator Author

kolesnikovae commented Apr 10, 2024

I noticed that the change causes notable increase of the working set size (the bottom boundary), which is not expected; debugging

image

Update: I didn't find an explanation better than google/cadvisor#3286. I think the best we can do is to test it under memory pressure.

@kolesnikovae kolesnikovae force-pushed the fix/reclaim-profile-buffer-space branch from 38b9edc to 25038be Compare April 10, 2024 09:22
Copy link
Contributor

@simonswine simonswine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

pkg/phlaredb/profiles.go Outdated Show resolved Hide resolved
@kolesnikovae
Copy link
Collaborator Author

Closing it for now: we do have some issues with how buffers are handled / retained, however, this fix does not solve them completely, and the benefit is barely measurable at scale. Instead, it makes sense to check how the row groups are merged at flush (we could probably avoid this altogether)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants