An estimated 100 million users per day, looking at an average of 30 valid video views per day, would make the traffic estimate 30 * 100 million views 3 billion views per day.
3 B views per day translates to 3B / 86400 = 34723 write transactions per second.
Considering peak hours are 5 times the amount of regular hours, the system should be able to handle an approximate of 174K write transactions per second.
Considering each view records metadata related to video ID, user location (Country), view start time, view end time, user id, cookie
user ID: 16 bytes (nullable)
video ID: 16 bytes (uuid)
user location: 2 (country code)
start time: 8 bytes (timestamp)
end time: 8 bytes (timestamp)
guest id: 16 bytes (nullable)
The estimation of storage for a video view is an approximate of 66 bytes, assuming the database overhead would make the storage cost approximately 100 bytes per view
100 bytes * 3B = 300 GB per day
the total storage requirement would fall at 109.5 TB per year over a period of 5 years would be 550 TB
GET videos/{video-id}/views
HTTP/1.1 200 OK
{
"viewCount": number of views
}
POST videos/hearbeat
Request Headers:
Authorization: Bearer
Cookie: guestId=abc123...; HttpOnly; Secure; // optional
Payload:
{
"videoId": video ID,
"playbackPosition": current time,
"playing": true
}
Client Behavior:
- The client sends a heartbeat every 10 seconds while the video is actively playing.
- No heartbeat is sent while the video is paused.
- A final heartbeat is sent when playback ends or the user closes the page.
GET videos/{video-id}/views/analytics?start=datetime&end=datetime
Request Headers:
Authentication: Bearer
start: datetime
end: datetime
Response:
{
"videoId": Id of video,
"viewsCount": count of views during time period,
"countries": {
{
"country": "code",
"viewsCount": count
}
}
}
CDN: The CDN caches frequently requested view count responses closer to users, reducing latency and origin traffic, and ensuring a sub 50 ms latency. Knowing that the video view counters would be shown on the video page, we need to ensure fast response time for user retention.
LB: The load balancer to ensure optimal distribution of traffic over the producer service layer. especially during peak traffic.
View Count Service: Layer to manage the API requests, check them for fraud and add them to the Message Broker or log them to analytics layer.
Analytics Service: Read view event messages from Broker, batch write them to the ClickHouse DB view events data and generate analytics for video owners on request.
Fraud Detection Service: This is the initial traffic filter implementing fraud detection measures such as rate limiting by IP, ensuring video watch time validity, ensuring views are human, looking at request browser agents.
Message Broker: a message broker such as Kafka on the view count writing, allows the consumer to buffer and distributes events requests and flush them to the database on set time periods (1 or 5 seconds), this ensures balanced database writes, instead a heavy expensive per view database write. Kafka also ensures at least once delivery, by having append-only logs that retails messages for 7 days, (The aggregator ensures no duplicate counts through idempotency).
Kafka Partitioning by video_id and an extra number, allows for consumer auto scaling during peak views and viral videos, ensuring that multiple consumers can share the load.
Aggregator: Consumes messages from Kafka and aggregates requests to write to the DB.
Redis Cluster: in memory data store, to cache view counts, for faster retrieval time.
Also Redis is used to store aggregated count requests to be flushed to DynamoDB periodically.
DynamoDB: a key-value Store to store view counts, it provides low latency write and reads, high availability, and out of the box atomic writes.
ClickHouse: A columnar database to store analytics data, it supports very fast batch writes, and optimized fast analytical data querying.
View counts stored on DynamoDB for fast atomic writes, and reads
video_id, view_count
Video view events stored on ClickHouse database
actor_id (uuid)
video_id (uuid)
user_location (string)
start_time (DateTime64)
end_time (DateTime64)
actor_type (FixedString)
Aggregator: Consumes view events from Kafka and aggregates view counts in Redis using atomic increments. Every 5 seconds (or after a configurable threshold), the Aggregator flushes the accumulated counters to DynamoDB. Each flush operation is idempotent and assigned a unique transaction ID to prevent duplicate updates. Kafka offsets are committed only after a successful flush. If the Aggregator or Redis fails before the offset is committed, Kafka redelivers the events, allowing the Aggregator to rebuild the counters and retry the flush without double-counting.