12/30/2025 at 7:27:22 PM
Don't miss how this works. It's not a server-side application - this code runs entirely in your browser using SQLite compiled to WASM, but rather than fetching a full 22GB database it instead uses a clever hack that retrieves just "shards" of the SQLite database needed for the page you are viewing.I watched it in the browser network panel and saw it fetch:
https://hackerbook.dosaygo.com/static-shards/shard_1636.sqlite.gz
https://hackerbook.dosaygo.com/static-shards/shard_1635.sqlite.gz
https://hackerbook.dosaygo.com/static-shards/shard_1634.sqlite.gz
As I paginated to previous days.It's reminiscent of that brilliant SQLite.js VFS trick from a few years ago: https://github.com/phiresky/sql.js-httpvfs - only that one used HTTP range headers, this one uses sharded files instead.
The interactive SQL query interface at https://hackerbook.dosaygo.com/?view=query asks you to select which shards to run the query against, there are 1636 total.
by simonw
12/30/2025 at 11:48:45 PM
A read-only VFS doing this can be really simple, with the right API…This is my VFS: https://github.com/ncruces/go-sqlite3/blob/main/vfs/readervf...
And using it with range requests: https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readerv...
And having it work with a Zstandard compressed SQLite database, is one library away: https://pkg.go.dev/github.com/SaveTheRbtz/zstd-seekable-form...
by ncruces
12/31/2025 at 9:29:06 AM
Your page is served over sqlitevfs with Range queries? Let's try this here.by keepamovin
12/31/2025 at 9:33:34 PM
I did a similar VFS in Go. It doesn't run client-side in a browser.But you can use it (e.g.) in a small VPS to access a multi-TB database directly from S3.
by ncruces
1/1/2026 at 5:14:07 AM
That is cool. Maybe i look at the go codeby keepamovin
12/31/2025 at 5:07:48 AM
this does not caches the data right? it would always fetch from network? by any chance do you know of solution/extension that caches the data it would make it so much more efficient.by pdyc
12/31/2025 at 5:56:57 AM
The package I'm using in the HTTP example can be configured to cache data: https://github.com/psanford/httpreadat?tab=readme-ov-file#ca...But, also, SQLite caches data; you can simply increase the page cache.
by ncruces
12/31/2025 at 4:45:34 AM
Thanks! I'm glad you enjoyed the sausage being made. There's a little easter egg if you click on the compact disc icon.And I just now added a 'me' view. Enter your username and it will show your comments/posts on any day. So you can scrub back through your 2006 - 2025 retrospective using the calendar buttons.
by keepamovin
12/31/2025 at 2:34:32 PM
I almost got tricked into trying to figure out what was Easter eggy about August 9 2015 :-) There's a clarifying tooltip on the link, but it is mostly obscured by the image's "Archive" title attribute.by oblosys
12/31/2025 at 2:37:56 PM
Oh, shit that was the problem! You solved the bug! I was trying to figure out why the right tooltip didn't display. A linked wrapped in an image wrapped in an easter egg! Or something. Ha, thank you. Will fix :)edit: Fixed! Also I just pushed a new version with a Dec 29th Data Dump, so ... updates - yay!
by keepamovin
12/31/2025 at 8:34:55 PM
Happy to help!by oblosys
12/30/2025 at 9:33:29 PM
Is there anything more production grade built around the same idea of HTTP range requests like that sqlite thing? This has so much potentialby nextaccountic
12/30/2025 at 10:25:06 PM
Yes — PMTiles is exactly that: a production-ready, single-file, static container for vector tiles built around HTTP range requests.I’ve used it in production to self-host Australia-only maps on S3. We generated a single ~900 MB PMTiles file from OpenStreetMap (Australia only, up to Z14) and uploaded it to S3. Clients then fetch just the required byte ranges for each vector tile via HTTP range requests.
It’s fast, scales well, and bandwidth costs are negligible because clients only download the exact data they need.
by Humphrey
12/30/2025 at 10:32:30 PM
PMTiles is absurdly great software.by simonw
12/30/2025 at 10:36:49 PM
I know right! I'd never heard of HTTP Range requests until PMTiles - but gee it's an elegant solution.by Humphrey
12/31/2025 at 8:25:20 AM
Hadn't seen PMTiles before, but that matches the mental model exactly! I chose physical file sharding over Range Requests on a single db because it felt safer for 'dumb' static hosts like CF. - less risk of a single 22GB file getting stuck or cached weirdly. Maybe it would workby keepamovin
12/31/2025 at 9:20:36 AM
My only gripe is that the tile metadata is stored as JSON, which I get is for compatibility reasons with existing software, but for e.g. a simple C program to implement the full spec you need to ship a JSON parser on top of the PMTiles parser itself.by hyperbolablabla
12/31/2025 at 3:22:12 PM
How would you store it?by keepamovin
12/31/2025 at 12:56:25 PM
A JSON parser is less than a thousand lines of code.by seg_lol
12/31/2025 at 5:50:33 PM
And where most of CPU time will be wasted in, if you care about profiling/improving responsiveness.by Diti
1/1/2026 at 6:44:41 AM
At that point you're just io bound, no? I can easily parse json at 100+GB/s on commodity hardware, but I'm gonna have a much harder time actually delivering that much data to parse.by monerozcash
1/1/2026 at 6:27:08 AM
What's a better way?by keepamovin
12/31/2025 at 1:23:56 AM
That's neat, but.. is it just for cartographic data?I want something like a db with indexes
by nextaccountic
12/31/2025 at 6:56:14 AM
Look into using duckdb with remote http/s3 parquet files. The parquet files are organized as columnar vectors, grouped into chunks of rows. Each row group stores metadata about the set it contains that can be used to prune out data that doesn’t need to be scanned by the query engine. https://duckdb.org/docs/stable/guides/performance/indexingLanceDB has a similar mechanism for operating on remote vector embeddings/text search.
It’s a fun time to be a dev in this space!
by jtbaker
1/2/2026 at 1:44:45 AM
> Look into using duckdb with remote http/s3 parquet files. The parquet files are organized as columnar vectors, grouped into chunks of rows. Each row group stores metadata about the set it contains that can be used to prune out data that doesn’t need to be scanned by the query engine. https://duckdb.org/docs/stable/guides/performance/indexingBut, when using this on frontend, are portions of files fetched specifically with http range requests? I tried to search for it but couldn't find details
by nextaccountic
12/30/2025 at 9:37:23 PM
There was a UK government GitHub repo that did something interesting with this kind of trick against S3 but I checked just now and the repo is a 404. Here are my notes about what it did: https://simonwillison.net/2025/Feb/7/sqlite-s3vfs/Looks like it's still on PyPI though: https://pypi.org/project/sqlite-s3vfs/
You can see inside it with my PyPI package explorer: https://tools.simonwillison.net/zip-wheel-explorer?package=s...
by simonw
12/30/2025 at 10:30:47 PM
I recovered it from https://archive.softwareheritage.org/browse/origin/directory... and pushed a fresh copy to GitHub here:https://github.com/simonw/sqlite-s3vfs
This comment was helpful in figuring out how to get a full Git clone out of the heritage archive: https://news.ycombinator.com/item?id=37516523#37517378
Here's a TIL I wrote up of the process: https://til.simonwillison.net/github/software-archive-recove...
by simonw
12/30/2025 at 11:12:06 PM
I also have a locally cloned copy of that repo from when it was on GitHub. Same latest commit as your copy of it.From what I see in GitHub in your copy of the repo, it looks like you don’t have the tags.
Do you have the tags locally?
If you don’t have the tags, I can push a copy of the repo to GitHub too and you can get the tags from my copy.
by QuantumNomad_
12/30/2025 at 11:23:38 PM
I don't have the tags! It would be awesome if you could push that.by simonw
12/30/2025 at 11:29:57 PM
Uploaded here:by QuantumNomad_
12/30/2025 at 11:33:57 PM
Thanks for that, though actually it turns out I had them after all - I needed to run: git push --tags origin
by simonw
12/30/2025 at 11:35:53 PM
All the better :)by QuantumNomad_
12/31/2025 at 1:00:38 AM
Doing all this in an hour is such a good example of how absurdly efficient you can be with LLMs.by bspammer
12/31/2025 at 6:35:48 PM
From reading the TIL, it doesn't appear as if Simon used LLM for a large portion of what he did; only the initial suggestion to check the archive, and the web tool to make his process reproducible. Also, if you read the script from his chat with Claude code, the prompt really does the heavy lifting.Sure, the LLM fills in all the boilerplate and makes an easy-to-use, reproducible tool with loads of documentation, and credit for that. But is it not more accurate to say that Simon is absurdly efficient, LLM or sans LLM? :)
by socialcommenter
12/30/2025 at 10:46:42 PM
didn't you do something similar for Datasette, Simon?by AceJohnny2
12/30/2025 at 11:02:07 PM
Nothing smart with HTTP range requests yet - I have https://lite.datasette.io which runs the full Python server app in the browser via WebAssembly and Pyodide but it still works by fetching the entire SQLite file at once.by simonw
12/30/2025 at 11:04:57 PM
oh! I must've been confused with your TIL where you linked to an explainer of this techniquehttps://simonwillison.net/2021/May/2/hosting-sqlite-database...
https://phiresky.github.io/blog/2021/hosting-sqlite-database...
by AceJohnny2
12/31/2025 at 3:13:32 AM
i played around with this a while back. you can see a demo here. it also lets you pull new WAL segments in and apply them to the current database. never got much time to go any further with it than this.https://just.billywhizz.io/sqlite/demo/#https://raw.githubus...
by billywhizz
12/30/2025 at 9:43:42 PM
This is somewhat related to a large dataset browsing service a friend and I worked on a while back - we made index files, and the browser ran a lightweight query planner to fetch static chunks which could be served from S3/torrents/whatever. It worked pretty well, and I think there’s a lot of potential for this style of data serving infra.by ericd
12/31/2025 at 3:05:12 AM
gdal vsis3 dynamically fetches chunks of rasters from s3 using range requests. It is the underlying technology for several mapping systems.There is also a file format to optimize this https://cogeo.org/
by __turbobrew__
12/31/2025 at 1:33:48 AM
I tried to implement something similar to optimize sampling semi-random documents from (very) large datasets on Huggingface, unfortunately their API doesn't support range requests well.by omneity
12/31/2025 at 4:04:00 AM
This is pretty much well what is so remarkable about parquet files; not only do you get seekable data, you can fetch only the columns you want too.I believe that there are also indexing opportunities (not necessarily via eg hive partitioning) but frankly - am kinda out of my depth pn it.
by mootothemax
12/31/2025 at 12:01:12 AM
I want to see a bittorrent version :Pby 6510
12/31/2025 at 1:21:07 AM
Maybe webtorrent-based?by nextaccountic
12/31/2025 at 4:28:59 AM
Parquet/icebergby tlarkworthy
12/31/2025 at 6:57:07 PM
A recent change is I added date spans to the shard checboxes on query view so it's easier to zero dates you want if you have that in mind. Because if your copy isn't local all those network pulls take a while.The sequence of shards you saw when you paginated to days is faciliated by the static-manifest which maps HN item ID ranges to shards, and since IDs are increasing and a pretty good proxy of time (a "HN clock"), we can also map the shards that we cut up by ID to the time spans their items cover. An in memory table sorted by time is created from the manifest on load so we can easily look up which shard we need when you pick a day.
Funnily enough, this system was thrown off early on by a handful of "ID/timestamp" outliers in the data: items with weird future timestamps (offset by a couple years), or null timestamps. To cleanse our pure data from this noise, and restore proper adjacent-in-time shard cuts we just did a 1/99 percentile grouping and discarded the outliers leaving shards with sensible 'effective' time spans.
Sometimes we end up fetching two shards when you enter a new day because some items' comments exist "cross shard". We needed another index for that and it lives in cross-shard-index.bin which is just a list of 4-byte item IDs that have children in more than 1 shard (2-bytes), which occurs when people have the self-indulgence to respond to comments a few days after a post has died down ;)
Thankfully HN imposes a 2 week horizon for replies so there aren't that many cross-shard comments (those living outside the 2-3 days span of most, recent, shards). But I think there's still around 1M or so, IIRC.
by keepamovin
12/31/2025 at 3:32:24 AM
I am curios why they don't use a single file and HTTP Range Requests instead. PMTiles (a distribution of OpenStreetMap) uses that.by maxloh
12/31/2025 at 4:48:12 AM
This would be a neat idea to try. Want to add a PR? Bench different "hackends" to see how DuckDB, SQLite shards, or range queries perform?by keepamovin
12/31/2025 at 2:18:03 AM
I love this so much, on my phone this is much faster than actual HN (I know it's only a read-only version).Where did you get the 22GB figure from? On the site it says:
> 46,399,072 items, 1,637 shards, 8.5GB, spanning Oct 9, 2006 to Dec 28, 2025
by meander_water
12/31/2025 at 2:57:17 AM
> Where did you get the 22GB figure from?The HN post title (:
by amitmahbubani
12/31/2025 at 4:46:18 AM
22GB is non-gzipped.by keepamovin
12/31/2025 at 2:59:48 AM
Hah, well that's embarrassingby meander_water
12/31/2025 at 3:13:21 AM
The GitHub page is no longer available, which is a shame because I'm really interested in how this works.How was the entirety of HN stored in a single SQLite database? In other words, how was the data acquired? And how does the page load instantly if there's 22GB of data having to be downloaded to the browser?
by sodafountan
12/31/2025 at 4:50:32 AM
You can see it now, forgot to make it public.- 1. download_hn.sh - bash script that queries BigQuery and saves the data to *.json.gz
- 2. etl-hn.js - does the sharding and ID -> shard map, plus the user stats shards.
- 3. Then either npx serve docs or upload to CloudFlare Pages.
The ./toool/s/predeploy-checks.sh script basically runs the entire pipeline. You can do it unattended with AUTO_RUN=true
by keepamovin
12/31/2025 at 6:47:00 AM
Awesome, I'll take a lookby sodafountan
12/31/2025 at 10:28:10 PM
Is it possible to implement search this way?by dzhiurgis
12/30/2025 at 7:47:57 PM
Vfs support is amazing.by tehlike