Build the complete replay architecture
A production replay service includes:
- Browser recorder: initial snapshot, incremental changes, selected interactions, masking, blocking, sampling, and batching.
- Ingestion API: project-scoped credentials, authentication, validation, rate limits, abuse protection, and size limits.
- Durable delivery: queue or stream, retries, deduplication, backpressure, dead-letter handling, and regional routing.
- Recording store: compressed chunks in object storage with lifecycle, encryption, versioning, and deletion controls.
- Metadata and search: Visit, Company, User, route, time, status, chunk references, and safe derived fields in a database or index.
- Processing: session assembly, ordering, manifests, quality flags, privacy transformations, and derived metadata.
- Replay player: authorized manifest loading, chunk delivery, seeking, decompression, missing-chunk handling, and compatibility logic.
- Governance: least privilege, audit logs, retention, deletion, backups, monitoring, administration, incident response, and upgrades.
The recorder is one layer of eight
1
Browser recorder
Snapshot, incremental changes, sampling, batching — masking and blocking applied here, before anything leaves
2
Ingestion API
Project-scoped credentials, validation, rate limits, size limits, abuse protection
3
Durable delivery
Queue or stream, retries, dedup, backpressure, dead-letter, regional routing
4
Processing workers
Session assembly, ordering, manifests, quality flags, privacy transformations
5
Recording store object storage
Immutable compressed chunks, lifecycle, encryption, versioning, deletion controls
manifest
order and versions — 56 chunks for a 14-minute Visit at 15 seconds each
6
Metadata & search database or index
Visit, Company, User, route, time, status, chunk references, safe derived fields
One recording payload per relational row
Large mutable blobs put pressure on backups, replication, queries and deletion
7
Replay player
Server-side authorization on every view, manifest loading, chunk delivery, seeking, decompression, missing-chunk handling, compatibility logic
8 — Governance, across every layer above
Do not store every recording payload as one ordinary relational row. Large mutable blobs create pressure on backups, replication, queries, and deletion. A common pattern is immutable compressed chunks in object storage, a small manifest describing order and versions, and relational/search metadata for discovery and authorization.
Own compatibility and reliable ingestion
A replay format is a long-lived contract. Version the recording envelope with recorder version, schema or event version, player compatibility range, plugin versions, compression codec, privacy-policy version, project, Visit, sequence, timestamps, checksum, and finalization state.
{
"recording_version": 4,
"recorder_version": "rrweb-x.y.z",
"player_min_version": 3,
"privacy_rule_version": 12,
"project_id": "project_12",
"visit_id": "visit_220",
"sequence": 18,
"started_at": "2026-08-04T10:15:30Z",
"codec": "gzip",
"checksum": "sha256:...",
"final": false
}Upgrade recorder and player deliberately. Keep compatibility fixtures from old versions, mutation-heavy pages, masked inputs, plugins, partial sessions, long idle periods, and unsupported elements. Decide how old recordings remain playable: retain a compatible player, migrate data, or publish an explicit playback horizon.
Batching improves efficiency but introduces partial-delivery and ordering questions. Give each batch a stable Visit ID and sequence. Treat retries as potentially duplicated after an ambiguous timeout. Preserve client and server timestamps, but never assume device clocks are correct.
- Use idempotency keys or sequence-aware writes.
- Validate size, project, version, and permitted fields.
- Separate accepted, rejected, retried, duplicate, late, and dropped counts.
- Do not rely on page-close delivery as guaranteed.
- Mark a Visit complete, partial, abandoned, expired, or corrupt explicitly.
- Apply backpressure before queues or databases collapse.
- Load-test retry storms and release spikes, not only average traffic.
A playback can look complete while missing early snapshots, intermediate chunks, or final events. Surface quality flags and gaps to reviewers instead of silently presenting reconstructed continuity.
Estimate storage, requests, and total cost
There is no universal megabytes-per-session number. Size depends on duration, page complexity, mutation frequency, scroll/mouse sampling, canvas and iframe handling, optional console/network data, masking, compression, batch size, and plugins. Measure representative production traffic after privacy rules are applied.
Recorded visits/day = eligible visits/day × sampling rate
Average recording size = recorded duration × measured compressed bytes/time
Daily storage = recorded visits/day × average recording size
Primary retained storage = daily storage × retention days
Effective retained storage = primary storage × retained-copy factor
Monthly ingestion = daily storage × billing days
Peak recorded bytes/second = simultaneous recording users × bytes/user/second
Designed peak throughput = peak recorded bytes/second × headroom factor
Chunks/visit = recorded seconds ÷ target chunk seconds (round up)
New objects/day = recorded visits/day × (chunks/visit + manifests/visit)
Requests/day = recorded visits/day × (batch requests/visit + lifecycle requests/visit)
Monthly reads = replay views/month × objects loaded/replay
Monthly delivery = replay views/month × bytes delivered/replayFor metadata, model a Visit row plus chunk rows and searchable/derived fields, then measure database index, free-space, log, replica, and backup overhead. Object count matters even when bytes are modest because requests, listing, lifecycle, replication, and deletion have costs.
Worked example: 4,000 users become 5.3 million objects
Sampling narrows the population
Eligible Visits 4,000 DAU × 1.4
Recorded Visits × 55% sampling
Sampling is the one lever that scales every number below it at once.
Bytes
Average recording
14 min × 180 KB/min
2.52MB
Daily
3,080 × 2.52 MB
7.76GB
Primary retention
× 30 days
232.8GB
Effective, two copies
× 2
465.7GB
Objects
Chunks per Visit
840 s ÷ 15 s
56chunks
Objects per Visit
+ 1 manifest
57objects
New per day
3,080 × 57
175,560
Live at 30 days
× 30
5.27million
Requests & delivery
Writes per Visit
56 batches + start + finalize
58
Writes per month
178,640/day × 30, before retries
5.36million
Reads per month
20,000 views × 57
1.14million
Delivered per month
20,000 × 2.52 MB
50.4GB
Peak, sized separately from the average
Peak concurrent Users
420
Recording at peak
231
Peak throughput
693KB/s
Design for
1.39MB/s
The 180 KB/minute rate is hypothetical. Replace it with measured p50, p90 and p99 after privacy rules are applied — long or mutation-heavy Visits can dominate the distribution, and every figure above is “before” indexes, free space, logs, replicas and backups.
Total cost combines storage, write/read operations, queue, database/index/cache, compute, delivery, backups, monitoring, security tooling, engineering labor, upgrades, privacy testing, deletion, incidents, and support.
Monthly TCO = storage + copies + writes + reads + queue
+ network delivery + database/index/cache
+ compute + observability/security + labor
+ backup/restore + incidents + supportSecure, minimize, retain, and delete replay data
Protect the entire path: TLS, project-scoped collection credentials, authenticated and authorized ingestion, request and resource limits, centralized secrets, private object storage, server-side authorization for every replay view, employee SSO/MFA, least privilege, environment separation, dependency maintenance, and incident response.
Privacy controls belong before storage. Mask or block sensitive fields and rendered content, exclude high-risk routes, normalize URLs, and treat console/network capture as separate high-risk decisions. Minimize identity to stable pseudonymous User and Company IDs where the purpose requires linkage. Test the stored payload and player—not merely SDK configuration.
Make retention and deletion explicit
Retention should vary by purpose, project, route, or capture class where operationally safe. Deletion must remove or make inaccessible:
- recording chunks and manifests;
- metadata rows and search-index entries;
- cache entries and generated clips;
- exports and temporary processing copies;
- User, Company, Visit, and project lookup references;
- replicas, versions, and backups according to the documented backup policy.
Use tombstones or deny lists when immutable backups cannot be altered immediately, prevent restored data from reappearing, and verify deletion through each lookup key. Handle legal/security holds through a separate authorized process.
Self-hosting can provide a chosen data boundary, but it does not automatically provide minimization, lawful collection, access control, deletion, or compliance. Those are design and operating outcomes.
Make search, playback, and reliability observable
Search is part of the product. Index only the metadata required for the stated workflows: project, Visit, time, normalized Page, pseudonymous User and Company IDs, processing state, quality flags, and approved event markers. Every searchable field expands storage, privacy, and deletion work.
For playback, load a small manifest first, fetch chunks on demand, prefetch conservatively, parse/decompress off the main interaction path where possible, support seeking, show missing chunks, set limits for very long Visits, and define how authorized assets are reconstructed. Archived playback may need an explicit restore state.
| Layer | Measure | Failure question |
|---|---|---|
| Ingestion | Accepted/rejected bytes, requests, retries, duplicates, queue age, throttling | Are recordings reaching durable storage? |
| Quality | Missing initial snapshots, sequence gaps, partial/finalized Visits, clock skew | Does a complete-looking replay omit evidence? |
| Processing | Worker lag, manifest errors, privacy-rule failures, index lag | Can recordings become searchable and playable? |
| Playback | Manifest/chunk latency, error rate, seek success, incompatible versions | Can authorized reviewers use old and current recordings? |
| Governance | Denied access, deletion backlog, retention drift, backup age, restore tests | Are access and lifecycle controls working? |
Set service-level indicators before the pilot and test dependency outage, queue saturation, corrupt chunk, incomplete Visit, lost index, object-store denial, expired asset, old recorder version, deletion failure, and backup restoration.
Compare managed, self-hosted, and hybrid replay
Self-hosting may fit when a hard data-location boundary, custom retention/deletion, deep integration, unusual scale, or replay-as-core-product strategy justifies ownership—and the team already operates queues, object storage, databases, monitoring, security, backups, and on-call.
Managed replay may fit when rapid deployment, polished search/player workflows, browser compatibility work, mobile support, vendor support, and limited internal operations matter more than full infrastructure control. Verify data flows, contract, regional processing, subprocessors, masking, exports, retention, deletion, access, and pricing.
Use hybrid boundaries deliberately
Hybrid options include self-hosted filtering/ingestion with managed processing, customer-controlled storage with a managed UI, managed replay with regional/private networking, recording only selected high-value Visits, or structured analytics internally with short-retention replay elsewhere. Every boundary needs a data contract and incident owner.
How far ownership travels, layer by layer
Layer
Provider operates it
You operate it
Durable delivery & queue
Metadata & search index
Replay player & compatibility
Backups & restore
Upgrades & browser support
Ingestion API
Recording store
Monitoring & alerting
Incident ownership
Retention & deletion
Access & authorization decisions
Masking rules & what you record
Where managed replay puts it
Where self-hosting puts it
Hybrid lands anywhere on this bar
The last two rows never move: masking rules, what you choose to record, and who may watch a recording are yours in every deployment model. For every row that does move, hybrid needs a written data contract and a named incident owner — it is an allocation, not an architecture.
Pilot representative routes and privacy rules. Measure p50/p90/p99 recording size, peak ingestion, requests, objects, index growth, search/playback latency, recorder overhead, incomplete-session rate, masking regressions, deletion, restore, upgrade compatibility, incident procedures, and staff time. Assign long-term ownership before launch.
How Hymetry combines analytics and replay
Hymetry links open-source B2B product analytics with Visits so teams can move from Pages and product areas to Companies, Users, and selected session evidence. Shared stable IDs make the analytical and replay paths inspectable without requiring one undifferentiated storage pipeline.
The current open-source deployment still requires teams to review tracker configuration, masking and filtering, authentication, storage, retention, deletion, backups, upgrades, licensing, and operational ownership. Inspect the repository and current terms before adopting it.
Frequently asked questions
Is rrweb a complete self-hosted replay platform?
No. It provides recording and replay building blocks; production systems still need ingestion, storage, indexing, access, privacy, deletion, monitoring, backups, and operations.
Is self-hosted replay more private?
Only if collection scope, masking, access, retention, deletion, and infrastructure are designed and operated well. Location alone does not guarantee privacy.
Is self-hosted replay cheaper?
Not universally. Compare infrastructure plus engineering, compatibility, security, privacy, support, incidents, and opportunity cost against a managed contract.
How much storage does replay need?
Measure your application's recording-size distribution after privacy rules. Duration, mutation rate, sampling, optional diagnostics, compression, chunking, retention, and copies determine it.
Can recordings be stored in PostgreSQL?
Possible at modest scale, but large chunk payloads often fit object storage better while PostgreSQL stores searchable metadata and manifests.
How long should recordings be retained?
Only as long as the documented purpose requires, subject to legal and contractual review. Short detailed retention with longer aggregate retention is a common pattern.
What is the main compatibility risk?
A new recorder, plugin, compression method, or player may not reproduce older payloads. Version the envelope and keep legacy fixtures and a playback policy.
What should a production pilot measure?
Privacy, recorder overhead, size distribution, peaks, retries, gaps, object/request volume, search and playback, deletion, restore, upgrades, incidents, and staff time.
Sources
Provider-specific billing, service behavior, and current project support should be rechecked before implementation.
rrweb and browser delivery
Object storage, queues, and databases
Security, privacy, and incident response
Reliability, observability, and Hymetry
Additional references from the original article
- GitHub: Plugin Api
- MDN: Keepalive
- AWS: Pricing
- AWS: Lifecycle Expire General Considerations
- AWS: Using Server Side Encryption
- AWS: Using Presigned Url
- AWS: Object Lock
- AWS: Lifecycle Transition General Considerations
- AWS: Standard Queues At Least Once Delivery
- AWS: Standard Queues
- AWS: FIFO Queues Understanding Logic
- Apache Kafka: Documentation
- Apache Kafka: Design
- PostgreSQL: Storage Toast
- PostgreSQL: Indexes
- OWASP: 0x11 T10
- OWASP: 0xa4 Unrestricted Resource Consumption
- OWASP: Transport Layer Security Cheat Sheet
- OWASP: Multifactor Authentication Cheat Sheet
- ICO: Data Minimisation
- ICO: Storage Limitation
- ICO: Security Outcomes
- ICO: Data Protection By Design And By Default
- EDPB: Guidelines 42019 On Article 25 Data Protection By Design And By Default En
- Google SRE: Implementing Slos
- Google SRE: Alerting On Slos
- Google SRE: Monitoring
- OpenTelemetry: Docs
- GitHub source: README
- GitHub: Models.Py
- GitHub: Views.Py



