Columnar vs Row JSON: What a 190-Metric Apple Health Export Costs Your Context Window
Every post about exporting Apple Health data tells you how to get a file. Almost none tells you what the file costs once an agent tries to read it. This one is the arithmetic: megabytes are not the unit that matters, tokens are, and row JSON quietly pays a tax on every single sample.
Quick answer
In row JSON, every sample repeats its own key names, so the overhead scales with the number of records rather than the number of metrics. A 190-metric export with a few samples per day reaches hundreds of thousands of tokens for a single day if you hand the whole thing to a model. Columnar shapes, which store the key names once and the values as a list, cut the same data by a large multiple. The cheaper answer again is not to put the file in context at all, but to let a local server return only the slice a question needs.
The unit that matters is tokens, not megabytes
People size health exports in megabytes because that is what the Files app shows. Models do not read megabytes. They read tokens, and the conversion is unpleasant: roughly four characters per token for ordinary English, but JSON is punctuation-heavy, so dense structured records run closer to three characters per token with contractions, quotes and braces everywhere.
So a 12 MB export is not 3 million tokens of friendly prose. It is a wall of repeated keys and ISO timestamps, and the effective cost per useful number is higher than it looks.
What one row of Apple Health JSON looks like
Here is a single resting heart rate record in the shape most exports use:
{
"type": "HKQuantityTypeIdentifierRestingHeartRate",
"value": 58,
"unit": "count/min",
"start": "2026-10-11T06:12:00Z",
"end": "2026-10-11T06:12:00Z",
"source": "Apple Watch",
"device": "Watch7,5"
}
About 45 tokens, and roughly 30 of those are the key names and punctuation rather than anything you asked for. The value itself is two tokens. That ratio is the entire problem with row JSON at scale.
The arithmetic for one day
Stop here if you dislike worked numbers, but the point is that every input is stated, so you can rerun it with your own assumptions:
- 190 metrics in the export.
- Assume an average of 96 samples per metric per day, which covers a heart rate series that samples often and a body mass reading that samples once, as a blend.
- That is 190 times 96, or 18,240 records a day.
- At about 45 tokens per row JSON record, that is roughly 820,000 tokens for one day.
No context window on a consumer plan holds that comfortably, and you have not asked a single question yet. Even a much lighter assumption, 10 samples per metric, is 1,900 records and about 85,000 tokens a day, which still crowds out the conversation you actually wanted.
What columnar does instead
Columnar keeps one set of key names and stores the values as a compact list per metric:
{
"type": "HKQuantityTypeIdentifierRestingHeartRate",
"unit": "count/min",
"start": ["2026-10-11T06:12:00Z", "2026-10-12T06:14:00Z"],
"value": [58, 57]
}
The key names appear once per metric, not once per sample. Each extra sample costs a timestamp and a value, so the marginal token cost of the second, tenth and hundredth reading drops hard. For the same 18,240 records the overhead collapses from per-record to per-metric: 190 header blocks instead of 18,240.
Someone already shipped this
This is not a hypothetical encoding. HealthKite ships a columnar-v1 encoding, served over a Bonjour-discovered LAN channel with TLS-PSK, read-only, and only while the app is open (evidence E4 and E5 in our weekly market pack, read 2026-10-18). The columnar choice is the interesting part: it is the same insight, that health data is dense in time and sparse in vocabulary, so store the vocabulary once.
How we sourced this. The columnar-v1 encoding, the Bonjour discovery and the TLS-PSK channel, and the app-open-only constraint are from the project's own site and API overview (E4, E5), read on 2026-10-18. The token arithmetic above is ours, with every input stated: 190 metrics, 96 samples per metric per day, about 45 tokens per row JSON record.
The trade, honestly
| Row JSON (file) | Columnar JSON (file) | Local MCP server | |
|---|---|---|---|
| Key names repeated | Every record | Once per metric | Never enters context |
| Context cost of a day | Very high | Much lower | Only the answer |
| Readable by any tool | Yes | Needs a reader that knows the shape | Yes, via the tools |
| Availability | Whenever the file exists | When the file exists | When the file exists |
Columnar is a real win if you insist on loading the file. It is still the second-best answer, because the best answer is not loading the file.
The third option that beats both
Context is only spent when data enters the model's window. A local MCP server sits between your export and your agent, so the model never sees 18,240 records. It sees the five numbers that answer the question. Ask "how did HRV trend this week against last week" and the server calls compare_periods on a filtered slice and returns a summary, not a dataset.
MetricBridge ships exactly that: 190 metrics as a local JSON export, and a zero-dependency stdio MCP server with 16 read-only tools that filters before anything reaches the model.
{
"mcpServers": {
"health-export": {
"command": "node",
"args": ["/Users/you/.health-export-mcp/server.mjs"],
"env": {
"HEALTH_DATA_DIR": "/Users/you/Library/Mobile Documents/iCloud~ai~healthexport~app/Documents"
}
}
}
}
Ask the agent to call get_mcp_status to confirm it is up and the data was found. Then the export size stops being a context problem, because the file never has to be read into the window at all.
FAQ
How many tokens is a day of Apple Health data?
It depends on your sample density. In the worked arithmetic above, 190 metrics at 96 samples each is 18,240 records and roughly 820,000 tokens in row JSON. At 10 samples per metric it is about 85,000 tokens. Both assume roughly 45 tokens per row JSON record, and you can rerun the maths with your own numbers.
Is columnar JSON smaller than row JSON?
For the same records, yes, and often by a large multiple, because key names are stored once per metric instead of once per sample. The marginal cost of each extra reading becomes a timestamp and a value rather than a full record.
Should I convert my export to columnar before giving it to an agent?
Only if you plan to load the file into context. If you use an MCP server that reads the file locally and returns summaries, the encoding of the file matters far less, because the full dataset never reaches the model.
What is columnar-v1?
It is a columnar encoding shipped by HealthKite, served over a Bonjour-discovered LAN channel with TLS-PSK, read-only and only while the app is open. It is one of several formats now competing to make Apple Health data cheaper to read.
Doing this yourself? MetricBridge handles the export step: all 190 metrics as local JSON, with the open-source MCP server (GitHub).
Your export never has to enter the context window
MetricBridge is a $24.99 one-time unlock: 190 metrics as local JSON, the open-source MCP server that filters before anything reaches the model, and the on-device Ask and Weekly Brief.