Economics deep-dive
The index pays twice.
A hot search index stores more bytes per event than a columnar lakehouse table, and it stores those bytes on more expensive storage, so it loses on both factors at once and they multiply. On the same ten million Zeek connection events, an OpenSearch index on block storage costs about 14.8× a warm Iceberg-on-S3 table, and the gap is mostly the storage class rather than the codec. At thirty days that is a rounding error on a SOC budget; at the seven-year retention horizon a regulated firm actually has to plan for, it is the difference between a line item and a project.
Reading time: about 16 minutes. Evidence tier: B for the measured footprints, C for the desk-priced retention curves, and I label which is which as I go. The footprints are first-party measurements on a single host over the same sha256-pinned 10M-row Zeek conn corpus as the flagship rerun; the prices are AWS us-east-1 list, read off the live rate card on 2026-06-10 and re-checked unchanged on 2026-06-14. This is a storage floor, not a total-cost-of-ownership model, and I keep those two layers separate throughout because conflating them is how cost arguments go wrong. The byte ratios are also parameters of one corpus, so I re-measured two more to show how far they move, and the short version is that the shape of the finding travels while the exact multiplier does not.
The decomposition
Four point two times the bytes, three point five times the price.
The headline multiplier factors cleanly, and the factoring is the whole argument. The first factor is how many bytes each realization writes for the same event. I loaded the identical ten-million-row synthetic Zeek conn corpus six ways and measured the footprint, and the spread is wide: an OpenSearch 2.18.0 index with best_compression and a force-merge lands at 186.8 bytes per event, while the same events written as Iceberg Parquet with pyiceberg's zstd defaults land at 44.0 bytes. That is a 4.2× difference in bytes before anyone has been charged a cent, because an inverted index carries the posting lists and the doc-values and the structures that make a term lookup instant, and those structures are the thing you are paying for when you ask for sub-millisecond search.
Measured footprint · same 10M Zeek conn events in every realization
| Realization | Bytes/event | Reduction vs raw |
|---|---|---|
| Raw JSONL | 374.3 | 1× |
| OpenSearch 2.18.0 index (best_compression, forcemerged) | 186.8 | 2.0× |
| ClickHouse MergeTree, default LZ4 | 68.5 | 5.5× |
| Iceberg Parquet, pyiceberg zstd defaults (warm) | 44.0 | 8.5× |
| ClickHouse MergeTree, blanket ZSTD(22) (tuned-hot) | 41.5 | 9.0× |
| Single-file Parquet, zstd-19, 1M-row groups (cold) | 38.6 | 9.7× |
What the table shows before any pricing is that the index is the outlier on the byte axis, and not by a little. Every columnar realization compresses the raw events somewhere between five and ten times, because columnar storage puts like values next to like values and lets the codec find the repetition that a row of mixed types hides. The index goes the other way and only halves the raw bytes, because it is not trying to store the events compactly; it is trying to answer a term query in microseconds, and the posting lists and doc-values and field structures that make that possible are extra bytes layered on top of the data rather than a tighter encoding of it. So the 2.0× is not a tuning failure that a better setting would fix, it is the access pattern showing up as bytes, the price of being able to look up any term instantly, and the rest of the analysis is really asking how much of your data actually needs that access pattern and for how long.
The second factor is the price of the byte, and it is set by the storage class each realization has to run on rather than by anything you tune. A hot search index has to sit on block storage to serve interactive queries, so its bytes live on gp3 at $0.08 per GB-month. A lakehouse table is read by an engine that pulls from object storage, so its bytes live on S3 Standard at $0.023, and colder tiers drop to Glacier Instant Retrieval at $0.004. Block storage against S3 Standard is a 3.5× difference in dollars per byte, and that factor is independent of how many bytes you wrote.
So the index pays twice: 4.2× the bytes, at 3.5× the price per byte, which compounds to 14.8× the monthly storage bill for the same events held warm on Iceberg. The number is not a single dramatic measurement but the product of two ordinary ones, and writing it that way matters, because it tells you which factor to attack.
The counterintuitive part
The storage class dominates the codec.
The instinct, when the storage bill is too high, is to squeeze the bytes harder, and the measurement says that instinct is aiming at the smaller lever. Tuned ClickHouse with a blanket ZSTD(22) codec actually compresses better than Iceberg's zstd default, 9.0× against 8.5×, so on bytes alone it should win. It still costs 3.3× more per month, because those well-compressed bytes are sitting on block storage to keep the hot OLAP table fast, while Iceberg's slightly larger bytes are sitting on S3. The better codec lost to the storage class.
That is the finding I would carry out of this whole exercise: compression tuning moves the bill by tens of percent, and moving bytes from gp3 to S3 moves it by 3.5×, and moving them to Glacier Instant Retrieval moves it by twenty. The architecture decision, which is where old data is allowed to live, dominates the engineering decision, which is how hard you squeeze it. A team can spend a quarter tuning codecs and dictionary encodings and shave a fifth off the bill, or it can decide that data older than a few weeks belongs on object storage and shave most of it off, and the second decision is usually less work than the first.
It also reframes what an open table format buys you. The case for Iceberg or Delta is often pitched as portability, which is real, but the cost-to-serve angle is more immediate: an open table on object storage is the thing that lets old data fall to a cheaper storage class without losing queryability, and that fall is where the money is. A vendor-managed hot store that you can only read through the vendor's compute gives you that lever only on the vendor's terms, because even when it does tier the bytes to cheaper storage you can still read them back through nothing but the compute the vendor sells you, so the cold data never leaves the vendor's price list even after it leaves the expensive disk.
The middle case
Even ClickHouse pays block-storage rent.
There is a tempting middle path the table quietly rules out, which is to keep everything in a fast columnar engine and skip the lakehouse entirely. ClickHouse is the obvious candidate, because its MergeTree storage compresses the same Zeek events to 68.5 bytes with nothing but its default LZ4, already 2.7× tighter than the OpenSearch index and quick enough to serve interactive queries. If the codec were the thing that decided the bill, that would be the answer, and you would never need the object-storage tier at all.
But a hot ClickHouse table has to live on block storage for the same reason the index does, so its well-compressed bytes still sit on gp3 at $0.08, and at a year of retention it costs $5,338 a month against $988 for the same events on warm Iceberg, which is 5.4× more. The engine you pick changes how many bytes you write; it does not change what a byte costs once it lands, and the second factor is the larger one, which is the same verdict the tuned-ClickHouse arm reached a section ago from the codec side rather than the engine side.
So the finding holds from a second direction. ClickHouse is an excellent hot engine and I reach for it constantly, but reaching for it as the place seven years of data lives is paying block-storage rent on archival bytes, and no codec setting buys that rent back. The cheap-retention lever is not a better engine or a tighter codec, it is letting the bytes move to a tier that charges what cold data should cost, and an open layout on object storage is what lets them move there without binding the cold data to one vendor's engine to read it back.
The retention lever
Annoying at thirty days, existential at seven years.
A multiplier on its own does not tell you whether to care. What makes the 14.8× matter is that both layers are linear in days, so the gap grows with the retention window, and the retention window for a regulated firm is not thirty days. Priced at one terabyte per day of raw ingest, held at steady state, storage only, the curves look like this:
Monthly storage cost · 1 TB/day raw, steady state, storage only · AWS us-east-1 list
| Realization · class | 30 d | 90 d | 365 d | 7 y |
|---|---|---|---|---|
| OpenSearch index · gp3 | $1,200 | $3,600 | $14,600 | $102,200 |
| ClickHouse LZ4 · gp3 | $439 | $1,316 | $5,338 | $37,367 |
| ClickHouse ZSTD-22 · gp3 | $266 | $797 | $3,234 | $22,636 |
| Iceberg zstd · S3 | $81 | $244 | $988 | $6,914 |
| Cold Parquet · S3 | $71 | $214 | $866 | $6,064 |
| Cold Parquet · Glacier IR | $12 | $37 | $151 | $1,055 |
At thirty days the index-versus-lakehouse gap is about $1,100 a month, the difference between $1,200 on the index and $81 on warm Iceberg. That is the kind of number a SOC absorbs without a meeting. At the seven-year horizon a regulated firm under rules like the SEC's 17a-4 and the EU's DORA has to plan for, the same gap is about $95,000 a month per terabyte per day of ingest, $102,200 against $6,914, and that is the kind of number that starts a migration. Nothing about the architecture changed between those two columns; only the retention window did, and the window is set by a regulator rather than by an engineer.
One more thing about the units, because they decide how alarming the table is for any given reader. Everything here is normalized to one terabyte per day of raw ingest, which is a mid-size enterprise SOC, and a large financial-services firm often runs several times that, so a shop ingesting five terabytes a day multiplies every figure in the table by five and the seven-year index-versus-lakehouse gap goes from $95,000 a month to most of half a million. The linearity that makes the tiering arithmetic clean makes the volume arithmetic clean too, so you can read your own number straight off the table by scaling it to your ingest.
There is a way to read that $102,200 as a choice rather than a fact, and it is the tiered design the table is really arguing for. Because both layers are linear in days, a real architecture does not pay the all-hot rate across the whole window; it keeps the recent thirty days on the index, where the $1,200 buys the instant lookups an analyst actually uses, and lets the remaining years fall to a cold class. The thirty-day index window is $1,200 a month and the full seven years of cold Parquet on Glacier Instant Retrieval is $1,055, so the tiered bill lands a little over $2,000 a month against $102,200 for keeping everything hot, roughly forty-five times less for the same retained data. That spread is the budget the architecture hands back, and it is the honest comparison hiding inside the table, because the all-hot column is not a design anyone defends out loud but what a platform costs once tiering gets hard enough that teams stop doing it.
The cold tier widens it further. Index-on-gp3 against cold Parquet on Glacier Instant Retrieval is 96.7× on storage alone, which is the largest number in the table and the most easily abused, so it comes with a condition: Glacier IR charges $0.03 per GB on retrieval, so that tier is for data you genuinely rarely read, and any claim that cites the 96.7× has to either stay storage-only or model how often the data actually gets pulled back. The honest version of the cold-tier number is that it is enormous for archival you touch a few times a year and shrinks fast if your analysts are querying it weekly.
The limits of the ratio
The ratio moves with the data.
Every number to this point rests on one corpus, a flat sixteen-column Zeek conn log, and the honest question is how much of it survives contact with telemetry that looks different. So I measured two more corpora at the same ten-million-row scale, and the answer is that the shape of the finding transfers while the exact ratio does not, which is the distinction that matters the moment you are about to quote a number to a customer.
The first re-measurement is an EDR corpus, synthetic Sysmon-style process-creation events with long command lines, process GUIDs, and high-cardinality user and host fields, which is about as far from flat network flow as security telemetry gets while staying structured. Its compression ratio came in at 7.94× with Parquet's zstd default and 9.25× at zstd-19, within about seven percent of the Zeek numbers, so the 8.5× is not a quirk of one friendly schema. That is the reassuring half. The unreassuring half is that the EDR events are physically larger, 600.1 raw bytes each against Zeek's 374.3, and they still store at 75.6 compressed bytes against Zeek's 44.0, so at any given retention the EDR corpus costs about 1.7× per event even though its compression ratio is nearly identical. The ratio transfers and the absolute bytes per event do not, and the storage bill is set by the absolute bytes, so an engagement has to re-measure both the ratio and the raw size of an event before it carries one number across.
The second re-measurement is the one that keeps me honest. I built a deliberately high-entropy corpus, base64-encoded payloads, full SHA-256 hashes, and a unique GUID on every event, which is what encoded-PowerShell command lines, packet captures, and file hashes actually look like on the wire. That corpus compresses 2.57× at zstd default and 2.75× at zstd-19, which is 0.30× the Zeek ratio, more than three times worse. High-entropy data is close to incompressible by construction, because compression earns its keep by finding repetition and a hash or a random GUID has none, so the eight-to-ten-fold reduction that flat logs enjoy collapses to under three.
Across these three corpora the storage ratio spans roughly 2.6× to 8.5×, a factor of three, set by what the data is rather than by which tool writes it. The practical consequence is direct: a migration model that assumes 8.5× on a feed that is mostly encoded payloads will underestimate the stored footprint by about a factor of three, and the place that error surfaces is the retention bill at year seven, where it is most expensive to be wrong. So the discipline the lab holds itself to, re-measure the byte ratio on the customer's own telemetry before pricing anything, is exactly the discipline a vendor's storage-savings slide skips, and it is why the cost-to-serve axis of the Capability Matrix carries a measured ratio per workload rather than one headline number.
What the number is not
A storage floor, not a TCO model.
I want to be precise about what this measures, because storage-cost arguments earn their bad reputation by sliding from one thing into everything. This is the storage floor and only the storage floor: no compute, no licensing, no ops labor, no egress, no IOPS or throughput add-ons beyond the gp3 baseline. A real platform spends on all of those, and on some workloads the compute and license lines dwarf the storage line, so the 14.8× is a floor under the storage share of the bill rather than a claim about the whole bill. The reason the storage share is still worth isolating is that it is the share retention compounds, so it is the part of the bill that grows on its own while everything else holds roughly flat per query.
The byte ratios are also parameters of this particular corpus, as the previous section measured rather than asserted, and they will move on other data. Nested OCSF with deep object structure, or long free-text message fields, compress differently in an index than in columnar Parquet, sometimes narrowing the footprint gap and sometimes widening it, so the right move in an engagement is to re-measure on the actual workload rather than to carry these exact ratios across. The shape of the finding, that the storage class dominates the codec and that retention is the multiplier, is the portable part; the specific 4.2× is not.
The curves also assume a steady state, a flat terabyte a day held at constant volume, and real ingest rarely holds flat. A growing program adds feeds and endpoints year over year, so its retention curve bends steeper than the linear one priced here and the all-hot bill compounds faster than the table suggests rather than slower. That steady state is the conservative direction for the lakehouse argument, because anything that makes the window longer or the daily volume larger widens the gap the table already shows, so I priced the flat case and left the reader to scale it up.
And no real architecture should run everything on one tier. Hardly anyone would choose to serve seven years of data from a hot index on purpose, so the all-hot column is a strawman if you read it as a deliberate proposal, and the reason it still shows up in the wild is that teams get backed into it. The honest comparison the table enables is a tiered design, a hot tier for the recent seven-to-thirty days plus warm and cold for the long tail, against an all-hot design that keeps everything on the index because the platform makes tiering hard. That second design is more common than it should be, because a lot of SIEM pricing couples ingestion and retention so that old data keeps paying the hot rate, and the table is really a measurement of what that coupling costs.
Finally, this is not the old desk-derived 130–227× storage-cost ratio I have used before, and these numbers do not confirm or replace it. That number came from list-price arithmetic, Splunk's indexed-storage rate of roughly $3 to $10 per GB-month against S3 Standard's $0.023, which pencils out anywhere from about 130× to 435× and which I have carried conservatively at 130× to 227×, so it was always storage list price over storage list price, with compute and license sitting on top of it rather than inside it. This benchmark measures bytes and storage prices directly, and what it does is put a measured storage floor of roughly 15× to 97×, depending on tier, under that same storage ratio, which is a smaller and more defensible thing to stand on than the desk arithmetic.
What to do with it
Design the tiers before you tune the codec.
The practical reading is an ordering of decisions. The first decision, the one that moves the bill most, is where data is allowed to live as it ages, which means choosing a storage layout that lets old data fall from block storage to object storage and then to a cold class without becoming unqueryable. An open table on S3 gives you that fall; a vendor-managed hot store generally does not, because the bytes can't leave the tier the vendor serves them from. The second decision, codec and encoding tuning, is real and worth doing, but it is a tens-of-percent lever and it should not be where the project starts.
For a regulated firm the math is sharper still, because the retention window is fixed by rule and the all-hot bill grows linearly inside it. The SEC's 17a-4 obliges broker-dealers to preserve defined categories of records for years in a tamper-resistant, auditable form, the EU's DORA obliges financial entities to retain ICT and security-incident records and be able to reconstruct events for their supervisors, and seven years is the conservative horizon a lot of firms standardize on rather than tracking each rule's exact period feed by feed. None of those rules cares how well your codec compresses; they care that the data is present and retrievable, so the question is not whether to retain but where the retained bytes are allowed to live, and the $95,000-a-month gap per terabyte per day is what a tiered design hands back for getting that placement right. That is the regulated-firm case writing itself: the firms with the longest mandatory retention are exactly the firms with the most to save by getting the storage classes right, which is why the lakehouse argument is strongest in financial services and weakest where thirty days of retention is plenty and the index never has to hold the long tail.
None of this says the index is the wrong tool. The index earns its bytes and its block storage on the recent data an analyst hits constantly, where instant term lookups are worth paying for, and the latency side of that same trade, where the index wins the cheap lookups while the columnar engines win the heavy hunting aggregations, is measured in the lab. The argument here is narrower and only about cost: keep the index for the window where its speed is worth its price, and let everything older fall to a tier that charges what cold data should cost, because the storage class is the lever and retention is the multiplier, and the codec is a detail you can tune after both of those are decided.