<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Pyroscope v2 architecture on Grafana Labs</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/</link><description>Recent content in Pyroscope v2 architecture on Grafana Labs</description><generator>Hugo -- gohugo.io</generator><language>en</language><atom:link href="/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/index.xml" rel="self" type="application/rss+xml"/><item><title>Design motivation</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/design-motivation/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/design-motivation/</guid><content><![CDATA[&lt;h1 id=&#34;design-motivation&#34;&gt;Design motivation&lt;/h1&gt;
&lt;p&gt;The v2 architecture addresses fundamental scalability and resilience limitations in v1 that cannot be resolved incrementally.&lt;/p&gt;
&lt;h2 id=&#34;write-path-limitations-in-v1&#34;&gt;Write path limitations in v1&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No write-ahead log (WAL)&lt;/strong&gt;: Ingesters accumulate profiles in memory and periodically flush them to disk, but there is no WAL to durably record writes on arrival. If an ingester crashes between flushes, the in-memory profiles are lost. Replication mitigates this but cannot fully prevent data loss when multiple ingesters fail.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deduplication overhead&lt;/strong&gt;: In v1, each profile series is replicated to N ingesters, and each ingester writes its own block. At query time, these duplicates have to be merged and deduplicated. This becomes increasingly expensive as the number of ingesters grows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weak read/write isolation&lt;/strong&gt;: Ingestion latency spikes can cause distributor out-of-memory (OOM) errors. Expensive queries can increase ingestion latency due to broad locks, and can themselves cause ingester OOM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suboptimal data distribution&lt;/strong&gt;: The label-hash-based sharding distributes profiles of the same service across all ingesters, causing excessive duplication of symbolic information and reducing query selectivity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow rollouts&lt;/strong&gt;: Ingester rollouts can take hours in large deployments due to the need to flush in-memory data before shutdown.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;read-path-limitations-in-v1&#34;&gt;Read path limitations in v1&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Store-gateway instability&lt;/strong&gt;: Heavy queries can cause store-gateway OOM. The block index overhead grows with the number of blocks, putting memory pressure on store-gateways.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited elasticity&lt;/strong&gt;: The querier and store-gateway services are difficult to scale dynamically, as store-gateways need to load block indexes before serving queries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow rollouts&lt;/strong&gt;: Like ingesters, store-gateway rollouts can be slow due to the need to load block indexes on startup.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;compaction-limitations-in-v1&#34;&gt;Compaction limitations in v1&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: The v1 compactor can struggle to keep up with large tenants as data is replicated during ingestion. Delays in compaction place pressure on the read path, as queries have to process and deduplicate more uncompacted blocks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;extensibility&#34;&gt;Extensibility&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Adding a new data access method (for example, a new API endpoint for heatmaps) in v1 requires changes across many components. The tight coupling between ingesters, store-gateways, and queriers makes the codebase harder to maintain and extend.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;comparison-with-v1&#34;&gt;Comparison with v1&lt;/h2&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;Aspect&lt;/th&gt;
              &lt;th&gt;v1&lt;/th&gt;
              &lt;th&gt;v2&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Write path&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Distributor → Ingester → Object Storage&lt;/td&gt;
              &lt;td&gt;Distributor → Segment writer → Object storage &#43; Metastore&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Per-tenant bucket index in object storage&lt;/td&gt;
              &lt;td&gt;Metastore (Raft-based, in-memory index)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Read path&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Query frontend → Query scheduler → Querier → Ingester / Store-gateway&lt;/td&gt;
              &lt;td&gt;Query frontend → Metastore &#43; Query backend&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Compaction&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Compactor (hash-ring sharded, per-tenant)&lt;/td&gt;
              &lt;td&gt;Compaction worker orchestrated by metastore&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;strong&gt;Replication&lt;/strong&gt;&lt;/td&gt;
              &lt;td&gt;Write replication to N ingesters&lt;/td&gt;
              &lt;td&gt;No write replication; durability via object storage&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;p&gt;v2 addresses these issues by eliminating write replication in favor of object storage durability, centralizing metadata in the metastore for fast query planning and enabling stateless query backends that access object storage directly, and decoupling compaction into a more scalable job-based system orchestrated by the metastore.&lt;/p&gt;
&lt;p&gt;For details on how the v2 architecture works, refer to &lt;a href=&#34;../about-pyroscope-v2-architecture/&#34;&gt;About the Pyroscope v2 architecture&lt;/a&gt;.&lt;/p&gt;
]]></content><description>&lt;h1 id="design-motivation">Design motivation&lt;/h1>
&lt;p>The v2 architecture addresses fundamental scalability and resilience limitations in v1 that cannot be resolved incrementally.&lt;/p>
&lt;h2 id="write-path-limitations-in-v1">Write path limitations in v1&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>No write-ahead log (WAL)&lt;/strong>: Ingesters accumulate profiles in memory and periodically flush them to disk, but there is no WAL to durably record writes on arrival. If an ingester crashes between flushes, the in-memory profiles are lost. Replication mitigates this but cannot fully prevent data loss when multiple ingesters fail.&lt;/li>
&lt;li>&lt;strong>Deduplication overhead&lt;/strong>: In v1, each profile series is replicated to N ingesters, and each ingester writes its own block. At query time, these duplicates have to be merged and deduplicated. This becomes increasingly expensive as the number of ingesters grows.&lt;/li>
&lt;li>&lt;strong>Weak read/write isolation&lt;/strong>: Ingestion latency spikes can cause distributor out-of-memory (OOM) errors. Expensive queries can increase ingestion latency due to broad locks, and can themselves cause ingester OOM.&lt;/li>
&lt;li>&lt;strong>Suboptimal data distribution&lt;/strong>: The label-hash-based sharding distributes profiles of the same service across all ingesters, causing excessive duplication of symbolic information and reducing query selectivity.&lt;/li>
&lt;li>&lt;strong>Slow rollouts&lt;/strong>: Ingester rollouts can take hours in large deployments due to the need to flush in-memory data before shutdown.&lt;/li>
&lt;/ul>
&lt;h2 id="read-path-limitations-in-v1">Read path limitations in v1&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Store-gateway instability&lt;/strong>: Heavy queries can cause store-gateway OOM. The block index overhead grows with the number of blocks, putting memory pressure on store-gateways.&lt;/li>
&lt;li>&lt;strong>Limited elasticity&lt;/strong>: The querier and store-gateway services are difficult to scale dynamically, as store-gateways need to load block indexes before serving queries.&lt;/li>
&lt;li>&lt;strong>Slow rollouts&lt;/strong>: Like ingesters, store-gateway rollouts can be slow due to the need to load block indexes on startup.&lt;/li>
&lt;/ul>
&lt;h2 id="compaction-limitations-in-v1">Compaction limitations in v1&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Scalability&lt;/strong>: The v1 compactor can struggle to keep up with large tenants as data is replicated during ingestion. Delays in compaction place pressure on the read path, as queries have to process and deduplicate more uncompacted blocks.&lt;/li>
&lt;/ul>
&lt;h2 id="extensibility">Extensibility&lt;/h2>
&lt;ul>
&lt;li>Adding a new data access method (for example, a new API endpoint for heatmaps) in v1 requires changes across many components. The tight coupling between ingesters, store-gateways, and queriers makes the codebase harder to maintain and extend.&lt;/li>
&lt;/ul>
&lt;h2 id="comparison-with-v1">Comparison with v1&lt;/h2>
&lt;section class="expand-table-wrapper">&lt;div class="button-div">
&lt;button class="expand-table-btn">Expand table&lt;/button>
&lt;/div>&lt;div class="responsive-table-wrapper">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Aspect&lt;/th>
&lt;th>v1&lt;/th>
&lt;th>v2&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Write path&lt;/strong>&lt;/td>
&lt;td>Distributor → Ingester → Object Storage&lt;/td>
&lt;td>Distributor → Segment writer → Object storage + Metastore&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Metadata&lt;/strong>&lt;/td>
&lt;td>Per-tenant bucket index in object storage&lt;/td>
&lt;td>Metastore (Raft-based, in-memory index)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Read path&lt;/strong>&lt;/td>
&lt;td>Query frontend → Query scheduler → Querier → Ingester / Store-gateway&lt;/td>
&lt;td>Query frontend → Metastore + Query backend&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Compaction&lt;/strong>&lt;/td>
&lt;td>Compactor (hash-ring sharded, per-tenant)&lt;/td>
&lt;td>Compaction worker orchestrated by metastore&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Replication&lt;/strong>&lt;/td>
&lt;td>Write replication to N ingesters&lt;/td>
&lt;td>No write replication; durability via object storage&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/div>
&lt;/section>&lt;p>v2 addresses these issues by eliminating write replication in favor of object storage durability, centralizing metadata in the metastore for fast query planning and enabling stateless query backends that access object storage directly, and decoupling compaction into a more scalable job-based system orchestrated by the metastore.&lt;/p></description></item><item><title>About the Pyroscope v2 architecture</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/about-pyroscope-v2-architecture/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/about-pyroscope-v2-architecture/</guid><content><![CDATA[&lt;h1 id=&#34;about-the-pyroscope-v2-architecture&#34;&gt;About the Pyroscope v2 architecture&lt;/h1&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;The Pyroscope v2 architecture is production-ready and powers Grafana Cloud Profiles exclusively. However, until it&amp;rsquo;s released by default as part of Pyroscope v2.0, there are no API stability guarantees.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;Pyroscope v2 is a complete architectural redesign focused on improving scalability, performance, and cost-efficiency. The architecture is built around the following goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deliver high write throughput&lt;/li&gt;
&lt;li&gt;Provide cost-effective storage&lt;/li&gt;
&lt;li&gt;Enable scalable query performance&lt;/li&gt;
&lt;li&gt;Reduce operational overhead&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For background on the v1 limitations that motivated this redesign, refer to &lt;a href=&#34;../design-motivation/&#34;&gt;Design motivation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;key-design-changes&#34;&gt;Key design changes&lt;/h2&gt;
&lt;p&gt;The biggest change in Pyroscope v2 is how it handles storage: data is written directly to object storage, removing the need for local disks in ingesters. For single-node deployments, local file systems can still be used as object storage, but this setup isn&amp;rsquo;t supported in microservice mode.&lt;/p&gt;
&lt;p&gt;Pyroscope v2 also decouples the write and query paths. This means each path can scale independently, so even the heaviest queries won&amp;rsquo;t interfere with ingestion performance. The read path can scale to hundreds of instances instantly.&lt;/p&gt;
&lt;h2 id=&#34;architecture-overview&#34;&gt;Architecture overview&lt;/h2&gt;
&lt;p&gt;The high-level components of the architecture include:&lt;/p&gt;

  &lt;script type=&#34;text/javascript&#34; src=&#34;/web/mermaid.867770685db36193a268b63e8c85a9291badc92208742df0df7a384e9ff1619d.js&#34; integrity=&#34;sha256-hndwaF2zYZOiaLY&amp;#43;jIWpKRutySIIdC3w33o4Tp/xYZ0=&#34; defer&gt;&lt;/script&gt;
  

&lt;div class=&#34;mermaid-container&#34;&gt;
  &lt;pre class=&#34;mermaid&#34;&gt;
graph TD

    subgraph entry_points[&#34; &#34;]
        ingest_entry[&#34;Ingest Path&#34;]:::entry_ingest --&gt; distributor
        query_entry[&#34;Query Path&#34;]:::entry_query --&gt; query_frontend
    end

    distributor --&gt;|writes to| segment_writer
    segment_writer --&gt;|updates| metastore
    segment_writer --&gt;|creates segments| object_storage

    metastore --&gt;|coordinates| compaction_worker
    compaction_worker --&gt;|compacts| object_storage

    query_frontend --&gt;|invokes| query_backend
    query_backend --&gt;|reads from| object_storage
    query_frontend --&gt;|queries| metastore

    distributor[&#34;distributor&#34;]
    segment_writer[&#34;segment-writer&#34;]
    metastore[&#34;metastore&#34;]
    compaction_worker[&#34;compaction-worker&#34;]
    query_backend[&#34;query-backend&#34;]
    query_frontend[&#34;query-frontend&#34;]

    subgraph object_storage[&#34;object storage&#34;]
        segments
        blocks
    end

    linkStyle 0 stroke:#a855f7,stroke-width:2px
    linkStyle 1 stroke:#3b82f6,stroke-width:2px
    linkStyle 2,3,4 stroke:#a855f7,stroke-width:2px
    linkStyle 6 stroke:#a855f7,stroke-width:2px
    linkStyle 7,8,9 stroke:#3b82f6,stroke-width:2px

    classDef entry_ingest stroke:#a855f7,stroke-width:2px,font-weight:bold
    classDef entry_query stroke:#3b82f6,stroke-width:2px,font-weight:bold
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&#34;pyroscope-v2-components&#34;&gt;Pyroscope v2 components&lt;/h2&gt;
&lt;p&gt;Most components in v2 are stateless and don&amp;rsquo;t require any data persisted between process restarts. The metastore is the only stateful component, using Raft consensus for replication. For details about each component, refer to &lt;a href=&#34;../components/&#34;&gt;Components&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;the-write-path&#34;&gt;The write path&lt;/h3&gt;
&lt;p&gt;Profiles are ingested through the Push RPC API and HTTP &lt;code&gt;/ingest&lt;/code&gt; API to &lt;a href=&#34;../components/distributor/&#34;&gt;distributors&lt;/a&gt;. The write path includes distributor and &lt;a href=&#34;../components/segment-writer/&#34;&gt;segment-writer&lt;/a&gt; services: both are stateless, disk-less, and scale horizontally with high efficiency.&lt;/p&gt;
&lt;p&gt;Profile ingest requests are distributed among distributors, which then route them to segment-writers to co-locate profiles from the same application. This ensures that profiles likely to be queried together are stored together.&lt;/p&gt;
&lt;p&gt;The segment-writer service accumulates profiles in small blocks (segments) and writes them to object storage while updating the block index with metadata of newly added objects. Each writer produces a single object per shard containing data of all tenant services per shard; this approach minimizes the number of write operations to the object storage, optimizing the cost of the solution.&lt;/p&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. By default, ingestion is synchronous, with median latency expected to be less than 500ms using default settings.&lt;/p&gt;
&lt;h3 id=&#34;the-read-path&#34;&gt;The read path&lt;/h3&gt;
&lt;p&gt;Profiling data is queried through the Query API available in the &lt;a href=&#34;../components/query-frontend/&#34;&gt;query-frontend&lt;/a&gt; service.&lt;/p&gt;
&lt;p&gt;A regular flame graph query users see in the UI may require fetching many gigabytes of data from storage. Moreover, the raw profiling data needs expensive post-processing to be displayed in flame graph format. Pyroscope addresses this challenge through adaptive data placement that minimizes the number of objects that need to be read to satisfy a query, and high parallelism in query execution.&lt;/p&gt;
&lt;p&gt;The query frontend is responsible for preliminary query planning and routing the query to the &lt;a href=&#34;../components/query-backend/&#34;&gt;query-backend&lt;/a&gt; service. Data objects are located using the &lt;a href=&#34;../components/metastore/&#34;&gt;metastore&lt;/a&gt; service, which maintains the metadata index.&lt;/p&gt;
&lt;p&gt;Queries are executed by the query-backend service with high parallelism. Query execution is represented as a graph where the results of sub-queries are combined and optimized. This minimizes network overhead and enables horizontal scalability of the read path without needing traditional disk-based solutions or even a caching layer.&lt;/p&gt;
&lt;p&gt;Both query-frontend and query-backend are stateless services that can scale out to hundreds of instances.&lt;/p&gt;
&lt;h3 id=&#34;compaction&#34;&gt;Compaction&lt;/h3&gt;
&lt;p&gt;The number of objects created in storage can reach millions per hour. This can severely degrade query performance due to high read amplification and excessive calls to object storage. Additionally, a high number of metadata entries can degrade performance across the entire cluster, impacting the write path as well.&lt;/p&gt;
&lt;p&gt;To ensure high query performance, data objects are compacted in the background. The &lt;a href=&#34;../components/compaction-worker/&#34;&gt;compaction-worker&lt;/a&gt; service is responsible for merging small segments into larger blocks, which are then written back to object storage. Compaction workers compact data as soon as possible after it&amp;rsquo;s written to object storage, with median time to the first compaction not exceeding 15 seconds.&lt;/p&gt;
&lt;p&gt;Compaction workers are coordinated by the metastore service, which maintains the metadata index and schedules compaction jobs. Compaction workers are stateless and don&amp;rsquo;t require any local storage.&lt;/p&gt;
&lt;p&gt;For more details, refer to &lt;a href=&#34;../compaction/&#34;&gt;Compaction&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;object-storage&#34;&gt;Object storage&lt;/h2&gt;
&lt;p&gt;Pyroscope v2 is designed to operate without local disks, relying entirely on object storage. This approach minimizes operational overhead and cost.&lt;/p&gt;
&lt;p&gt;Pyroscope requires any of the following object stores for block files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/s3&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Amazon S3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloud.google.com/storage/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Google Cloud Storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://azure.microsoft.com/en-us/services/storage/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Microsoft Azure Storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wiki.openstack.org/wiki/Swift&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;OpenStack Swift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Local Filesystem (single node only)&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="about-the-pyroscope-v2-architecture">About the Pyroscope v2 architecture&lt;/h1>
&lt;div class="admonition admonition-note">&lt;blockquote>&lt;p class="title text-uppercase">Note&lt;/p>&lt;p>The Pyroscope v2 architecture is production-ready and powers Grafana Cloud Profiles exclusively. However, until it&amp;rsquo;s released by default as part of Pyroscope v2.0, there are no API stability guarantees.&lt;/p></description></item><item><title>Pyroscope v2 components</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/</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/</guid><content><![CDATA[&lt;h1 id=&#34;pyroscope-v2-components&#34;&gt;Pyroscope v2 components&lt;/h1&gt;
&lt;p&gt;Pyroscope v2 includes a set of components that interact to form a cluster.&lt;/p&gt;
&lt;p&gt;Most components are stateless and don&amp;rsquo;t require any data persisted between process restarts. The &lt;a href=&#34;metastore/&#34;&gt;metastore&lt;/a&gt; is the only stateful component in the architecture, using Raft consensus for replication and fault tolerance.&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;
    &lt;a href=&#34;/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/distributor/&#34;&gt;Distributor&lt;/a&gt;&lt;/li&gt;&lt;li&gt;
    &lt;a href=&#34;/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/segment-writer/&#34;&gt;Segment-writer&lt;/a&gt;&lt;/li&gt;&lt;li&gt;
    &lt;a href=&#34;/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/metastore/&#34;&gt;Metastore&lt;/a&gt;&lt;/li&gt;&lt;li&gt;
    &lt;a href=&#34;/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/compaction-worker/&#34;&gt;Compaction-worker&lt;/a&gt;&lt;/li&gt;&lt;li&gt;
    &lt;a href=&#34;/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/query-frontend/&#34;&gt;Query-frontend&lt;/a&gt;&lt;/li&gt;&lt;li&gt;
    &lt;a href=&#34;/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/components/query-backend/&#34;&gt;Query-backend&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
]]></content><description>&lt;h1 id="pyroscope-v2-components">Pyroscope v2 components&lt;/h1>
&lt;p>Pyroscope v2 includes a set of components that interact to form a cluster.&lt;/p>
&lt;p>Most components are stateless and don&amp;rsquo;t require any data persisted between process restarts. The &lt;a href="metastore/">metastore&lt;/a> is the only stateful component in the architecture, using Raft consensus for replication and fault tolerance.&lt;/p></description></item><item><title>Block format</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/block-format/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/block-format/</guid><content><![CDATA[&lt;h1 id=&#34;block-format&#34;&gt;Block format&lt;/h1&gt;
&lt;p&gt;In Pyroscope v2, a block is a single object in object storage (&lt;code&gt;block.bin&lt;/code&gt;) containing data from one or more &lt;em&gt;datasets&lt;/em&gt;. Each dataset holds profiling data for a specific service and includes its own TSDB index, symbol data, and profile tables. Block metadata — stored in the &lt;a href=&#34;../components/metastore/&#34;&gt;metastore&lt;/a&gt; and embedded in the object itself — describes the datasets, their labels, and byte offsets within the object.&lt;/p&gt;
&lt;h2 id=&#34;object-storage-layout&#34;&gt;Object storage layout&lt;/h2&gt;
&lt;p&gt;Segments (level 0, not yet compacted) and compacted blocks are stored in separate top-level directories. Segments are not yet split by tenant and use an anonymous tenant directory. After compaction, blocks are organized by tenant:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;segments/
  {shard}/
    anonymous/
      {block_id}/
        block.bin

blocks/
  {shard}/
    {tenant}/
      {block_id}/
        block.bin

dlq/
  {shard}/
    {tenant}/
      {block_id}/
        block.bin&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;block-structure&#34;&gt;Block structure&lt;/h2&gt;
&lt;p&gt;Each &lt;code&gt;block.bin&lt;/code&gt; object contains a sequence of datasets followed by a metadata footer:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;Offset    | Content
----------|-------------------------------------------
0         | Dataset 0 data
          | Dataset 1 data
          | ...
          | Dataset N data
          | Protobuf-encoded block metadata
end-8     | uint32 (big-endian): raw metadata size
end-4     | uint32 (big-endian): CRC32 of metadata &amp;#43; size&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;datasets&#34;&gt;Datasets&lt;/h2&gt;
&lt;p&gt;A dataset is a self-contained region within the block that stores profiling data for a specific service. Each dataset contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;a href=&#34;https://ganeshvernekar.com/blog/prometheus-tsdb-persistent-block-and-its-index/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;TSDB index&lt;/a&gt; mapping series labels to profiles&lt;/li&gt;
&lt;li&gt;Symbol data (&lt;code&gt;symbols.symdb&lt;/code&gt;) for stack traces and function names&lt;/li&gt;
&lt;li&gt;A &lt;a href=&#34;https://parquet.apache.org/docs/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Parquet&lt;/a&gt; table of profile samples&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Datasets are annotated with labels (such as &lt;code&gt;service_name&lt;/code&gt; and &lt;code&gt;profile_type&lt;/code&gt;) that allow the query path to select only the relevant datasets without reading the entire block.&lt;/p&gt;
&lt;p&gt;A separate tenant-wide dataset index allows queries that don&amp;rsquo;t target a specific service to locate the relevant datasets.&lt;/p&gt;
&lt;h2 id=&#34;block-metadata&#34;&gt;Block metadata&lt;/h2&gt;
&lt;p&gt;Block metadata is a protobuf-encoded structure that describes the block&amp;rsquo;s contents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Block ID (&lt;a href=&#34;https://github.com/ulid/spec&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;ULID&lt;/a&gt;), tenant, shard, compaction level, and time range&lt;/li&gt;
&lt;li&gt;A list of datasets with their byte offsets (table of contents), labels, and sizes&lt;/li&gt;
&lt;li&gt;A string table for deduplicating strings across the metadata entry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The metadata is stored both in the &lt;a href=&#34;../components/metastore/&#34;&gt;metastore&lt;/a&gt; index and embedded in the block object itself.&lt;/p&gt;
]]></content><description>&lt;h1 id="block-format">Block format&lt;/h1>
&lt;p>In Pyroscope v2, a block is a single object in object storage (&lt;code>block.bin&lt;/code>) containing data from one or more &lt;em>datasets&lt;/em>. Each dataset holds profiling data for a specific service and includes its own TSDB index, symbol data, and profile tables. Block metadata — stored in the &lt;a href="../components/metastore/">metastore&lt;/a> and embedded in the object itself — describes the datasets, their labels, and byte offsets within the object.&lt;/p></description></item><item><title>Pyroscope v2 deployment modes</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/deployment-modes/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/deployment-modes/</guid><content><![CDATA[&lt;h1 id=&#34;pyroscope-v2-deployment-modes&#34;&gt;Pyroscope v2 deployment modes&lt;/h1&gt;
&lt;p&gt;Pyroscope v2 can be deployed in different configurations depending on your scale and operational requirements.&lt;/p&gt;
&lt;h2 id=&#34;microservices-mode&#34;&gt;Microservices mode&lt;/h2&gt;
&lt;p&gt;In microservices mode, each component runs as a separate process. This is the recommended deployment for production environments at scale.&lt;/p&gt;
&lt;h3 id=&#34;benefits&#34;&gt;Benefits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Independent scaling&lt;/strong&gt;: Scale each component based on its specific load&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fault isolation&lt;/strong&gt;: Component failures don&amp;rsquo;t affect other components&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource optimization&lt;/strong&gt;: Allocate resources based on component needs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rolling updates&lt;/strong&gt;: Update components independently&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;components-to-deploy&#34;&gt;Components to deploy&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;Component&lt;/th&gt;
              &lt;th&gt;Instances&lt;/th&gt;
              &lt;th&gt;Stateful&lt;/th&gt;
              &lt;th&gt;Notes&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;Distributor&lt;/td&gt;
              &lt;td&gt;2&#43;&lt;/td&gt;
              &lt;td&gt;No&lt;/td&gt;
              &lt;td&gt;Scale based on ingestion rate&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Segment-writer&lt;/td&gt;
              &lt;td&gt;2&#43;&lt;/td&gt;
              &lt;td&gt;No&lt;/td&gt;
              &lt;td&gt;Scale based on ingestion rate&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Metastore&lt;/td&gt;
              &lt;td&gt;3 or 5&lt;/td&gt;
              &lt;td&gt;Yes&lt;/td&gt;
              &lt;td&gt;Odd number for Raft consensus&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Compaction-worker&lt;/td&gt;
              &lt;td&gt;2&#43;&lt;/td&gt;
              &lt;td&gt;No&lt;/td&gt;
              &lt;td&gt;Scale based on compaction backlog&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Query-frontend&lt;/td&gt;
              &lt;td&gt;2&#43;&lt;/td&gt;
              &lt;td&gt;No&lt;/td&gt;
              &lt;td&gt;Scale based on query load&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Query-backend&lt;/td&gt;
              &lt;td&gt;2&#43;&lt;/td&gt;
              &lt;td&gt;No&lt;/td&gt;
              &lt;td&gt;Scale based on query load&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h3 id=&#34;object-storage-requirement&#34;&gt;Object storage requirement&lt;/h3&gt;
&lt;p&gt;Microservices mode requires object storage (Amazon S3, Google Cloud Storage, Azure Blob Storage, or OpenStack Swift). Local filesystem storage is not supported in this mode.&lt;/p&gt;
&lt;h2 id=&#34;single-node-mode&#34;&gt;Single-node mode&lt;/h2&gt;
&lt;p&gt;For evaluation, development, or small-scale deployments, Pyroscope v2 can run as a single process with all components enabled.&lt;/p&gt;
&lt;h3 id=&#34;benefits-1&#34;&gt;Benefits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple deployment&lt;/strong&gt;: Single binary to run&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lower resource requirements&lt;/strong&gt;: Suitable for smaller workloads&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local storage option&lt;/strong&gt;: Can use local filesystem for storage&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;limitations&#34;&gt;Limitations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No high availability&lt;/strong&gt;: Single point of failure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited scalability&lt;/strong&gt;: Cannot scale individual components&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not recommended for production&lt;/strong&gt;: Use microservices mode for production workloads&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;kubernetes-deployment&#34;&gt;Kubernetes deployment&lt;/h2&gt;
&lt;p&gt;For Kubernetes deployments, use the Helm chart with v2 storage enabled.&lt;/p&gt;
&lt;h3 id=&#34;single-binary-mode&#34;&gt;Single-binary mode&lt;/h3&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm install pyroscope grafana/pyroscope --version 1.20.3 \
  --set architecture.storage.v1=false \
  --set architecture.storage.v2=true&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;microservices-mode-1&#34;&gt;Microservices mode&lt;/h3&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm install pyroscope grafana/pyroscope --version 1.20.3 \
  --set architecture.microservices.enabled=true \
  --set architecture.storage.v1=false \
  --set architecture.storage.v2=true&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For migrating an existing v1 deployment, refer to the &lt;a href=&#34;../migrate-from-v1/&#34;&gt;migration guide&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;helm-chart-considerations&#34;&gt;Helm chart considerations&lt;/h3&gt;
&lt;p&gt;When deploying on Kubernetes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configure persistent volumes for metastore nodes&lt;/li&gt;
&lt;li&gt;Set up object storage credentials&lt;/li&gt;
&lt;li&gt;Configure resource requests and limits for each component&lt;/li&gt;
&lt;li&gt;Set up ingress for distributor and query-frontend&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;storage-configuration&#34;&gt;Storage configuration&lt;/h2&gt;
&lt;h3 id=&#34;object-storage&#34;&gt;Object storage&lt;/h3&gt;
&lt;p&gt;Pyroscope v2 supports the following object storage backends:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Amazon S3&lt;/strong&gt;: Recommended for AWS deployments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud Storage&lt;/strong&gt;: Recommended for GCP deployments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Blob Storage&lt;/strong&gt;: Recommended for Azure deployments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenStack Swift&lt;/strong&gt;: For OpenStack environments&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;local-filesystem&#34;&gt;Local filesystem&lt;/h3&gt;
&lt;p&gt;Local filesystem storage is only supported for single-node deployments and is not suitable for production use in microservices mode.&lt;/p&gt;
&lt;h2 id=&#34;resource-planning&#34;&gt;Resource planning&lt;/h2&gt;
&lt;h3 id=&#34;metastore&#34;&gt;Metastore&lt;/h3&gt;
&lt;p&gt;The metastore is the only component requiring persistent storage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Disk space&lt;/strong&gt;: A few gigabytes, even at large scale&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory&lt;/strong&gt;: Benefits from keeping the index in memory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU&lt;/strong&gt;: Moderate usage for Raft consensus operations&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;stateless-components&#34;&gt;Stateless components&lt;/h3&gt;
&lt;p&gt;All other components are stateless and primarily need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU&lt;/strong&gt;: For data processing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory&lt;/strong&gt;: For in-flight data and query execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network&lt;/strong&gt;: For object storage access&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;object-storage-1&#34;&gt;Object storage&lt;/h3&gt;
&lt;p&gt;Plan for object storage costs based on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Write operations&lt;/strong&gt;: Segment flushes and compaction uploads&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read operations&lt;/strong&gt;: Query execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage&lt;/strong&gt;: Retained profile data&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="pyroscope-v2-deployment-modes">Pyroscope v2 deployment modes&lt;/h1>
&lt;p>Pyroscope v2 can be deployed in different configurations depending on your scale and operational requirements.&lt;/p>
&lt;h2 id="microservices-mode">Microservices mode&lt;/h2>
&lt;p>In microservices mode, each component runs as a separate process. This is the recommended deployment for production environments at scale.&lt;/p></description></item><item><title>Data distribution</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/data-distribution/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/data-distribution/</guid><content><![CDATA[&lt;h1 id=&#34;data-distribution&#34;&gt;Data distribution&lt;/h1&gt;
&lt;p&gt;Pyroscope v2 uses a sophisticated data distribution algorithm to place profiles across segment-writers. The algorithm ensures that profiles from the same application are co-located while maintaining even load distribution across the cluster.&lt;/p&gt;
&lt;h2 id=&#34;design-goals&#34;&gt;Design goals&lt;/h2&gt;
&lt;p&gt;The distribution algorithm is designed to achieve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data co-location&lt;/strong&gt;: Profiles from the same tenant service are stored together&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query performance&lt;/strong&gt;: Co-located data reduces the number of objects needed for queries&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;Even distribution&lt;/strong&gt;: Load is balanced across segment-writers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimal re-balancing&lt;/strong&gt;: Changes to the cluster minimize data movement&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;three-step-placement&#34;&gt;Three-step placement&lt;/h2&gt;
&lt;p&gt;The choice of placement for a profile involves a three-step process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tenant shards&lt;/strong&gt;: Find &lt;em&gt;m&lt;/em&gt; suitable locations from the total &lt;em&gt;N&lt;/em&gt; 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;: Find &lt;em&gt;n&lt;/em&gt; suitable locations from &lt;em&gt;m&lt;/em&gt; options using 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 &lt;em&gt;s&lt;/em&gt; from &lt;em&gt;n&lt;/em&gt; options.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;N&lt;/strong&gt; is the total number of shards in the deployment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;m&lt;/strong&gt; (tenant shard limit) is configured explicitly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;n&lt;/strong&gt; (dataset shard limit) is selected dynamically based on observed ingestion rate&lt;/li&gt;
&lt;/ul&gt;

  &lt;script type=&#34;text/javascript&#34; src=&#34;/web/mermaid.867770685db36193a268b63e8c85a9291badc92208742df0df7a384e9ff1619d.js&#34; integrity=&#34;sha256-hndwaF2zYZOiaLY&amp;#43;jIWpKRutySIIdC3w33o4Tp/xYZ0=&#34; defer&gt;&lt;/script&gt;
  

&lt;div class=&#34;mermaid-container&#34;&gt;
  &lt;pre class=&#34;mermaid&#34;&gt;
block-beta
    columns 15

    shards[&#34;ring&#34;]:2
    space
    shard_0[&#34;0&#34;]
    shard_1[&#34;1&#34;]
    shard_2[&#34;2&#34;]
    shard_3[&#34;3&#34;]
    shard_4[&#34;4&#34;]
    shard_5[&#34;5&#34;]
    shard_6[&#34;6&#34;]
    shard_7[&#34;7&#34;]
    shard_8[&#34;8&#34;]
    shard_9[&#34;9&#34;]
    shard_10[&#34;10&#34;]
    shard_11[&#34;11&#34;]

    tenant[&#34;tenant&#34;]:2
    space:4
    ts_3[&#34;3&#34;]
    ts_4[&#34;4&#34;]
    ts_5[&#34;5&#34;]
    ts_6[&#34;6&#34;]
    ts_7[&#34;7&#34;]
    ts_8[&#34;8&#34;]
    ts_9[&#34;9&#34;]
    space:2

    dataset[&#34;dataset&#34;]:2
    space:5
    ds_4[&#34;4&#34;]
    ds_5[&#34;5&#34;]
    ds_6[&#34;6&#34;]
    ds_7[&#34;7&#34;]
    space:4
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The tenant&amp;rsquo;s shard range starts at offset 3 with size 8&lt;/li&gt;
&lt;li&gt;The dataset&amp;rsquo;s shard range is a subset within the tenant&amp;rsquo;s range, starting at offset 1 with 4 shards&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;consistent-hashing&#34;&gt;Consistent hashing&lt;/h2&gt;
&lt;p&gt;Pyroscope uses &lt;a href=&#34;https://arxiv.org/pdf/1406.2294&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Jump consistent hash&lt;/a&gt; to select positions within each subring. This algorithm ensures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Balance&lt;/strong&gt;: Objects are evenly distributed among buckets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monotonicity&lt;/strong&gt;: When buckets are added, objects only move from old to new buckets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This minimizes data re-balancing when the cluster size changes.&lt;/p&gt;
&lt;h2 id=&#34;hot-spot-mitigation&#34;&gt;Hot spot mitigation&lt;/h2&gt;
&lt;p&gt;To prevent hot spots where many datasets end up on the same node, shards are mapped to instances through a separate mapping table. This mapping:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensures even distribution across nodes&lt;/li&gt;
&lt;li&gt;Is updated when nodes are added or removed&lt;/li&gt;
&lt;li&gt;Preserves existing mappings as much as possible&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&#34;mermaid-container&#34;&gt;
  &lt;pre class=&#34;mermaid&#34;&gt;
graph LR
    Distributor==&gt;SegmentWriter
    PlacementAgent-.-PlacementRules
    SegmentWriter--&gt;|metadata|PlacementManager
    SegmentWriter==&gt;|data|Segments
    PlacementManager-.-&gt;PlacementRules

    subgraph Distributor[&#34;distributor&#34;]
        PlacementAgent
    end

    subgraph Metastore[&#34;metastore&#34;]
        PlacementManager
    end

    subgraph ObjectStore[&#34;object store&#34;]
        PlacementRules(placement rules)
        Segments(segments)
    end

    subgraph SegmentWriter[&#34;segment-writer&#34;]
    end
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&#34;adaptive-load-balancing&#34;&gt;Adaptive load balancing&lt;/h2&gt;
&lt;p&gt;Due to the nature of continuous profiling, data can be distributed unevenly across profile series. To mitigate this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;By default: &lt;code&gt;fingerprint mod n&lt;/code&gt; is used as the distribution key&lt;/li&gt;
&lt;li&gt;When skew is detected: Switches to &lt;code&gt;random(n)&lt;/code&gt; distribution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This adaptive approach handles uneven data distribution while maintaining locality when possible.&lt;/p&gt;
&lt;h2 id=&#34;placement-management&#34;&gt;Placement management&lt;/h2&gt;
&lt;p&gt;The Placement Manager runs on the metastore leader and:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tracks dataset statistics from segment-writer metadata&lt;/li&gt;
&lt;li&gt;Builds placement rules at regular intervals&lt;/li&gt;
&lt;li&gt;Determines the number of shards for each dataset&lt;/li&gt;
&lt;li&gt;Decides the load balancing strategy (fingerprint mod vs round robin)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Placement rules are stored in object storage and fetched by distributors. Since actual data re-balancing is not performed, placement rules don&amp;rsquo;t need to be synchronized in real-time.&lt;/p&gt;
&lt;h2 id=&#34;failure-handling&#34;&gt;Failure handling&lt;/h2&gt;
&lt;p&gt;If a segment-writer fails:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The distributor selects the next suitable segment-writer from available options.&lt;/li&gt;
&lt;li&gt;The shard identifier is specified explicitly in the request.&lt;/li&gt;
&lt;li&gt;Data locality is maintained even during transient failures.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Two requests with the same distribution key may occasionally end up in different shards, but this is expected to be rare.&lt;/p&gt;
&lt;h2 id=&#34;implementation-details&#34;&gt;Implementation details&lt;/h2&gt;
&lt;p&gt;For detailed implementation information, including the full algorithm specification and shard mapping procedures, refer to the &lt;a href=&#34;https://github.com/grafana/pyroscope/blob/main/pkg/segmentwriter/client/distributor/README.md&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;internal documentation&lt;/a&gt;.&lt;/p&gt;
]]></content><description>&lt;h1 id="data-distribution">Data distribution&lt;/h1>
&lt;p>Pyroscope v2 uses a sophisticated data distribution algorithm to place profiles across segment-writers. The algorithm ensures that profiles from the same application are co-located while maintaining even load distribution across the cluster.&lt;/p></description></item><item><title>Compaction</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/compaction/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/compaction/</guid><content><![CDATA[&lt;h1 id=&#34;compaction&#34;&gt;Compaction&lt;/h1&gt;
&lt;p&gt;Compaction is the process of merging multiple small segments into larger, optimized blocks. This is essential for maintaining query performance and controlling metadata index size.&lt;/p&gt;
&lt;h2 id=&#34;why-compaction-matters&#34;&gt;Why compaction matters&lt;/h2&gt;
&lt;p&gt;The ingestion pipeline creates many small segments—potentially millions of objects per hour at scale. Without compaction:&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;API costs&lt;/strong&gt;: More 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;: Impacts both read and write paths&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;p&gt;Compaction in Pyroscope v2 is coordinated by the &lt;a href=&#34;../components/metastore/&#34;&gt;metastore&lt;/a&gt; and executed by &lt;a href=&#34;../components/compaction-worker/&#34;&gt;compaction-workers&lt;/a&gt;.&lt;/p&gt;

  &lt;script type=&#34;text/javascript&#34; src=&#34;/web/mermaid.867770685db36193a268b63e8c85a9291badc92208742df0df7a384e9ff1619d.js&#34; integrity=&#34;sha256-hndwaF2zYZOiaLY&amp;#43;jIWpKRutySIIdC3w33o4Tp/xYZ0=&#34; defer&gt;&lt;/script&gt;
  

&lt;div class=&#34;mermaid-container&#34;&gt;
  &lt;pre class=&#34;mermaid&#34;&gt;
sequenceDiagram
    participant W as Compaction Worker
    participant M as Metastore
    participant S as Object Storage

    loop Continuous
        W-&gt;&gt;M: Poll for jobs
        M-&gt;&gt;W: Assign job with source blocks
        W-&gt;&gt;S: Download source segments
        W-&gt;&gt;W: Merge segments into block
        W-&gt;&gt;S: Upload compacted block
        W-&gt;&gt;M: Report completion
        M-&gt;&gt;M: Update metadata index
    end
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&#34;compaction-service&#34;&gt;Compaction service&lt;/h2&gt;
&lt;p&gt;The compaction service runs within the metastore and is responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Job planning&lt;/strong&gt;: Creating compaction jobs when enough segments are available&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job scheduling&lt;/strong&gt;: Assigning jobs to workers based on capacity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job tracking&lt;/strong&gt;: Monitoring progress and handling failures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Index updates&lt;/strong&gt;: Replacing source block entries with compacted block entries&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;raft-consistency&#34;&gt;Raft consistency&lt;/h3&gt;
&lt;p&gt;The compaction service relies on Raft to guarantee consistency:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Plan preparation&lt;/strong&gt;: The leader prepares job state changes (read-only).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan proposal&lt;/strong&gt;: Changes are committed to the Raft log.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State update&lt;/strong&gt;: All replicas apply the changes atomically.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This ensures all replicas maintain consistent views of compaction state.&lt;/p&gt;
&lt;h2 id=&#34;job-planner&#34;&gt;Job planner&lt;/h2&gt;
&lt;p&gt;The job planner maintains a queue of blocks eligible for compaction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Queue structure&lt;/strong&gt;: FIFO queue, segmented by tenant, shard, and level&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job creation&lt;/strong&gt;: Jobs are created when enough blocks are queued&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boundaries&lt;/strong&gt;: Compaction never crosses tenant, shard, or level boundaries&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;data-layout&#34;&gt;Data layout&lt;/h3&gt;
&lt;p&gt;Profiling data from each service is stored as a separate dataset within a block. During compaction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Matching datasets from source 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;li&gt;Output block contains optimized, non-overlapping datasets&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;job-scheduler&#34;&gt;Job scheduler&lt;/h2&gt;
&lt;p&gt;The scheduler uses a &lt;strong&gt;Small Job First&lt;/strong&gt; strategy:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lower-level blocks are prioritized (smaller, affect read amplification more).&lt;/li&gt;
&lt;li&gt;Within a level, unassigned jobs are processed first.&lt;/li&gt;
&lt;li&gt;Jobs with fewer failures are prioritized.&lt;/li&gt;
&lt;li&gt;Jobs with earlier lease expiration are considered first.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;adaptive-capacity&#34;&gt;Adaptive capacity&lt;/h3&gt;
&lt;p&gt;Workers specify available capacity when polling for jobs. The scheduler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates jobs based on reported worker capacity&lt;/li&gt;
&lt;li&gt;Balances queue size with worker utilization&lt;/li&gt;
&lt;li&gt;Adapts to available resources automatically&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;job-ownership&#34;&gt;Job ownership&lt;/h2&gt;
&lt;p&gt;Jobs are assigned using a lease-based model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lease duration&lt;/strong&gt;: Workers are granted ownership for a limited time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fencing tokens&lt;/strong&gt;: Raft log index serves as a unique token&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lease refresh&lt;/strong&gt;: Workers must refresh leases before expiration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reassignment&lt;/strong&gt;: Expired leases allow job reassignment&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;failure-handling&#34;&gt;Failure handling&lt;/h3&gt;
&lt;p&gt;When a worker fails:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The job lease expires.&lt;/li&gt;
&lt;li&gt;The metastore detects the expired lease.&lt;/li&gt;
&lt;li&gt;The job is reassigned to another worker.&lt;/li&gt;
&lt;li&gt;Source blocks remain until compaction succeeds.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Jobs that repeatedly fail are deprioritized to prevent blocking the queue.&lt;/p&gt;
&lt;h2 id=&#34;job-status-lifecycle&#34;&gt;Job status lifecycle&lt;/h2&gt;

&lt;div class=&#34;mermaid-container&#34;&gt;
  &lt;pre class=&#34;mermaid&#34;&gt;
stateDiagram-v2
    [*] --&gt; Unassigned : Create Job
    Unassigned --&gt; InProgress : Assign Job
    InProgress --&gt; Success : Job Completed
    InProgress --&gt; LeaseExpired: Job Lease Expires
    LeaseExpired: Abandoned Job

    LeaseExpired --&gt; Excluded: Failure Threshold Exceeded
    Excluded: Faulty Job

    Success --&gt; [*] : Remove Job from Schedule
    LeaseExpired --&gt; InProgress : Reassign Job
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&#34;performance-characteristics&#34;&gt;Performance characteristics&lt;/h2&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;li&gt;&lt;strong&gt;Horizontal scaling&lt;/strong&gt;: Add more workers to handle compaction backlog&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Priority-based&lt;/strong&gt;: Smaller blocks compacted first for fastest impact&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;block-deletion&#34;&gt;Block deletion&lt;/h2&gt;
&lt;p&gt;After successful compaction:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tombstone creation&lt;/strong&gt;: Source blocks are marked for deletion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delay period&lt;/strong&gt;: Blocks are retained to allow in-flight queries to complete.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard deletion&lt;/strong&gt;: After the delay, source blocks are removed from storage.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This two-phase deletion prevents query failures during compaction.&lt;/p&gt;
&lt;h2 id=&#34;implementation-details&#34;&gt;Implementation details&lt;/h2&gt;
&lt;p&gt;For detailed implementation information, including job scheduling algorithms and lease management, refer to the &lt;a href=&#34;https://github.com/grafana/pyroscope/blob/main/pkg/metastore/compaction/README.md&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;internal documentation&lt;/a&gt;.&lt;/p&gt;
]]></content><description>&lt;h1 id="compaction">Compaction&lt;/h1>
&lt;p>Compaction is the process of merging multiple small segments into larger, optimized blocks. This is essential for maintaining query performance and controlling metadata index size.&lt;/p></description></item><item><title>Metadata index</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/metadata-index/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/metadata-index/</guid><content><![CDATA[&lt;h1 id=&#34;metadata-index&#34;&gt;Metadata index&lt;/h1&gt;
&lt;p&gt;The metadata index stores information about all data objects (blocks and segments) in object storage. It is maintained by the &lt;a href=&#34;../components/metastore/&#34;&gt;metastore&lt;/a&gt; service and provides fast lookups for query planning.&lt;/p&gt;
&lt;h2 id=&#34;purpose&#34;&gt;Purpose&lt;/h2&gt;
&lt;p&gt;The metadata index enables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Block discovery&lt;/strong&gt;: Finding blocks that match a query&amp;rsquo;s time range and filters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query planning&lt;/strong&gt;: Identifying exactly which objects need to be read&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compaction coordination&lt;/strong&gt;: Tracking which blocks can be compacted together&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retention enforcement&lt;/strong&gt;: Managing block lifecycle and cleanup&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;implementation&#34;&gt;Implementation&lt;/h2&gt;
&lt;p&gt;The index is implemented using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BoltDB&lt;/strong&gt;: Key-value store for metadata entries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Raft&lt;/strong&gt;: Consensus protocol for replication and consistency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;BoltDB was chosen for its simplicity and efficiency with a single writer and concurrent readers. For better performance, the index can be stored on an in-memory volume since it&amp;rsquo;s recovered from the Raft log on startup.&lt;/p&gt;
&lt;h2 id=&#34;block-metadata&#34;&gt;Block metadata&lt;/h2&gt;
&lt;p&gt;Each block in object storage has a corresponding metadata entry containing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Block ID&lt;/strong&gt;: Unique identifier (ULID) based on creation time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tenant&lt;/strong&gt;: The tenant that owns the data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shard&lt;/strong&gt;: The shard assignment for data distribution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time range&lt;/strong&gt;: Start and end timestamps of the data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datasets&lt;/strong&gt;: Information about contained datasets (services)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;dataset-information&#34;&gt;Dataset information&lt;/h3&gt;
&lt;p&gt;Each dataset within a block includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service name&lt;/strong&gt;: The &lt;code&gt;service_name&lt;/code&gt; label identifying the application&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Labels&lt;/strong&gt;: Additional metadata labels for filtering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Table of contents&lt;/strong&gt;: Offsets to data sections within the dataset&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;index-structure&#34;&gt;Index structure&lt;/h2&gt;
&lt;p&gt;The index is partitioned by time, with each partition covering a 6-hour window:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;Partition (6h window)
├── Tenant A
│   ├── Shard 0
│   ├── Shard 1
│   └── Shard N
└── Tenant B
    ├── Shard 0
    └── Shard N&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Within each shard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Block entries&lt;/strong&gt;: Key-value pairs (block ID → metadata)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;String table&lt;/strong&gt;: Deduplicated strings for space efficiency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shard index&lt;/strong&gt;: Time range for efficient filtering&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;index-writes&#34;&gt;Index writes&lt;/h2&gt;
&lt;p&gt;Index writes are performed by &lt;a href=&#34;../components/segment-writer/&#34;&gt;segment-writers&lt;/a&gt; when new segments are created:&lt;/p&gt;

  &lt;script type=&#34;text/javascript&#34; src=&#34;/web/mermaid.867770685db36193a268b63e8c85a9291badc92208742df0df7a384e9ff1619d.js&#34; integrity=&#34;sha256-hndwaF2zYZOiaLY&amp;#43;jIWpKRutySIIdC3w33o4Tp/xYZ0=&#34; defer&gt;&lt;/script&gt;
  

&lt;div class=&#34;mermaid-container&#34;&gt;
  &lt;pre class=&#34;mermaid&#34;&gt;
sequenceDiagram
    participant SW as segment-writer
    participant M as Metastore
    participant R as Raft
    participant I as Index

    SW-&gt;&gt;M: AddBlock(metadata)
    M-&gt;&gt;R: Propose ADD_BLOCK
    R-&gt;&gt;R: Commit to log
    R-&gt;&gt;I: Insert block
    I--&gt;&gt;R: Success
    R--&gt;&gt;M: Committed
    M--&gt;&gt;SW: Success
&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&#34;tombstone-protection&#34;&gt;Tombstone protection&lt;/h3&gt;
&lt;p&gt;Before adding a block, the index checks for tombstones to prevent re-adding blocks that were already compacted. This handles cases where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A writer&amp;rsquo;s response was lost but the block was added&lt;/li&gt;
&lt;li&gt;The block was already compacted before the retry&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;index-queries&#34;&gt;Index queries&lt;/h2&gt;
&lt;p&gt;Queries use the linearizable read pattern to ensure consistency:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Read index request&lt;/strong&gt;: Query asks for the current commit index.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leader check&lt;/strong&gt;: Verifies the current leader.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wait for commit&lt;/strong&gt;: Waits until the commit index is applied locally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read state&lt;/strong&gt;: Reads from the local state machine.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This allows both leader and follower replicas to serve queries while ensuring they see the latest committed state.&lt;/p&gt;
&lt;h3 id=&#34;query-types&#34;&gt;Query types&lt;/h3&gt;
&lt;p&gt;The index supports two main query patterns:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metadata queries&lt;/strong&gt;: Find blocks matching criteria&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;Query:
  - Time range: [start, end]
  - Tenant: [&amp;#34;tenant-1&amp;#34;]
  - Labels: {service_name=&amp;#34;frontend&amp;#34;}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Label queries&lt;/strong&gt;: List available labels without reading data&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;Query:
  - Return: distinct values for &amp;#34;profile_type&amp;#34; label
  - Filter: {service_name=&amp;#34;frontend&amp;#34;}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;retention&#34;&gt;Retention&lt;/h2&gt;
&lt;h3 id=&#34;compaction-based-retention&#34;&gt;Compaction-based retention&lt;/h3&gt;
&lt;p&gt;When blocks are compacted:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Source block entries are replaced with compacted block entry.&lt;/li&gt;
&lt;li&gt;Tombstones are created for source blocks.&lt;/li&gt;
&lt;li&gt;Tombstones trigger eventual deletion of source objects.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;time-based-retention&#34;&gt;Time-based retention&lt;/h3&gt;
&lt;p&gt;Retention policies delete entire partitions based on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Block creation time&lt;/strong&gt;: Primary retention criteria&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data timestamps&lt;/strong&gt;: Blocks are only deleted if data is also past retention&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Retention policies are tenant-specific and configurable per tenant.&lt;/p&gt;
&lt;h2 id=&#34;cleanup-process&#34;&gt;Cleanup process&lt;/h2&gt;
&lt;p&gt;The cleaner runs on the Raft leader and:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lists partitions and applies retention policy.&lt;/li&gt;
&lt;li&gt;Identifies partitions to delete.&lt;/li&gt;
&lt;li&gt;Proposes deletion to Raft.&lt;/li&gt;
&lt;li&gt;Creates tombstones for affected blocks.&lt;/li&gt;
&lt;li&gt;Tombstones are processed during compaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;mermaid-container&#34;&gt;
  &lt;pre class=&#34;mermaid&#34;&gt;
sequenceDiagram
    participant C as Cleaner
    participant M as Metastore
    participant R as Raft
    participant I as Index

    C-&gt;&gt;M: TruncateIndex(policy)
    M-&gt;&gt;I: List partitions
    I--&gt;&gt;M: Partition list
    M-&gt;&gt;M: Apply retention policy
    M-&gt;&gt;R: Propose TRUNCATE_INDEX
    R-&gt;&gt;I: Delete partitions
    R-&gt;&gt;I: Add tombstones
    R--&gt;&gt;M: Committed
    M--&gt;&gt;C: Success
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&#34;performance&#34;&gt;Performance&lt;/h2&gt;
&lt;h3 id=&#34;caching&#34;&gt;Caching&lt;/h3&gt;
&lt;p&gt;The index uses several caches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shard cache&lt;/strong&gt;: Keeps shard indexes and string tables in memory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block cache&lt;/strong&gt;: Stores decoded metadata entries&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;scalability&#34;&gt;Scalability&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage requirements&lt;/strong&gt;: A few gigabytes even at large scale&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query performance&lt;/strong&gt;: Sub-millisecond lookups with caching&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write throughput&lt;/strong&gt;: Limited by Raft consensus, typically sufficient for ingestion rates&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;implementation-details&#34;&gt;Implementation details&lt;/h2&gt;
&lt;p&gt;For detailed implementation information, including the protobuf schema and internal structures, refer to the &lt;a href=&#34;https://github.com/grafana/pyroscope/blob/main/pkg/metastore/index/README.md&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;internal documentation&lt;/a&gt;.&lt;/p&gt;
]]></content><description>&lt;h1 id="metadata-index">Metadata index&lt;/h1>
&lt;p>The metadata index stores information about all data objects (blocks and segments) in object storage. It is maintained by the &lt;a href="../components/metastore/">metastore&lt;/a> service and provides fast lookups for query planning.&lt;/p></description></item><item><title>Migrate from v1 to v2 storage using Helm</title><link>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/migrate-from-v1/</link><pubDate>Mon, 20 Apr 2026 09:02:32 +0000</pubDate><guid>https://grafana.com/docs/pyroscope/v2.0.x/reference-pyroscope-v2-architecture/migrate-from-v1/</guid><content><![CDATA[&lt;h1 id=&#34;migrate-from-v1-to-v2-storage-using-helm&#34;&gt;Migrate from v1 to v2 storage using Helm&lt;/h1&gt;
&lt;p&gt;This guide walks you through migrating a Pyroscope installation from v1 to v2 storage architecture using the Helm chart. The migration uses a phased approach that lets you run both storage backends simultaneously before fully cutting over to v2.&lt;/p&gt;
&lt;p&gt;For an overview of what changed in v2 and why, refer to &lt;a href=&#34;../about-pyroscope-v2-architecture/&#34;&gt;About the v2 architecture&lt;/a&gt; and &lt;a href=&#34;../design-motivation/&#34;&gt;Design motivation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before starting the migration, make sure you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm chart version 1.19.2 or later&lt;/strong&gt;. Verify with:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm list -n pyroscope -f pyroscope&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Check that the &lt;code&gt;CHART&lt;/code&gt; column shows &lt;code&gt;pyroscope-1.19.2&lt;/code&gt; or higher. If your chart is older, upgrade it first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pyroscope running on v1 storage via Helm.&lt;/strong&gt; Verify with:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm get values -n pyroscope pyroscope -o yaml --all | grep -A8 &amp;#39;storage:&amp;#39; | grep -E &amp;#39;v1:|v2:&amp;#39;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see &lt;code&gt;v1: true&lt;/code&gt; and &lt;code&gt;v2: false&lt;/code&gt;. If you see &lt;code&gt;v2: true&lt;/code&gt;, your installation is already using v2 or is mid-migration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Object storage configured.&lt;/strong&gt; v2 writes directly to object storage — it doesn&amp;rsquo;t use local disk for block storage. If you haven&amp;rsquo;t configured object storage yet, add it to your Helm values. For example, for S3:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;YAML&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;pyroscope:
  structuredConfig:
    storage:
      backend: s3
      s3:
        endpoint: s3.us-east-1.amazonaws.com
        bucket_name: pyroscope-data
        access_key_id: &amp;#34;${AWS_ACCESS_KEY_ID}&amp;#34;
        secret_access_key: &amp;#34;${AWS_SECRET_ACCESS_KEY}&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For other backends (GCS, Azure, Swift), refer to 
    &lt;a href=&#34;/docs/pyroscope/v2.0.x/configure-server/storage/configure-object-storage-backend/&#34;&gt;Configure object storage backend&lt;/a&gt;. You can also use the &lt;code&gt;filesystem&lt;/code&gt; backend, but in Kubernetes, this requires a &lt;code&gt;ReadWriteMany&lt;/code&gt; volume that is shared across all pods.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;helm&lt;/code&gt; CLI access&lt;/strong&gt; to your cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;The examples in this guide assume Pyroscope is installed in the &lt;code&gt;pyroscope&lt;/code&gt; namespace with the release name &lt;code&gt;pyroscope&lt;/code&gt;. Adjust the &lt;code&gt;-n&lt;/code&gt; namespace flag and release name in &lt;code&gt;helm&lt;/code&gt; and &lt;code&gt;kubectl&lt;/code&gt; commands if your installation differs.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;migration-overview&#34;&gt;Migration overview&lt;/h2&gt;
&lt;p&gt;The migration has three phases:&lt;/p&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;Phase&lt;/th&gt;
              &lt;th&gt;What happens&lt;/th&gt;
              &lt;th&gt;Reversible?&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;1. Dual ingest&lt;/td&gt;
              &lt;td&gt;v2 components deploy alongside v1. Writes go to both storage backends.&lt;/td&gt;
              &lt;td&gt;Yes&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;2. Validate&lt;/td&gt;
              &lt;td&gt;Run both backends for at least 24 hours. Verify v2 data and compaction.&lt;/td&gt;
              &lt;td&gt;Yes&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;3. Remove v1&lt;/td&gt;
              &lt;td&gt;Remove v1 components. Only v2 serves reads and writes.&lt;/td&gt;
              &lt;td&gt;Partial&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;p&gt;The steps below are specific to your deployment mode. Follow the section that matches your installation.&lt;/p&gt;
&lt;h2 id=&#34;single-binary-mode&#34;&gt;Single-binary mode&lt;/h2&gt;
&lt;h3 id=&#34;phase-1-enable-dual-ingest&#34;&gt;Phase 1: Enable dual ingest&lt;/h3&gt;
&lt;p&gt;In this phase, the single-binary process enables the v2 storage modules alongside v1. Writes go to both storage backends simultaneously, and the read path serves data from both v1 and v2.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm upgrade -n pyroscope pyroscope grafana/pyroscope --version 2.0.0 \
  --reset-then-reuse-values \
  --set architecture.storage.v1=true \
  --set architecture.storage.v2=true&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;--reuse-values&lt;/code&gt; flag preserves your existing configuration. Alternatively, you can pass your values file with &lt;code&gt;-f values.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&#34;verify-phase-1&#34;&gt;Verify Phase 1&lt;/h4&gt;
&lt;p&gt;After the upgrade completes, check that the pod has restarted and is running:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl get pods -n pyroscope -l app.kubernetes.io/instance=pyroscope&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Check the Helm release notes for migration status:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm get notes -n pyroscope pyroscope&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see output similar to:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;# Pyroscope v2 Migration is active

Write traffic will be written to:
- 100% v1: ingester
- 100% v2: segment-writer

Read traffic is served from v2 read path from as soon as data was first ingested to v2.&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Also verify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;metastore&lt;/strong&gt; raft has initialized. Check the pod logs for a message like &lt;code&gt;entering leader state&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl logs -n pyroscope -l app.kubernetes.io/instance=pyroscope --tail=500 | grep -i &amp;#34;entering leader state&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;segment-writer&lt;/strong&gt; ring is healthy:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl port-forward -n pyroscope svc/pyroscope 4040:4040 &amp;amp;
PF_PID=$!
sleep 2
curl -s http://localhost:4040/ring-segment-writer | grep -o &amp;#39;ACTIVE&amp;#39; | wc -l
kill $PF_PID&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In single-binary mode, the count should be 1.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;phase-2-validate-v2-is-working&#34;&gt;Phase 2: Validate v2 is working&lt;/h3&gt;
&lt;p&gt;Run both storage backends simultaneously for at least 24 hours before proceeding. During this time, you should be able to query data ingested to v2.&lt;/p&gt;
&lt;h4 id=&#34;verify-data-is-being-written-to-v2&#34;&gt;Verify data is being written to v2&lt;/h4&gt;
&lt;p&gt;Query recent profiling data. The v2 read path should serve data ingested after Phase 1. You can use &lt;code&gt;profilecli&lt;/code&gt;, the Pyroscope UI, or the API to query profiles from the last hour and confirm results are returned:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl port-forward -n pyroscope svc/pyroscope 4040:4040 &amp;amp;
PF_PID=$!
sleep 2
profilecli query series --url http://localhost:4040 --from &amp;#34;now-1h&amp;#34; --to &amp;#34;now&amp;#34;
kill $PF_PID&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see series labels for the profiling data being ingested. If no results are returned, check the distributor and segment-writer logs for errors.&lt;/p&gt;
&lt;h4 id=&#34;verify-v2-compaction-is-running&#34;&gt;Verify v2 compaction is running&lt;/h4&gt;
&lt;p&gt;The compaction-worker compacts segments through the L0 → L1 → L2 levels. Verify that compaction jobs are completing:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl logs -n pyroscope -l app.kubernetes.io/instance=pyroscope --tail=500 | grep &amp;#34;compaction finished successfully&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see log lines like:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;msg=&amp;#34;compaction finished successfully&amp;#34; input_blocks=20 output_blocks=1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Compaction typically starts within minutes of ingestion, the first block is created once enough segments accumulate for a shard.&lt;/p&gt;
&lt;h4 id=&#34;verify-error-rates-are-stable&#34;&gt;Verify error rates are stable&lt;/h4&gt;
&lt;p&gt;Check that write and read error rates haven&amp;rsquo;t increased since enabling v2. If you have Prometheus metrics configured:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;promql&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-promql&#34;&gt;sum(rate(pyroscope_request_duration_seconds_count{status_code=~&amp;#34;5..&amp;#34;}[5m]))&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Error rates should be zero or negligible. Compare against pre-migration baselines to confirm no regression.&lt;/p&gt;
&lt;h3 id=&#34;phase-3-switch-to-v2-storage&#34;&gt;Phase 3: Switch to v2 storage&lt;/h3&gt;
&lt;p&gt;Once you&amp;rsquo;re confident that v2 is working correctly and you no longer need to query data ingested before Phase 1, you can disable the v1 storage.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-warning&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Warning&lt;/p&gt;&lt;p&gt;After this step, data ingested before Phase 1 is no longer queryable through Pyroscope. The data still exists in object storage, but the v1 storage modules that serve it will be disabled. Make sure you don&amp;rsquo;t need to query historical data from before the migration started.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;


&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm upgrade -n pyroscope pyroscope grafana/pyroscope --version 2.0.0 \
  --reset-then-reuse-values \
  --set architecture.storage.v1=false \
  --set architecture.storage.v2=true&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&#34;verify-phase-3&#34;&gt;Verify Phase 3&lt;/h4&gt;
&lt;p&gt;Verify that the Pyroscope pod has restarted:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl get pods -n pyroscope -l app.kubernetes.io/instance=pyroscope&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Verify that queries still return data:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl port-forward -n pyroscope svc/pyroscope 4040:4040 &amp;amp;
PF_PID=$!
sleep 2
profilecli query series --url http://localhost:4040 --from &amp;#34;now-1h&amp;#34; --to &amp;#34;now&amp;#34;
kill $PF_PID&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see series labels for recent profiling data. You can also open the Pyroscope UI at &lt;code&gt;http://localhost:4040&lt;/code&gt; and verify that you can query recent profiles. An empty or errored UI indicates a problem — see &lt;a href=&#34;#rollback&#34;&gt;Rollback&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;microservices-mode&#34;&gt;Microservices mode&lt;/h2&gt;
&lt;p&gt;If you deployed Pyroscope using the &lt;code&gt;values-micro-services.yaml&lt;/code&gt; file as described in 
    &lt;a href=&#34;/docs/pyroscope/v2.0.x/deploy-kubernetes/helm/&#34;&gt;Deploy on Kubernetes&lt;/a&gt;, follow the steps below.&lt;/p&gt;
&lt;h3 id=&#34;phase-1-deploy-v2-components-alongside-v1&#34;&gt;Phase 1: Deploy v2 components alongside v1&lt;/h3&gt;
&lt;p&gt;In this phase, you deploy the v2 components (segment-writer, metastore, compaction-worker, query-backend) alongside your existing v1 installation. The distributor starts writing to both storage backends simultaneously. The read path serves data from both v1 and v2.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm upgrade -n pyroscope pyroscope grafana/pyroscope \
  --reuse-values \
  --set architecture.microservices.enabled=true \
  --set architecture.storage.v1=true \
  --set architecture.storage.v2=true&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;--reuse-values&lt;/code&gt; flag preserves your existing configuration. Alternatively, you can pass your values file with &lt;code&gt;-f values.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&#34;verify-phase-1-1&#34;&gt;Verify Phase 1&lt;/h4&gt;
&lt;p&gt;After the upgrade completes, check that the new components are running:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl get pods -n pyroscope -l app.kubernetes.io/instance=pyroscope&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Check the Helm release notes for migration status:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm get notes -n pyroscope pyroscope&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see output similar to:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;# Pyroscope v2 Migration is active

Write traffic will be written to:
- 100% v1: ingester
- 100% v2: segment-writer

Read traffic is served from v2 read path from as soon as data was first ingested to v2.&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Also verify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;metastore&lt;/strong&gt; raft cluster has elected a leader:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl logs -n pyroscope -l app.kubernetes.io/component=metastore --tail=500 | grep -i &amp;#34;entering leader state&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;segment-writer&lt;/strong&gt; ring is healthy. All instances should show as &lt;code&gt;ACTIVE&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl port-forward -n pyroscope svc/pyroscope-distributor 4040:4040 &amp;amp;
PF_PID=$!
sleep 2
curl -s http://localhost:4040/ring-segment-writer | grep -o &amp;#39;ACTIVE&amp;#39; | wc -l
kill $PF_PID&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The count should match the number of segment-writer instances.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;phase-2-validate-v2-is-working-1&#34;&gt;Phase 2: Validate v2 is working&lt;/h3&gt;
&lt;p&gt;Run both storage backends simultaneously for at least 24 hours before proceeding. During this time, you should be able to query data ingested to v2.&lt;/p&gt;
&lt;h4 id=&#34;verify-data-is-being-written-to-v2-1&#34;&gt;Verify data is being written to v2&lt;/h4&gt;
&lt;p&gt;Query recent profiling data. The v2 read path should serve data ingested after Phase 1. You can use &lt;code&gt;profilecli&lt;/code&gt;, the Pyroscope UI, or the API to query profiles from the last hour and confirm results are returned:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl port-forward -n pyroscope svc/pyroscope-query-frontend 4040:4040 &amp;amp;
PF_PID=$!
sleep 2
profilecli query series --url http://localhost:4040 --from &amp;#34;now-1h&amp;#34; --to &amp;#34;now&amp;#34;
kill $PF_PID&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see series labels for the profiling data being ingested. If no results are returned, check the distributor and segment-writer logs for errors.&lt;/p&gt;
&lt;h4 id=&#34;verify-v2-compaction-is-running-1&#34;&gt;Verify v2 compaction is running&lt;/h4&gt;
&lt;p&gt;The compaction-worker compacts segments through the L0 → L1 → L2 levels. Verify that compaction jobs are completing:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl logs -n pyroscope -l app.kubernetes.io/component=compaction-worker --tail=500 | grep &amp;#34;compaction finished successfully&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see log lines like:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;msg=&amp;#34;compaction finished successfully&amp;#34; input_blocks=20 output_blocks=1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Compaction typically starts within minutes of ingestion, the first block is created once enough segments accumulate for a shard.&lt;/p&gt;
&lt;h4 id=&#34;verify-error-rates-are-stable-1&#34;&gt;Verify error rates are stable&lt;/h4&gt;
&lt;p&gt;Check that write and read error rates haven&amp;rsquo;t increased since enabling v2. If you have Prometheus metrics configured, query error rates per component:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;promql&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-promql&#34;&gt;# Server-side errors by component (distributor, segment-writer, query-frontend, query-backend, etc.)
sum by (component) (rate(pyroscope_request_duration_seconds_count{status_code=~&amp;#34;5..&amp;#34;}[5m]))&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;All components should show zero or negligible error rates. Compare against pre-migration baselines to confirm no regression.&lt;/p&gt;
&lt;h3 id=&#34;phase-3-remove-v1-components&#34;&gt;Phase 3: Remove v1 components&lt;/h3&gt;
&lt;p&gt;Once you&amp;rsquo;re confident that v2 is working correctly and you no longer need to query data ingested before Phase 1, you can remove the v1 components.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-warning&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Warning&lt;/p&gt;&lt;p&gt;After this step, data ingested before Phase 1 is no longer queryable through Pyroscope. The data still exists in object storage, but the v1 read path components (ingester, store-gateway, querier) that serve it will be removed. Make sure you don&amp;rsquo;t need to query historical data from before the migration started.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;


&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm upgrade -n pyroscope pyroscope grafana/pyroscope \
  --reuse-values \
  --set architecture.storage.v1=false \
  --set architecture.storage.v2=true&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Helm chart automatically removes v1-only components (ingester, compactor, store-gateway, querier, query-scheduler) when &lt;code&gt;architecture.storage.v1&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt;, even if your values file or &lt;code&gt;--reuse-values&lt;/code&gt; state still contains overrides for those components.&lt;/p&gt;
&lt;h4 id=&#34;verify-phase-3-1&#34;&gt;Verify Phase 3&lt;/h4&gt;
&lt;p&gt;Check that v1 components have been removed and v2 is serving all traffic:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;# v1 components (ingester, store-gateway, querier, compactor, query-scheduler) should be gone
kubectl get pods -n pyroscope -l app.kubernetes.io/instance=pyroscope&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Verify that queries still return data:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl port-forward -n pyroscope svc/pyroscope-query-frontend 4040:4040 &amp;amp;
PF_PID=$!
sleep 2
profilecli query series --url http://localhost:4040 --from &amp;#34;now-1h&amp;#34; --to &amp;#34;now&amp;#34;
kill $PF_PID&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You should see series labels for recent profiling data. You can also open the Pyroscope UI at &lt;code&gt;http://localhost:4040&lt;/code&gt; and verify that you can query recent profiles. An empty or errored UI indicates a problem — see &lt;a href=&#34;#rollback&#34;&gt;Rollback&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;rollback&#34;&gt;Rollback&lt;/h2&gt;
&lt;h3 id=&#34;during-phase-1-or-phase-2&#34;&gt;During Phase 1 or Phase 2&lt;/h3&gt;
&lt;p&gt;Rolling back is straightforward — set &lt;code&gt;architecture.storage.v2=false&lt;/code&gt; to remove the v2 components and return to v1-only:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm upgrade -n pyroscope pyroscope grafana/pyroscope \
  --reuse-values \
  --set architecture.storage.v1=true \
  --set architecture.storage.v2=false&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Data written to v2 during the dual-ingest period is orphaned but doesn&amp;rsquo;t affect v1 operation.&lt;/p&gt;
&lt;h3 id=&#34;during-or-after-phase-3&#34;&gt;During or after Phase 3&lt;/h3&gt;
&lt;p&gt;If you removed v1 components (Phase 3), rolling back requires redeploying them:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;helm upgrade -n pyroscope pyroscope grafana/pyroscope \
  --reuse-values \
  --set architecture.storage.v1=true \
  --set architecture.storage.v2=true&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This returns you to dual-ingest mode (Phase 1). Note that any data ingested between Phase 3 and the rollback was only written to v2 and won&amp;rsquo;t be visible through the v1 read path.&lt;/p&gt;
&lt;h2 id=&#34;helm-values-reference&#34;&gt;Helm values reference&lt;/h2&gt;
&lt;p&gt;The following Helm values control the v1/v2 storage configuration and migration behavior.&lt;/p&gt;
&lt;h3 id=&#34;storage-layer-toggles&#34;&gt;Storage layer toggles&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;Value&lt;/th&gt;
              &lt;th&gt;Type&lt;/th&gt;
              &lt;th&gt;Default&lt;/th&gt;
              &lt;th&gt;Description&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;architecture.storage.v1&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;bool&lt;/td&gt;
              &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;Enable v1 storage and its components (ingester, store-gateway, querier, compactor).&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;architecture.storage.v2&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;bool&lt;/td&gt;
              &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;Enable v2 storage and its components (segment-writer, metastore, compaction-worker, query-backend).&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h3 id=&#34;migration-tuning&#34;&gt;Migration tuning&lt;/h3&gt;
&lt;p&gt;These values only apply when both &lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;v2&lt;/code&gt; are enabled (dual-ingest mode). All values are under &lt;code&gt;architecture.storage.migration&lt;/code&gt;.&lt;/p&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;Value&lt;/th&gt;
              &lt;th&gt;Type&lt;/th&gt;
              &lt;th&gt;Default&lt;/th&gt;
              &lt;th&gt;Description&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;.ingesterWeight&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;float&lt;/td&gt;
              &lt;td&gt;&lt;code&gt;1.0&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;Fraction &lt;code&gt;[0, 1]&lt;/code&gt; of write traffic sent to v1 ingesters.&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;.segmentWriterWeight&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;float&lt;/td&gt;
              &lt;td&gt;&lt;code&gt;1.0&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;Fraction &lt;code&gt;[0, 1]&lt;/code&gt; of write traffic sent to v2 segment-writers.&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;.queryBackend&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;bool&lt;/td&gt;
              &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;Enable the v2 query backend for reads.&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;.queryBackendFrom&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;string&lt;/td&gt;
              &lt;td&gt;&lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;RFC 3339 timestamp (e.g. &lt;code&gt;2025-01-01T00:00:00Z&lt;/code&gt;) from which the v2 read path serves traffic. When set to &lt;code&gt;auto&lt;/code&gt;, the query frontend consults the metastore per tenant to determine when v2 data first appeared. If no v2 data exists for a tenant, queries fall back to v1.&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;]]></content><description>&lt;h1 id="migrate-from-v1-to-v2-storage-using-helm">Migrate from v1 to v2 storage using Helm&lt;/h1>
&lt;p>This guide walks you through migrating a Pyroscope installation from v1 to v2 storage architecture using the Helm chart. The migration uses a phased approach that lets you run both storage backends simultaneously before fully cutting over to v2.&lt;/p></description></item></channel></rss>