Media pinning flow
This page explains how orbitdb-relay pins IPFS media CIDs while syncing OrbitDB databases.
Goal
When peers publish OrbitDB entries containing media references (for example imageCid), the relay
should:
- replicate the OrbitDB records,
- extract media CIDs from those records,
- pin those CIDs locally in Helia/IPFS,
- keep that media available to later peers that sync through the relay.
Trigger path
The event entrypoint is src/events/handlers.ts.
- Pubsub
message:- If the topic starts with
/orbitdb/, queue a sync task.
- If the topic starts with
- Pubsub
subscription-change:- Remember the subscribing peer as an in-memory hint for that OrbitDB topic.
- For each topic starting with
/orbitdb/, queue a normal sync task.
Sync tasks call DatabaseService.syncAllOrbitDBRecords(dbAddress) in src/services/database.ts.
Sync flow
For each sync task:
- Open the OrbitDB database from
dbAddress. - Wait for OrbitDB
updateevent(s). - If updates were observed, extract media CIDs from those update entries.
- If no update event is observed, scan
db.all()because replication may have completed before the listener was attached. - Enqueue discovered media CIDs asynchronously and record an exact local-state snapshot.
- Keep databases with replicated state open so the relay continues serving native OrbitDB heads.
Notes:
- Sync does not call
db.all()when an update event supplied the records. - If no update arrives within the observation window,
db.all()provides the fallback scan and sync proof. - A short update-burst window is used to collect multiple closely spaced updates in the same sync execution.
- An explicit
POST /pinning/syncmay reconnect peers previously observed subscribing to the database topic. The reconnect happens only after the database is open and its native OrbitDB heads topology is registered.
CID extraction rules (update payload)
CID extraction inspects update payload objects and currently supports:
imageCidimageCIDimage.cidprofilePictureprofilePictureCidprofilePictureCID- key/value style profile picture records:
_idequalsprofilePicture,profilePictureCid, orprofilePictureCID- CID is read from
value
mediaIdmediaIds(array)
Only non-empty string candidates are kept, deduplicated per sync run and across queue state.
Pinning mechanics
Pinning is done through the Helia pin API, not direct blockstore writes:
ipfs.pins.add(CID.parse(cidString))
Pinning uses an internal PQueue in DatabaseService:
- Concurrency:
4 - Non-blocking from the sync method's perspective
- Dedupe sets:
queuedImageCidsprevents duplicate queued workpinnedImageCidsavoids re-pinning already pinned CIDs
Shutdown behavior
Shutdown is coordinated across relay, handlers, metrics, and the DB service:
- Event handlers stop accepting new sync tasks and drain/clear the queue.
- The database service enters shutdown mode, drains/clears the pin queue, and stops OrbitDB.
- The metrics HTTP server is closed.
- Helia/IPFS and libp2p are stopped.
This reduces hanging test runs and avoids lingering background tasks.
Test coverage
Integration coverage exists in mocha/relay-media-replication.mjs:
- Alice writes a post with an image CID.
- The relay syncs and pins those CIDs.
- Alice goes offline.
- Bob connects and syncs the same DB.
- Bob fetches each CID from his blockstore and the bytes are asserted against Alice's original content.
Additional unit-style integration coverage lives in mocha/database-update-event-pinning.mjs,
which verifies that a single update event triggers pinning and that db.all() is not used for this
update-driven pinning path.
Related
- Peer recovery — why the relay remembers subscribing peers and how explicit recovery reconnects them.
- HTTP API — the
/pinning/*and/ipfs/*routes.