Importing SQLite Database Snapshots
Overview
A new gateway builds its index of the Arweave network block by block. That can take days or weeks, depending on your hardware and on how much data you index. You can skip most of it in one of two ways:
- Import a SQLite snapshot. The 2025-04-23 snapshot holds transactions and the data items uploaded through ArDrive products, including Turbo, up to block 1645229.
- Bootstrap from L1 bands (Release 85 or later). Your gateway follows the chain tip and, while it runs, fills in the Arweave base layer beneath it (blocks, transactions, tags and owners) from signed bands that another gateway publishes. Data items are not included.
Looking to resolve data items faster without a full database? Index Sharing lets your gateway subscribe to another gateway's root transaction indexes, about 21 GB today and kept current, instead of importing a snapshot.
Note
The below instructions are designed to be used in a linux environment. Windows and MacOS users must modify the instructions to use the appropriate package manager/ command syntax for their platform.
Unless otherwise specified, all commands should be run from the root directory of the gateway.
Import a SQLite Snapshot
SQLite snapshots are large and hard to update in steps, so ar.io distributes them over BitTorrent.
IMPORTANT
Importing a snapshot deletes your existing database and replaces it with the snapshot.
Install a Torrent Client
Any BitTorrent client works. For example, transmission-cli:
# Ubuntu/Debian
sudo apt-get install transmission-cli
# CentOS/RHEL
sudo yum install transmission-cli
# macOS
brew install transmission-cliDownload the Snapshot
transmission-cli "magnet:?xt=urn:btih:62ca6e05248e6df59fac9e38252e9c71951294ed&dn=2025-04-23-sqlite.tar.gz&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Fp4p.arenabg.com%3A1337%2Fannounce&tr=https%3A%2F%2Ftracker.bt4g.com%3A443%2Fannounce"This downloads 2025-04-23-sqlite.tar.gz, about 42.8 GB. Check that it arrived whole with ls -lh 2025-04-23-sqlite.tar.gz.
Seeding the torrent after the download is not required, but it keeps the snapshot available for other operators.
Extract the Archive
tar -xzf 2025-04-23-sqlite.tar.gz
ls -la 2025-04-23-sqlite/This creates a directory named after the file, without .tar.gz, that holds the SQLite database files. If you use a different snapshot, replace the file name.
Stop the Gateway
docker compose downIf you start your gateway with your own -f files, pass the same ones to every docker compose command on this page.
Replace the Database
Back up your existing database (optional), then move the snapshot into data/sqlite:
mkdir sqlite-backup
mv data/sqlite/* sqlite-backup/
mv 2025-04-23-sqlite/* data/sqlite/If you skip the backup, delete the old files with rm data/sqlite/* before the last command.
Start the Gateway
docker compose up -dVerify the Import
Watch the gateway logs for the block height being imported:
docker compose logs -f coreThe 2025-04-23 snapshot was taken at block 1645229. If the import worked, the gateway imports blocks from 1645230 upward. The Grafana extension also shows the last block imported.
Bootstrap from L1 Bands
Release Requirement: L1 bands, and the ar-io-node CLI that imports
them, require gateway Release 85 or later, on your gateway and on the
publisher you subscribe to.
Upgrade to Release 87
From Release 87, L1 bands are layout l1-4, whose witness files bind
every block from genesis, so your gateway verifies and fills the whole
chain with nothing more to set. A gateway still on Release 86 keeps the
l1-3 bands it has installed, but refuses each band its publisher
upgrades to l1-4, and tries it again every poll, until it runs Release
87. If you subscribe to L1 bands, upgrade.
An L1 band (parquet-l1) holds the Arweave base layer for a range of block heights, as Parquet files: blocks, transactions, tags and owners. Gateways that publish them sign every file, and your gateway checks each one before it installs the band. Your gateway then fills its core.db from the installed bands, instead of indexing the chain from block 0.
L1 bands carry no data items. Your gateway still unbundles the bundles your filters select, and it does that for imported history only if you also set BACKFILL_BUNDLE_RECORDS.
There are two ways to do it:
- Fill the history while the gateway runs, as below. This is the usual way. It works on a new gateway, on one that began mid-chain, and on one with a hole in what it holds.
- Import with the gateway stopped. Faster, but only for a new
core.dbthat the gateway has never run on.
With L1_BAND_IMPORT_ENABLED=true, core runs ar-io-node index-l1-import --fill every 10 minutes (L1_BAND_IMPORT_INTERVAL_SECONDS, counted from the end of the last run), as a child process, so a crash in the import can't take the gateway down. A fill writes only the heights core.db lacks beneath what the block importer writes itself, so nothing your gateway indexed is rewritten. It writes in short transactions with a pause after each, so the gateway keeps indexing and serving the whole time.
Verified Before It Is Written
From Release 86, before a band's first row reaches core.db, the import checks it, with the gateway running or stopped:
- The band, whole, with the same code as
index-l1-verify --bands-dir: its name is its identity, its files and columns are the layout's, its tables digest to whatband.jsonsays, and every block, transaction, link, tag and wallet row passes. - Its edges, against
core.db. The first height written must build on the blockcore.dbholds below it, and the blockcore.dbholds above the last height written must build on the band. - A binding to the chain. The block above, when
core.dbholds one the gateway synced from its own nodes, or one an earlier import wrote bound to the chain without accepting a gap. From the 2.0 fork, the band'shash_list_merklerecurrence then binds every block hash, weave size andtx_rootbeneath it. Otherwise, an anchor fetched from the sources (--anchor-from), asindex-l1-verifydoes. Anl1-4band below the 2.0 fork that ends at a pinned height is anchored there by its witnessed folds, with no header fetched. - A sample. What the binding does not reach, such as transaction ids, their order and every transaction field, is checked by the same keyed sample of headers and signatures as
index-l1-verify, from the same sources. Without a sample the import is refused unless you accept that (--allow-unsampled). A sample that could not be read is an outage, not evidence: the band is refused asunreadableand a later run tries again. - What nothing binds is refused unless accepted. An
l1-4band's witness files bind its blocks below the 2.0 fork and its blocks holding a format-1 transaction with data, so a whole chain ofl1-4bands imports with no--allow-*flag, and the importer writes the witnessed data roots andtx_roots over whatevercore.dbkept. What is left is blocks whose transactions or owners' keys the band lacks (--allow-incomplete). A band without witnesses (l1-3) has those first two kinds unbound, and is refused below height 2,000,000 unless they are accepted (--allow-unbound,--allow-unverified-format1).
A refused band removes nothing that was there before. While the gateway runs, ranges are filled from the top down, so each is bound by the block the gateway holds above it: its own, or one the range above it just wrote and verified. The run stops at the first range refused.
The gateway's own fill takes these settings from .env:
| Variable | Default | Does |
|---|---|---|
L1_BAND_IMPORT_SAMPLE_FROM | TRUSTED_NODE_URL and the TRUSTED_GATEWAYS_URLS gateways not marked trusted: false | The sources to anchor at and sample from. They must resolve to at least two distinct machines; the worker warns at its first run when they don't, and the import refuses to run on them |
L1_BAND_IMPORT_ALLOW_UNVERIFIED | false | Legacy, for l1-3 bands only: true passes --allow-unbound --allow-unverified-format1, so their blocks below height 2,000,000 are filled. l1-4 bands don't need it. Never --allow-incomplete. Every other check still runs |
L1_BAND_IMPORT_ALLOW_UNSAMPLED | false | true passes --allow-unsampled: fill without a sample, so transaction ids and fields are checked against nothing on the chain. A sample whose sources are only unreachable is never waived: that fill waits and is tried again |
A refused fill is not re-verified every interval: the worker waits, twice as long each time (up to a day), until the bands directory changes. One refused only because its sources could not be read is tried again after an interval, twice as long each time it repeats (up to two hours), and at once when the bands directory changes.
On a new gateway, set this up before the first start and leave START_HEIGHT unset: run the setup script below without --restart (on a gateway that isn't running, --restart would start core and the sidecar alone), then start the gateway as usual (the Index Sharing sidecar starts with it). With an empty core.db, the block importer then starts at the chain tip instead of block 0, and the history beneath it arrives as the bands do.
Prerequisites
- A gateway on Release 85 or later, and Release 87 to take
l1-4bands - Room on disk: about 15 GB for the bands, and about 120 GB for a
core.dbthat covers the whole chain. The setup script sets the Index Sharing disk budget to 100 GiB if it is unset, and warns if yours is lower
Subscribe with --l1
L1 bands arrive through Index Sharing. Run the setup script with --l1, first with --dry-run to see the changes:
./tools/index-swarm-setup --subscribe 34LYvMptiDvBP5sqfh1oAd6Q4qFsy4PWaZ1HTFmML7h5 --l1 --torrent --dry-run
./tools/index-swarm-setup --subscribe 34LYvMptiDvBP5sqfh1oAd6Q4qFsy4PWaZ1HTFmML7h5 --l1 --torrent --restart--l1 subscribes to the publisher's L1 bands as well as its root-TX bands, sets L1_BAND_IMPORT_ENABLED=true, and restarts core. To move bands over HTTP only, see HTTP Only.
Check It
./tools/index-swarm-statusBands install under data/indexes/installed/parquet-l1, and each fill writes what they hold. On the gateway's /ar-io/__gateway_metrics, l1_band_import_runs_total{result} counts fills (filled, idle or failed, and from Release 86 deferred for a refused fill waiting for the bands to change, and unreadable for one whose sources could not be read; a refused band counts as failed), l1_band_import_rows_total the rows written, and l1_band_import_uncovered_heights the heights no installed band holds yet.
Uncovered heights are normal for a while: bands still downloading, and, on a new gateway, the heights between the newest band and where it began, which the publisher's next tip band holds, usually within a day. A run with nothing to fill writes nothing. Only one import or fill runs against a core.db at a time.
Import L1 Bands with the Gateway Stopped
The fastest way to load the whole chain into an empty database, at the cost of the gateway being down while it runs. It is only for a new core.db that the gateway has not run on yet. The import refuses a core.db that another process is writing to, and one that holds unstable blocks (the new_* tables). A gateway that has run always holds some, because it moves blocks to the stable tables only once they are 18 deep. For a gateway that already runs, fill while it runs instead.
Prerequisites
- A gateway on Release 85 or later, and a migrated
core.db(yarn db:migrate up; the core service also runs it when it starts). The import refuses one without its ledger table - Room on disk: about 15 GB for the bands, and about 120 GB for a
core.dbthat covers the whole chain. The import refuses to start without the space it needs - Time: the whole chain takes about 15 hours on 12 cores. The import logs its progress once a minute
Subscribe to L1 Bands
A subscription takes L1 bands only when it names them, so add parquet-l1 to the name list in INDEX_SWARM_SUBSCRIBE:
INDEX_SWARM_SUBSCRIBE='[{"publisher":"34LYvMptiDvBP5sqfh1oAd6Q4qFsy4PWaZ1HTFmML7h5","name":["root-tx-index","parquet-l1"]}]'Then recreate the index-swarm sidecar and wait until ./tools/index-swarm-status shows the bands installed under data/indexes/installed/parquet-l1.
Stop the Gateway
Stop only the core service, with the same -f files your gateway was started with:
docker compose stop coreImport the Bands
Run it from the gateway's directory. The tool runs in the core image, so the host needs only Docker:
./tools/ar-io-node index-l1-import \
--bands-dir data/indexes/installed/parquet-l1 \
--core-db data/sqlite/core.db \
--cache-mib 8192 \
--anchor-from https://<gateway one>,https://<gateway two>From Release 86, every planned band is verified first, as one contiguous run anchored once at its top, and a plan that does not verify writes nothing. With an empty core.db there is no block above to bind the bands, so give --anchor-from with sources that resolve to at least two distinct machines; they are also where the sample is drawn from. With l1-4 bands (Release 87) no --allow-* flag is needed; l1-3 bands below height 2,000,000 would need --allow-unbound --allow-unverified-format1 (see Verified Before It Is Written). The check's DuckDB spills to a folder beside core.db, capped at --spill-max (default 128GB) and at 80% of the space free there, and never into the last --spill-reserve-gb (default 100).
Bands import in height order, lowest first. --cache-mib sets SQLite's page cache for the run (default 1024 MiB); give it what your machine can spare, since a larger cache keeps a long import fast. An interrupted run is safe: run the command again and it carries on from the last band that landed. A band that fails stops the run: the bands imported before it are kept, the one that failed is cleared, and the result goes to stderr with exit code 1. --max-bands splits the work across several runs.
Start the Gateway
docker compose up -d --no-deps coreThe block importer continues from the highest height imported.
To check a set of bands, or your gateway's own core.db, against the chain, use ./tools/ar-io-node index-l1-verify (see The ar-io-node CLI). Every option, and the JSON each command prints, is in the ar-io-node CLI reference.
How is this guide?
Content Moderation
Gateway operators have the right and ability to blocklist any content or ArNS name that is deemed in violation of its content policies or is non-compliant with local regulations.
The ar-io-node CLI
The gateway's command-line tool: build and check index bands, fill and verify a gateway's L1 index, and run ar.io CLI commands, with JSON on stdout