What is an Activity Feed?
An activity feed shows users a chronological stream of events from people and things they follow: “Alice liked your photo”, “Bob published a new article”, “Your order shipped”. LinkedIn’s news feed, GitHub’s activity feed, and Shopify’s store activity feed are all examples. The core design question is the fan-out strategy: do you write events to each follower’s feed at write time (push/fan-out-on-write), or compile the feed at read time (pull/fan-out-on-read)? Each trades write amplification against read latency.
Requirements
- Show activities from followed users/entities in reverse chronological order
- Activity types: follow, like, comment, publish, purchase, status change
- Feed latency: new activity appears in follower feeds within 5 seconds
- Read feed: <50ms for first page of 20 items
- 100M users, average 500 followers per user, 10K events/second
- Pagination: infinite scroll with cursor
Data Model
Activity(
activity_id UUID PRIMARY KEY,
actor_id UUID NOT NULL, -- who performed the action
verb VARCHAR NOT NULL, -- 'liked', 'commented', 'published', 'followed'
object_type VARCHAR, -- 'post', 'photo', 'order'
object_id UUID,
target_id UUID, -- who/what was acted upon (nullable)
metadata JSONB, -- extra context: {'post_title': '...'}
created_at TIMESTAMPTZ NOT NULL
)
UserFeed(
user_id UUID NOT NULL,
activity_id UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL, -- denormalized for sort
PRIMARY KEY (user_id, activity_id)
)
-- UserFeed is the fan-out table: one row per follower per activity
Fan-out on Write (Push)
def on_new_activity(activity):
# Store the activity
db.insert(Activity(**activity))
# Fan out to all followers
followers = get_followers(activity.actor_id) # could be millions
# For small follower counts: synchronous DB inserts
if len(followers) <= 1000:
db.bulk_insert([
UserFeed(user_id=f, activity_id=activity.id,
created_at=activity.created_at)
for f in followers
])
else:
# For large follower counts (celebrities): async via Kafka
kafka.produce('feed-fanout', {
'activity_id': activity.id,
'actor_id': activity.actor_id,
'created_at': activity.created_at.isoformat()
})
# Workers consume and fan out in parallel batches
def read_feed(user_id, cursor=None, limit=20):
# Single fast indexed query on UserFeed
query = '''
SELECT a.* FROM UserFeed uf
JOIN Activity a ON uf.activity_id = a.activity_id
WHERE uf.user_id = :uid
'''
if cursor:
query += ' AND uf.created_at < :cursor'
query += ' ORDER BY uf.created_at DESC LIMIT :limit'
return db.query(query, uid=user_id, cursor=cursor, limit=limit)
Hybrid: Redis Feed Cache
For hot users who read their feed frequently, cache the feed in a Redis sorted set:
# Score = timestamp (Unix); member = activity_id
def add_to_feed_cache(user_id, activity_id, timestamp):
key = f'feed:{user_id}'
redis.zadd(key, {activity_id: timestamp})
redis.zremrangebyrank(key, 0, -1001) # keep only latest 1000
redis.expire(key, 86400) # TTL 24h
def read_feed_cached(user_id, before_ts=None, limit=20):
key = f'feed:{user_id}'
max_score = before_ts or '+inf'
ids = redis.zrevrangebyscore(key, max_score, '-inf',
start=0, num=limit, withscores=False)
if len(ids) < limit:
# Cache miss or expired: fall back to DB
return read_feed_from_db(user_id, before_ts, limit)
return get_activities_by_ids(ids) # batch fetch activity details
Celebrity Problem
A user with 50M followers posts once — 50M fan-out writes would take hours. Solutions:
- Async fan-out via Kafka with many parallel workers
- Pull for celebrities: regular users get fan-out-on-write; celebrity posts are fetched at read time and merged into the feed
- Cap fan-out: only fan out to users who have been active in the last 30 days
Key Design Decisions
- Fan-out on write for most users — fast reads; feed query is a simple indexed scan
- Async fan-out for high-follower accounts — prevents write spikes blocking the DB
- Redis sorted set cache — feeds can be served entirely from cache for active users
- Activity object model (actor, verb, object) — extensible; new activity types don’t require schema changes
- Cursor pagination on created_at — stable, O(1) regardless of feed depth
Activity feed and news feed design is a common topic in Meta system design interview questions.
Activity feed fan-out architecture is discussed in Twitter system design interview preparation.
Activity feed and professional network feed design is covered in LinkedIn system design interview guide.