Neo4j Certified Professional study materials
Neo4j Certified Professional study resources

Supernode in a Social Feed and Likers Query

Neo4j Certified Professional · Put your knowledge to work in context.

All case studies

Supernode in a Social Feed and Likers Query

A reading app stores 2 million :User nodes, 30 million :Post nodes, 400 million :LIKED relationships with an at timestamp, and about 360 million :FOLLOWS relationships. The average user follows 180 accounts, but one celebrity has 5 million followers. Feed query A returns the latest 20 posts liked by accounts the current user follows: it matches (me)-[:FOLLOWS]->(f)-[l:LIKED]->(p), filters l.at > $since, then orders by l.at and limits 20. For users who follow the celebrity, p95 is 11 seconds; for other users it is 300 ms. Query B lists the first 50 likers of a post ordered by l.at; for a viral post with 2.1 million likes it takes 3 seconds and deep pagination is worse. The team cannot fan out every like to every follower and wants to keep the LIKED relationship for audit.

1. Explain why the celebrity causes Query A to slow down even though the query has a timestamp filter. Does converting LIKED to a :Like node solve the problem?

Read the worked answer

The filter is applied after expansion. Starting from the celebrity, the traversal expands the celebrity's outgoing :LIKED relationships, then checks l.at; it cannot jump directly to only recent relationships without an index or a narrower access path. The degree of the celebrity's LIKED relationships is the supernode. Reifying LIKED as a :Like node with (:User)-[:LIKED]->(:Like)-[:ON]->(:Post) does not remove the degree: the celebrity still has millions of outgoing relationships to :Like nodes, and the feed query adds a join. It can help if you need to index Like.at globally or attach more metadata, but it is not a fix by itself. The fix is to avoid traversing the celebrity's full history at read time: precompute or fan out for normal users, time-bucket relationship types or Like nodes, or merge a cached celebrity recent-likes list.

2. Propose a read/write strategy that meets feed latency for both normal and celebrity follows without fanning out every like to every follower. Include what you would store.

Read the worked answer

Use a hybrid feed. On like, write the audit edge (:User)-[:LIKED {at}]->(:Post) always. For users with follower count below a threshold, for example 10k, also write a feed entry (:User)-[:FEED_ITEM {at}]->(:Post) to each follower, in batches; this makes the common feed a bounded 20-row lookup. For celebrity accounts above the threshold, skip fan-out. At read time, take the precomputed FEED_ITEM rows for normal followees and merge them with recent LIKED rows for the few celebrity followees, using l.at > $since and a small limit; cache the celebrity segment for a short TTL. Store followerCount on :User to make the threshold decision cheap. This keeps write amplification bounded and avoids the 5M-degree traversal on the hot path. The tradeoff is that celebrity likes may appear with slight delay or require a merge.

3. What indexes/constraints and pagination would you use for Query B, and how would you avoid deep SKIP costs?

Read the worked answer

Create a uniqueness constraint on :Post(id) and :User(id). For the likers query, start from the post: MATCH (p:Post {id:$postId})<-[l:LIKED]-(u:User) RETURN u ORDER BY l.at DESC LIMIT 50. A relationship range index on :LIKED(at) can help queries that scan likes by time, but it does not make expansion from one post ordered. The expansion from a viral post still touches many relationships. Use cursor pagination: first page orders by l.at DESC LIMIT 50; next page adds WHERE l.at < $lastAt with a tie-breaker on user id and limits again. Avoid SKIP 100000. If first-page latency is critical, maintain a small :RECENT_LIKER summary or cache the top N likers per post; do not scan all 2.1M likes to return 50.