<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Pyroscope v2 components on Grafana Labs</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/</link><description>Recent content in Pyroscope v2 components on Grafana Labs</description><generator>Hugo -- gohugo.io</generator><language>en</language><atom:link href="/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/index.xml" rel="self" type="application/rss+xml"/><item><title>Pyroscope v2 distributor</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/distributor/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/distributor/</guid><content><![CDATA[&lt;h1 id=&#34;pyroscope-v2-distributor&#34;&gt;Pyroscope v2 distributor&lt;/h1&gt;
&lt;p&gt;The distributor is a stateless component that serves as the entry point for the ingestion path. It receives profiling data from agents and routes it to &lt;a href=&#34;../segment-writer/&#34;&gt;segment-writers&lt;/a&gt; for storage.&lt;/p&gt;
&lt;h2 id=&#34;profile-routing&#34;&gt;Profile routing&lt;/h2&gt;
&lt;p&gt;Unlike v1 where profiles are routed to ingesters based on hash ring token distribution, the v2 distributor routes profiles to segment-writers based on the profile&amp;rsquo;s &lt;code&gt;service_name&lt;/code&gt; label. This co-location strategy ensures that profiles from the same application are stored together, which is crucial for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query performance&lt;/strong&gt;: Profiles likely to be queried together are stored in the same blocks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compaction efficiency&lt;/strong&gt;: Related data can be compacted more effectively&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage optimization&lt;/strong&gt;: Reduces the number of objects needed to satisfy typical queries&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;distribution-algorithm&#34;&gt;Distribution algorithm&lt;/h2&gt;
&lt;p&gt;The distributor uses a three-step process to determine where to place a profile:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tenant shards&lt;/strong&gt;: Find suitable locations from the total shards using the &lt;code&gt;tenant_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dataset shards&lt;/strong&gt;: Narrow down to locations suitable for the &lt;code&gt;service_name&lt;/code&gt; label.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Final placement&lt;/strong&gt;: Select the exact shard using consistent hashing or adaptive load balancing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This algorithm balances data locality with even distribution across the cluster.&lt;/p&gt;
&lt;p&gt;For detailed information about the distribution algorithm, refer to &lt;a href=&#34;../../data-distribution/&#34;&gt;Data distribution&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;validation&#34;&gt;Validation&lt;/h2&gt;
&lt;p&gt;The distributor cleans and validates data before sending it to segment-writers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensures profiles have timestamps set (defaults to receive time if missing)&lt;/li&gt;
&lt;li&gt;Removes samples with zero values&lt;/li&gt;
&lt;li&gt;Sums samples that share the same stacktrace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a request contains invalid data, the distributor returns a 400 HTTP status code with details in the response body.&lt;/p&gt;
&lt;h2 id=&#34;load-balancing&#34;&gt;Load balancing&lt;/h2&gt;
&lt;p&gt;Randomly load balance write requests across distributor instances. If you&amp;rsquo;re running Pyroscope in a Kubernetes cluster, you can define a Kubernetes &lt;a href=&#34;https://kubernetes.io/docs/concepts/services-networking/service/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Service&lt;/a&gt; as ingress for the distributors.&lt;/p&gt;
&lt;p&gt;The distributor discovers segment-writers through memberlist-based ring discovery, which maintains the list of available segment-writer instances.&lt;/p&gt;
&lt;h2 id=&#34;stateless-design&#34;&gt;Stateless design&lt;/h2&gt;
&lt;p&gt;The distributor is completely stateless and disk-less:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requires no local storage&lt;/li&gt;
&lt;li&gt;Scales horizontally by adding more instances&lt;/li&gt;
&lt;li&gt;Allows instances to be added or removed without data migration&lt;/li&gt;
&lt;li&gt;Supports deployment in ephemeral containers&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="pyroscope-v2-distributor">Pyroscope v2 distributor&lt;/h1>
&lt;p>The distributor is a stateless component that serves as the entry point for the ingestion path. It receives profiling data from agents and routes it to &lt;a href="../segment-writer/">segment-writers&lt;/a> for storage.&lt;/p></description></item><item><title>Pyroscope v2 segment-writer</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/segment-writer/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/segment-writer/</guid><content><![CDATA[&lt;h1 id=&#34;pyroscope-v2-segment-writer&#34;&gt;Pyroscope v2 segment-writer&lt;/h1&gt;
&lt;p&gt;The segment-writer is a stateless component that accumulates incoming profiles in memory and periodically writes them to object storage as segments. This is a new component in v2 that replaces the ingester&amp;rsquo;s role in the write path.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Profile accumulation&lt;/strong&gt;: The segment-writer receives profiles from &lt;a href=&#34;../distributor/&#34;&gt;distributors&lt;/a&gt; and accumulates them in memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Segment creation&lt;/strong&gt;: Profiles are batched into small blocks called segments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object storage write&lt;/strong&gt;: Segments are written directly to object storage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metadata update&lt;/strong&gt;: The segment-writer updates the &lt;a href=&#34;../metastore/&#34;&gt;metastore&lt;/a&gt; with metadata about newly created segments.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;key-features&#34;&gt;Key features&lt;/h2&gt;
&lt;h3 id=&#34;single-object-per-shard&#34;&gt;Single object per shard&lt;/h3&gt;
&lt;p&gt;Each segment-writer produces a single object per shard containing data from all tenant services assigned to that shard. This approach minimizes the number of write operations to object storage, significantly reducing costs compared to writing individual objects for each tenant or service.&lt;/p&gt;
&lt;h3 id=&#34;synchronous-ingestion&#34;&gt;Synchronous ingestion&lt;/h3&gt;
&lt;p&gt;Ingestion clients are blocked until data is durably stored in object storage and an entry for the object is created in the metadata index. This guarantees data durability without requiring local disk persistence.&lt;/p&gt;
&lt;p&gt;By default, ingestion is synchronous with median latency expected to be less than 500ms using default settings and popular object storage providers such as Amazon S3, Google Cloud Storage, and Azure Blob Storage.&lt;/p&gt;
&lt;h3 id=&#34;in-memory-accumulation&#34;&gt;In-memory accumulation&lt;/h3&gt;
&lt;p&gt;Profiles are accumulated in an in-memory database before being flushed to object storage. The in-memory structure includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Profile index&lt;/strong&gt;: Efficient indexing for accumulated profiles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inverted index&lt;/strong&gt;: For label-based lookups during segment creation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;data-co-location&#34;&gt;Data co-location&lt;/h3&gt;
&lt;p&gt;Profiles from the same application (identified by the &lt;code&gt;service_name&lt;/code&gt; label) are co-located in the same segments. This co-location is maintained by the distributor&amp;rsquo;s routing algorithm and is crucial for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improves query performance&lt;/li&gt;
&lt;li&gt;Increases compaction efficiency&lt;/li&gt;
&lt;li&gt;Optimizes storage usage&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;stateless-design&#34;&gt;Stateless design&lt;/h2&gt;
&lt;p&gt;Unlike the v1 ingester which required local disk storage, the segment-writer is completely stateless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requires no persistent local storage&lt;/li&gt;
&lt;li&gt;Writes all data directly to object storage&lt;/li&gt;
&lt;li&gt;Scales horizontally by adding more instances&lt;/li&gt;
&lt;li&gt;Allows instances to be added or removed without data migration&lt;/li&gt;
&lt;li&gt;Recovers immediately after failure (no WAL replay needed)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;segment-lifecycle&#34;&gt;Segment lifecycle&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Creation&lt;/strong&gt;: Profiles are accumulated in memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flush&lt;/strong&gt;: When conditions are met (time or size thresholds), a segment is written to object storage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Registration&lt;/strong&gt;: Segment metadata is registered in the metastore.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compaction&lt;/strong&gt;: Small segments are later merged into larger blocks by &lt;a href=&#34;../compaction-worker/&#34;&gt;compaction-workers&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;failure-handling&#34;&gt;Failure handling&lt;/h2&gt;
&lt;p&gt;The segment writer relies on at-least-once delivery semantics. If a write fails after the segment has been uploaded to object storage but before the metastore acknowledges the metadata, the client retries the request. This can result in the same profile appearing in multiple segments, which is resolved during compaction.&lt;/p&gt;
&lt;h3 id=&#34;dead-letter-queue&#34;&gt;Dead letter queue&lt;/h3&gt;
&lt;p&gt;If the segment writer cannot register metadata with the metastore (for example, during metastore unavailability), the metadata is written to a dead letter queue (DLQ) directory in object storage. The metastore recovers these entries in the background, ensuring that data is eventually made visible to queries.&lt;/p&gt;
&lt;h2 id=&#34;deployment&#34;&gt;Deployment&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The segment writer runs as a StatefulSet without persistent volumes.&lt;/li&gt;
&lt;li&gt;It participates in a hash ring used by the &lt;a href=&#34;../distributor/&#34;&gt;distributor&lt;/a&gt; to route profiles.&lt;/li&gt;
&lt;li&gt;Multiple segment writers can write to the same shard during topology changes (for example, scaling events or rollouts). This is expected and handled by compaction.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The segment-writer flush behavior can be configured to balance between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt;: How quickly data becomes queryable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost&lt;/strong&gt;: Number of write operations to object storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory usage&lt;/strong&gt;: Amount of data held in memory before flush&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="pyroscope-v2-segment-writer">Pyroscope v2 segment-writer&lt;/h1>
&lt;p>The segment-writer is a stateless component that accumulates incoming profiles in memory and periodically writes them to object storage as segments. This is a new component in v2 that replaces the ingester&amp;rsquo;s role in the write path.&lt;/p></description></item><item><title>Pyroscope v2 metastore</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/metastore/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/metastore/</guid><content><![CDATA[&lt;h1 id=&#34;pyroscope-v2-metastore&#34;&gt;Pyroscope v2 metastore&lt;/h1&gt;
&lt;p&gt;The metastore is the only stateful component in the Pyroscope v2 architecture. It maintains the metadata index for all data objects stored in object storage and coordinates the compaction process.&lt;/p&gt;
&lt;h2 id=&#34;responsibilities&#34;&gt;Responsibilities&lt;/h2&gt;
&lt;p&gt;The metastore service is responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metadata index&lt;/strong&gt;: Maintaining an index of all blocks and segments in object storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compaction coordination&lt;/strong&gt;: Scheduling and coordinating compaction jobs for &lt;a href=&#34;../compaction-worker/&#34;&gt;compaction-workers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query planning&lt;/strong&gt;: Providing metadata to &lt;a href=&#34;../query-frontend/&#34;&gt;query-frontend&lt;/a&gt; for locating data objects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data placement&lt;/strong&gt;: Managing placement rules for the data distribution algorithm&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retention enforcement&lt;/strong&gt;: Applying time-based retention policies and generating tombstones for expired data&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;raft-consensus&#34;&gt;Raft consensus&lt;/h2&gt;
&lt;p&gt;The metastore uses the Raft protocol for consensus and replication, ensuring:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: All replicas maintain the same view of the metadata&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High availability&lt;/strong&gt;: The cluster can continue operating if some nodes fail&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fault tolerance&lt;/strong&gt;: Data is replicated across multiple nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;fault-tolerance&#34;&gt;Fault tolerance&lt;/h3&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Cluster size&lt;/th&gt;
              &lt;th&gt;Tolerated failures&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;3 nodes&lt;/td&gt;
              &lt;td&gt;1 node&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;5 nodes&lt;/td&gt;
              &lt;td&gt;2 nodes&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;storage-requirements&#34;&gt;Storage requirements&lt;/h2&gt;
&lt;p&gt;Even at large scale, the metastore only needs a few gigabytes of disk space for the metadata index. The index is implemented using BoltDB as the underlying key-value store.&lt;/p&gt;
&lt;p&gt;For better performance, the index database can be stored on an in-memory volume, as it&amp;rsquo;s recovered from the Raft log and snapshot on startup. Durable storage is not required for the index itself—only for the Raft log.&lt;/p&gt;
&lt;h2 id=&#34;metadata-index&#34;&gt;Metadata index&lt;/h2&gt;
&lt;p&gt;The metadata index stores information about data objects (blocks and segments) including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Block identifiers (ULID)&lt;/li&gt;
&lt;li&gt;Tenant and shard assignments&lt;/li&gt;
&lt;li&gt;Time ranges&lt;/li&gt;
&lt;li&gt;Dataset information (service names, profile types)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The index is partitioned by time, with each partition covering a 6-hour window. Within each partition, data is organized by tenant and shard.&lt;/p&gt;
&lt;p&gt;For detailed information about the metadata index structure, refer to &lt;a href=&#34;../../metadata-index/&#34;&gt;Metadata index&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;compaction-coordination&#34;&gt;Compaction coordination&lt;/h2&gt;
&lt;p&gt;The metastore coordinates the compaction process by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Job planning&lt;/strong&gt;: Creates compaction jobs when enough segments are available.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job scheduling&lt;/strong&gt;: Assigns jobs to available compaction-workers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job tracking&lt;/strong&gt;: Monitors job progress and handles failures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Index updates&lt;/strong&gt;: Updates the metadata index when compaction completes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The compaction service uses a lease-based ownership model with fencing tokens to prevent conflicts when workers fail or become unresponsive.&lt;/p&gt;
&lt;p&gt;For detailed information about the compaction process, refer to &lt;a href=&#34;../../compaction/&#34;&gt;Compaction&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;dead-letter-queue&#34;&gt;Dead letter queue&lt;/h2&gt;
&lt;p&gt;If the metastore is temporarily unavailable, &lt;a href=&#34;../segment-writer/&#34;&gt;segment writers&lt;/a&gt; fall back to writing metadata to a dead letter queue (DLQ) directory in object storage. The metastore recovers these entries in the background once it becomes available again.&lt;/p&gt;
&lt;h2 id=&#34;retention&#34;&gt;Retention&lt;/h2&gt;
&lt;p&gt;The metastore enforces time-based retention policies on a per-tenant basis. Retention operates at the partition level: entire partitions are removed when they exceed the configured retention period, rather than evaluating individual blocks. When partitions are deleted, tombstones are created for the underlying data objects, which are eventually cleaned up by compaction workers.&lt;/p&gt;
&lt;h2 id=&#34;query-support&#34;&gt;Query support&lt;/h2&gt;
&lt;p&gt;The metastore provides linearizable reads for query operations, ensuring that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Queries observe the most recent committed state&lt;/li&gt;
&lt;li&gt;Previous writes are visible to read operations&lt;/li&gt;
&lt;li&gt;Both leader and follower replicas can serve queries&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;leader-election&#34;&gt;Leader election&lt;/h2&gt;
&lt;p&gt;One metastore instance is elected as the leader through Raft consensus. The leader is responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Processing write requests&lt;/li&gt;
&lt;li&gt;Coordinating compaction scheduling&lt;/li&gt;
&lt;li&gt;Enforcing retention policies&lt;/li&gt;
&lt;li&gt;Running cleanup operations&lt;/li&gt;
&lt;li&gt;Recovering metadata entries from the dead letter queue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Follower replicas can serve read requests, distributing the query load across the cluster.&lt;/p&gt;
]]></content><description>&lt;h1 id="pyroscope-v2-metastore">Pyroscope v2 metastore&lt;/h1>
&lt;p>The metastore is the only stateful component in the Pyroscope v2 architecture. It maintains the metadata index for all data objects stored in object storage and coordinates the compaction process.&lt;/p></description></item><item><title>Pyroscope v2 compaction-worker</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/compaction-worker/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/compaction-worker/</guid><content><![CDATA[&lt;h1 id=&#34;pyroscope-v2-compaction-worker&#34;&gt;Pyroscope v2 compaction-worker&lt;/h1&gt;
&lt;p&gt;The compaction-worker is a stateless component responsible for merging small segments into larger blocks. This improves query performance by reducing the number of objects that need to be read from object storage.&lt;/p&gt;
&lt;h2 id=&#34;why-compaction-is-needed&#34;&gt;Why compaction is needed&lt;/h2&gt;
&lt;p&gt;The ingestion pipeline creates many small segments—potentially millions of objects per hour at scale. Without compaction, this leads to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read amplification&lt;/strong&gt;: Queries must fetch many small objects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Increased costs&lt;/strong&gt;: More API calls to object storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metadata bloat&lt;/strong&gt;: The metastore index grows unboundedly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance degradation&lt;/strong&gt;: Both read and write paths slow down&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Job polling&lt;/strong&gt;: Workers poll the &lt;a href=&#34;../metastore/&#34;&gt;metastore&lt;/a&gt; for available compaction jobs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Segment download&lt;/strong&gt;: Workers download source segments from object storage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merge operation&lt;/strong&gt;: Matching datasets from different segments are merged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block upload&lt;/strong&gt;: The compacted block is uploaded to object storage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Status report&lt;/strong&gt;: Workers report job completion to the metastore.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;compaction-speed&#34;&gt;Compaction speed&lt;/h2&gt;
&lt;p&gt;Compaction workers compact data as soon as possible after it&amp;rsquo;s written to object storage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Median time to first compaction&lt;/strong&gt;: Less than 15 seconds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous operation&lt;/strong&gt;: Workers constantly poll for new jobs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This ensures that query performance remains optimal even during high ingestion rates.&lt;/p&gt;
&lt;h2 id=&#34;job-scheduling&#34;&gt;Job scheduling&lt;/h2&gt;
&lt;p&gt;Compaction jobs are coordinated by the metastore, which:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates jobs when enough segments are available for compaction&lt;/li&gt;
&lt;li&gt;Assigns jobs to workers based on available capacity&lt;/li&gt;
&lt;li&gt;Tracks job progress and handles failures&lt;/li&gt;
&lt;li&gt;Uses a &amp;ldquo;Small Job First&amp;rdquo; strategy to prioritize smaller blocks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Workers specify their available capacity when polling for jobs, allowing the system to adapt to the available resources.&lt;/p&gt;
&lt;h2 id=&#34;data-layout&#34;&gt;Data layout&lt;/h2&gt;
&lt;p&gt;Profiling data from each service (identified by the &lt;code&gt;service_name&lt;/code&gt; label) is stored as a separate dataset within a block. During compaction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Matching datasets from different blocks are merged&lt;/li&gt;
&lt;li&gt;TSDB indexes are combined&lt;/li&gt;
&lt;li&gt;Symbols and profile tables are merged and rewritten&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output block contains non-overlapping, independent datasets optimized for efficient reading.&lt;/p&gt;
&lt;h2 id=&#34;stateless-design&#34;&gt;Stateless design&lt;/h2&gt;
&lt;p&gt;Compaction workers are completely stateless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Require no persistent local storage&lt;/li&gt;
&lt;li&gt;Scale horizontally by adding more instances&lt;/li&gt;
&lt;li&gt;Allow instances to be added or removed at any time&lt;/li&gt;
&lt;li&gt;Use default concurrency based on available CPU cores&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;fault-tolerance&#34;&gt;Fault tolerance&lt;/h2&gt;
&lt;p&gt;If a compaction worker fails:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The job lease expires&lt;/li&gt;
&lt;li&gt;The metastore reassigns the job to another worker&lt;/li&gt;
&lt;li&gt;Source segments remain in object storage until compaction succeeds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jobs that repeatedly fail are deprioritized to prevent blocking the compaction queue.&lt;/p&gt;
&lt;h2 id=&#34;garbage-collection&#34;&gt;Garbage collection&lt;/h2&gt;
&lt;p&gt;After compaction completes, the original source blocks are not immediately deleted. Instead, tombstones are created in the metastore. The actual deletion happens after a configurable delay, giving queries time to discover the new compacted blocks and stop accessing the original ones. Eventually, tombstones are included in compaction jobs, and the worker removes the source objects from object storage.&lt;/p&gt;
&lt;p&gt;For detailed information about the compaction process, refer to &lt;a href=&#34;../../compaction/&#34;&gt;Compaction&lt;/a&gt;.&lt;/p&gt;
]]></content><description>&lt;h1 id="pyroscope-v2-compaction-worker">Pyroscope v2 compaction-worker&lt;/h1>
&lt;p>The compaction-worker is a stateless component responsible for merging small segments into larger blocks. This improves query performance by reducing the number of objects that need to be read from object storage.&lt;/p></description></item><item><title>Pyroscope v2 query-frontend</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/query-frontend/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/query-frontend/</guid><content><![CDATA[&lt;h1 id=&#34;pyroscope-v2-query-frontend&#34;&gt;Pyroscope v2 query-frontend&lt;/h1&gt;
&lt;p&gt;The query-frontend is a stateless component that serves as the entry point for the query path. It handles query planning and routes requests to &lt;a href=&#34;../query-backend/&#34;&gt;query-backend&lt;/a&gt; instances for execution.&lt;/p&gt;
&lt;h2 id=&#34;responsibilities&#34;&gt;Responsibilities&lt;/h2&gt;
&lt;p&gt;The query-frontend is responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Receiving and validating queries&lt;/strong&gt; through the Query API&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Executing queries&lt;/strong&gt; by using the &lt;a href=&#34;../metastore/&#34;&gt;metastore&lt;/a&gt; for block discovery and delegating execution to &lt;a href=&#34;../query-backend/&#34;&gt;query-backend&lt;/a&gt; instances&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;query-flow&#34;&gt;Query flow&lt;/h2&gt;
&lt;p&gt;When a query arrives, the query frontend:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validates the query request.&lt;/li&gt;
&lt;li&gt;Queries the &lt;a href=&#34;../metastore/&#34;&gt;metastore&lt;/a&gt; to find all blocks matching the query criteria (time range, tenant, and optionally service name).&lt;/li&gt;
&lt;li&gt;Builds a physical query plan as a tree: leaf nodes are read operations targeting specific blocks and datasets, while intermediate nodes are merge operations that combine results from their children.&lt;/li&gt;
&lt;li&gt;Sends the plan root to a &lt;a href=&#34;../query-backend/&#34;&gt;query backend&lt;/a&gt; instance, which distributes subtrees to other query backend instances for parallel execution and merging. For more details, refer to &lt;a href=&#34;../query-backend/&#34;&gt;Query backend&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because the metastore serves block metadata from memory with linearizable reads, query planning is fast and does not require the query frontend to maintain any local state about blocks.&lt;/p&gt;
&lt;h2 id=&#34;stateless-design&#34;&gt;Stateless design&lt;/h2&gt;
&lt;p&gt;The query-frontend is completely stateless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requires no persistent storage&lt;/li&gt;
&lt;li&gt;Scales horizontally to hundreds of instances&lt;/li&gt;
&lt;li&gt;Allows instances to be added or removed without coordination&lt;/li&gt;
&lt;li&gt;Supports auto-scaling based on query load&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;scalability&#34;&gt;Scalability&lt;/h2&gt;
&lt;p&gt;The query-frontend can scale independently of the write path:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Heavy query workloads don&amp;rsquo;t impact ingestion performance&lt;/li&gt;
&lt;li&gt;Handles increased query volume by adding more instances&lt;/li&gt;
&lt;li&gt;Works with any number of query-backend instances&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;load-balancing&#34;&gt;Load balancing&lt;/h2&gt;
&lt;p&gt;Query-frontends can be load balanced using standard HTTP load balancers. Each instance can handle any query, making round-robin load balancing effective.&lt;/p&gt;
]]></content><description>&lt;h1 id="pyroscope-v2-query-frontend">Pyroscope v2 query-frontend&lt;/h1>
&lt;p>The query-frontend is a stateless component that serves as the entry point for the query path. It handles query planning and routes requests to &lt;a href="../query-backend/">query-backend&lt;/a> instances for execution.&lt;/p></description></item><item><title>Pyroscope v2 query-backend</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/query-backend/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/query-backend/</guid><content><![CDATA[&lt;h1 id=&#34;pyroscope-v2-query-backend&#34;&gt;Pyroscope v2 query-backend&lt;/h1&gt;
&lt;p&gt;The query-backend is a stateless component that executes queries with high parallelism. It reads data directly from object storage and processes it according to the query plan received from the &lt;a href=&#34;../query-frontend/&#34;&gt;query-frontend&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&#34;../query-frontend/&#34;&gt;query frontend&lt;/a&gt; builds a physical query plan as a tree structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read nodes&lt;/strong&gt; (leaves) fetch and process data from specific blocks in object storage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merge nodes&lt;/strong&gt; (intermediate) combine results from their child nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The query frontend sends the plan root to a query backend instance. That instance distributes subtrees to other query backend instances for parallel execution, collects their results, and merges them. The final merged result is returned to the query frontend, which forwards it to the client.&lt;/p&gt;
&lt;p&gt;This tree-based execution allows queries to fan out across many query backend instances in parallel, with merging happening at each level of the tree rather than in a single aggregation point.&lt;/p&gt;
&lt;h2 id=&#34;direct-object-storage-access&#34;&gt;Direct object storage access&lt;/h2&gt;
&lt;p&gt;Unlike v1 where queries may need to access ingesters for recent data, the v2 query-backend reads directly from object storage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No coordination with write-path components needed&lt;/li&gt;
&lt;li&gt;Simplified query execution&lt;/li&gt;
&lt;li&gt;Better isolation between read and write paths&lt;/li&gt;
&lt;li&gt;Easier horizontal scaling&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;stateless-design&#34;&gt;Stateless design&lt;/h2&gt;
&lt;p&gt;The query-backend is completely stateless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requires no persistent storage&lt;/li&gt;
&lt;li&gt;Needs no caching layer (reads directly from object storage)&lt;/li&gt;
&lt;li&gt;Scales horizontally to hundreds of instances&lt;/li&gt;
&lt;li&gt;Allows instances to be added or removed without coordination&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;scalability&#34;&gt;Scalability&lt;/h2&gt;
&lt;p&gt;The query-backend enables horizontal scaling of the read path:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handles heavier query workloads by adding more instances&lt;/li&gt;
&lt;li&gt;Scales independently of the write path&lt;/li&gt;
&lt;li&gt;Shares no state between instances&lt;/li&gt;
&lt;li&gt;Supports auto-scaling based on query load&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;performance-characteristics&#34;&gt;Performance characteristics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High parallelism&lt;/strong&gt;: Multiple blocks processed concurrently&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory efficient&lt;/strong&gt;: Tree-based execution minimizes memory requirements&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network optimized&lt;/strong&gt;: Results combined close to the data source&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="pyroscope-v2-query-backend">Pyroscope v2 query-backend&lt;/h1>
&lt;p>The query-backend is a stateless component that executes queries with high parallelism. It reads data directly from object storage and processes it according to the query plan received from the &lt;a href="../query-frontend/">query-frontend&lt;/a>.&lt;/p></description></item></channel></rss>