Est.
FeaturesLong read

MergeTree Settings That Change Behavior Between Managed Providers

Some MergeTree settings vanish, lock, or silently override when moving to managed platforms.

· 11 min read · Updated
Cover illustration for “MergeTree Settings That Change Behavior Between Managed Providers”
Features · September 18, 2026 · 11 min read · 2,390 words

MergeTree settings split into three groups once a platform gets involved: some vanish entirely because the architecture underneath them no longer exists, some stay in the codebase but get locked at the server config layer, and some look tunable while the platform quietly overrides them anyway. That three-way split is the whole story of why a schema that works perfectly on a self-managed cluster can misbehave, throttle, or silently drift on a managed one. Self-managed deployments running ReplicatedMergeTree put every setting within reach: data sits on local disks, replication copies raw part files between nodes, and whoever runs the cluster controls server config directly. Moving to a managed platform built on SharedMergeTree shifts durable data to object storage (S3, GCS, or Blob Storage, depending on the cloud); compute nodes become stateless, and local disk becomes nothing more than an ephemeral cache. That shift doesn't just restrict settings. It erases some of them outright, and the rest of this piece maps exactly which.

Settings that are architecturally absent on SharedMergeTree-based platforms

Diagram: Three Fates of MergeTree Settings on a Managed Platform. Visualizes: Visualize the three-way split that governs every MergeTree setting when moving from self-managed to a managed SharedMergeTree platform.

The settings most self-managed operators reach for first, merge coordination, part-file sync, zero-copy replication toggles, simply don't exist on SharedMergeTree platforms. The provider didn't choose to hide them; the storage model those settings were built to govern has been replaced with something else.

Picture how ReplicatedMergeTree actually works: each replica keeps its own copy of part files on local disk and synchronizes with other replicas through ZooKeeper or Keeper. These settings matter because there's real duplication happening at the storage layer, and they control how aggressively a replica fetches, merges, or deduplicates parts across nodes. SharedMergeTree removes that duplication. Metadata coordination still happens in Keeper, but the data itself lives once, in object storage, so it's shared across every replica instead of copied to each. Those replica-sync settings made sense when each node kept its own files and needed help staying in sync with its neighbors, but that world is gone now.

Self-managed teams who ran MergeTree on S3 tried to bridge this gap earlier with zero-copy replication: store the data in S3, keep local metadata files, and try to keep the two in sync. It never sat comfortably. Data in S3 isn't self-contained without the local metadata that describes it, and every modification needs synchronization between two media that don't share a transaction boundary, a recipe for drift and corruption under load. SharedMergeTree fixes this by moving metadata into Keeper itself, so S3 (for data parts) and Keeper (for metadata) both become authoritative stores that draw on the same coordination layer. It's a real architectural fix, but it's also not headed for open-source release, so any team that wants it has to go through a platform that runs it.

What this means in practice: a schema built around specific zero-copy replication behavior, or one that depends on tuning local-disk merge scheduling, cannot simply move over to a SharedMergeTree platform and keep working the same way. The behavior it relied on isn't locked or hidden somewhere in a config file. It was never ported over, because there's nothing on the other side for it to configure.

Settings that managed platforms lock at the server config layer

A second category of settings still exists in the codebase and would do real work if exposed, but managed providers hold them at the server config layer and never hand the keys to the operator. This is where most of the surprises in production actually come from, because unlike the architecturally absent settings, these look like they should be adjustable. They're just out of reach.

Insert-throttling thresholds sit at the center of this problem: parts_to_delay_insert and parts_to_throw_insert. These settings govern how the server responds as the number of active parts in a table climbs, first by slowing writes down, then by rejecting them. The defaults are set conservatively on purpose, built for a broad range of workloads rather than any one team's ingest pattern. On a self-managed cluster, you can raise those thresholds for a high-velocity pipeline as a routine tuning exercise. On a platform that restricts server-config access, the operator is stuck at whatever the provider decided was safe, and high-frequency, small-batch writers run straight into "Too Many Parts" errors with no lever to pull.

Background merge concurrency settings fall into the same bucket. The number of background merge threads running at once determines how fast the part count comes back down after a burst of writes, and on most managed platforms, that number belongs to the provider, not the operator.

Query priority and resource group settings, the ones that decide which workloads get to preempt others, get locked for a related reason: platforms running a shared resource model across tenants need to protect every customer's latency from every other customer's batch job. An operator who used query priority to shield a latency-sensitive dashboard from a bulk backfill loses that protection the moment that setting moves behind the provider's wall.

Settings that appear tunable but are silently overridden by platform storage policy

The third category is the hardest one to catch, because nothing fails loudly. An operator sets a value and the platform accepts it without complaint, but the actual behavior of queries and merges follows the platform's policy instead. No error message points back to the conflict.

Storage tiering and TTL-move settings most often reveal this divergence, since a platform can accept a value at the syntax level while overriding it at execution time. If a platform routes all data through object storage as a matter of global policy, it may accept an operator-defined storage volume or a TTL ... MOVE TO DISK expression at the syntax level, then override it at execution time. The expression parses fine. It just doesn't do what it says.

This gets worse when a provider enforces that global storage policy without also adjusting insert-throttling thresholds downward to match. Object storage is slower than local NVMe, so if the thresholds that govern part accumulation were tuned for local disk, they keep behaving as though the backend never changed. The "Too Many Parts" problem compounds quietly in exactly the pipelines least equipped to absorb it.

TTL-based lifecycle management, expiring old partitions, recompressing cold data on a schedule, depends entirely on the platform actually honoring what the operator wrote. When storage policy gets overridden globally, an operator can no longer plan around aggressive TTL design and has to discover it through trial and error instead, usually after noticing storage costs or query latency drifting in the wrong direction.

None of this traces back to a provider trying to be difficult. When every query and every merge passes through object storage, the platform has to govern I/O centrally just to control cost and keep tenants from stepping on each other. Operator settings that conflict with that governance get overridden as a byproduct of keeping the system stable, not as a documented policy decision anyone made on purpose. ObsessionDB's cache-mesh architecture sidesteps this particular problem by splitting the durability function from the query-serving function: S3 holds the data for durability, but a distributed NVMe mesh serves the actual queries, so hot data never has to round-trip through S3 at read time. Because the query path never contends on S3 latency in the first place, the platform has no multi-tenant fairness problem to protect against by overriding storage settings behind the scenes.

How insert-throttling divergence plays out in real ingest pipelines

Of all the production incidents that trace back to locked MergeTree settings, "Too Many Parts" is the most common, and the uncomfortable part is that it's predictable before a single row gets written, just from looking at the schema and the ingest pattern.

Here's the mechanism. Small-batch inserts arrive faster than background merges can consolidate them into larger parts, so the active part count climbs. Once it crosses parts_to_delay_insert, the server starts throttling writes to buy the merges time to catch up. If the count keeps climbing past parts_to_throw_insert, the server stops accepting writes and returns an error. On a self-managed cluster, both thresholds are dials an operator can turn. On a managed platform with locked server config, they're fixed, and a workload that would have been a quick settings change becomes a hard wall.

High-velocity ingest patterns common in observability pipelines, many small producers writing over HTTP or another streaming transport at high frequency, are exactly the schemas most likely to hit that wall on a platform holding conservative defaults. The self-managed fix is usually a combination: raise the throw threshold, add merge concurrency, and get producers to batch more aggressively before they write. The only lever left is changing the producers, since the first two options are gone, which may not even be realistic if those producers belong to clients or services the team doesn't control.

AI agents that explore and write data in parallel tend to issue bursts of small writes during that exploration, which is precisely the pattern most likely to exhaust part limits on a platform with locked throttling thresholds. That's a failure mode that capacity planning built around human usage patterns doesn't anticipate.

How schema design changes when settings cannot be tuned

When you can't tune insert-throttling and merge concurrency, partition design becomes the main tool left for controlling how fast parts accumulate. That shifts real complexity out of configuration and into the schema itself, which is a different kind of work than most teams expect to be doing on a managed platform.

Partitioning more finely keeps part counts low within each partition, but it comes at a cost: more partitions means more metadata for the cluster to track, and on SharedMergeTree platforms, that adds Keeper coordination overhead that can turn into its own bottleneck at scale. There's a real trade-off here, and it's worth running the numbers for a specific workload rather than assuming finer partitioning always helps.

The choice of MergeTree variant matters too, but this part can stay brief. ReplacingMergeTree, AggregatingMergeTree, and CollapsingMergeTree all share the same columnar storage, the same primary index, the same TTL and replication capabilities. What separates them is background merge behavior, specifically how each one handles duplicate or related rows. That difference in merge behavior compounds when merge scheduling itself isn't something the operator can adjust, so the variant choice carries more weight on a locked platform than it would on a self-managed one.

TTL design deserves particular care. A schema that assumes TTL expressions move data between storage tiers on schedule is making a bet that the platform will honor those expressions. But on a platform where storage policy gets overridden globally, that bet doesn't pay off. TTL moves fail silently, so data piles up in the primary tier, and you only notice once cost or query latency starts drifting.

The practical move, before signing on with any managed platform, is to enumerate every MergeTree setting the schema assumes it can tune, sort each one into absent, locked, or silently overridden for each candidate platform, and redesign partition and TTL strategy around what's actually left. Treat it as a migration audit step up front, not something to patch after the first production incident.

Upgrade timing and backward-incompatible changes that managed platforms apply without operator control

A managed platform controls more than the settings a self-managed operator would normally give up. Timing matters too: when the underlying software changes beneath a running schema, and whether the operator gets any say in when that happens.

GitHub PR #117002, from August 2026, is a concrete example. It places a stack guard in shared query-tree visitor bases, and the side effect is that existing deep-DEFAULT tables and old MergeTree parts, tables that worked fine before, start throwing TOO_DEEP_RECURSION failures on INSERT, ALTER, or SELECT after the upgrade lands. Tables that depend on deep DEFAULT chains can fail outright, and you have to adjust the schema or the workload to work around it.

On a self-managed cluster, the operator decides when to upgrade. So you stage the rollout, run pre-upgrade validation against the actual schema in production, and roll back if something regresses. On a managed platform that applies upgrades automatically, that same regression reaches production directly, with no operator-controlled window to catch it first.

Locked settings and automatic upgrades together mean a managed platform controls two separate dimensions of behavior at once: what the operator is allowed to tune, and when the software underneath changes shape. Neither shift originates with the operator. That's not a reason to rule out managed platforms on its own, but it belongs in the same evaluation as the settings audit. Providers vary quite a bit in how much control they give operators over upgrade scheduling, and that variance should be checked before committing, the same way the settings taxonomy should be checked.

MergeTree system tables and agent utility under setting restrictions

Everything above describes how setting restrictions affect a human operator. Agents feel the same restrictions harder, because they lean on system-table access and broad SQL scope in a way that manual operation doesn't always require.

Providers that restrict or redact access to system.* tables usually do it as a multi-tenant safety measure, and it's a reasonable one in isolation. But it also removes the exact observability surface that an agent needs to catch an ingest problem before it turns into an incident. Agentic SRE tooling built to flag when an ingest pattern is about to trip parts_to_delay_insert, trace the root cause back to small-batch inserts or missing partition pruning, and in some cases auto-tune merge settings or temporarily relax thresholds, depends on being able to see into those system tables and act on what it finds there. Without that access, the agent is diagnosing blind.

Setting policy is no longer purely an infrastructure decision; it is an AI product decision as well. If a platform silently overrides storage settings or locks insert-throttling without logging the conflict, a human operator isn't the only one left guessing. It leaves an agent issuing parallel, high-frequency queries with no way to predict how the platform will actually respond to what it sends. Platforms built from the ground up with agent-driven analytics in mind, ObsessionDB among them, tend to expose settings with more granularity and log overrides as they happen, so an agent (and the engineer auditing its behavior afterward) can predict what a query will do instead of discovering the platform's real behavior only after something breaks at scale.

Sources

  1. jack-vanlightly.com
  2. MergeTree tables settings

More in Features