A Reddit discussion looks like a page of text, but its structure is closer to a tree that changes while people are reading it. New replies appear at different depths, votes change the order, moderators alter visibility and popular posts attract far more traffic than ordinary ones.
Storing the text is only the beginning. The system also needs to preserve relationships, retrieve useful portions of a discussion and keep several views consistent as the conversation evolves.
Introduction
Reddit's engineering team has published a detailed account of modernising its comment backend. It identifies comments, accounts, posts and subreddits as core models, and describes the move from a legacy Python service to domain-specific Go services.
That account provides a documented storage foundation. We will combine it with an illustrative discussion model to explain nested replies, pagination, caching and updates. The sample fields and ranking approaches below are teaching examples, not a claim to reproduce Reddit's complete current schema or ranking algorithm.
The useful principle is to separate the durable conversation from the different ways readers want to explore it.
Posts and Comments Have Different Roles
A post establishes the main discussion: its author, community, title and content or external reference. A comment contributes to that discussion and may reply either to the post or to another comment.
In a teaching schema, a comment could contain its own ID, a post ID, a parent reference, author ID, body, timestamps and visibility state. Keeping the post ID even for deeply nested replies makes it easy to identify which overall discussion contains the comment.
The parent reference answers a different question: where does this reply sit in the local conversation? Both relationships matter.
Stable comment identity lets a permalink continue to identify the same contribution after the body is edited or its ranking changes. A comment's position on today's page is therefore a poor substitute for its actual identity.
A Tree Can Be Stored as Individual Records
Imagine a post P with comments A and B. Comment C replies to A, and D replies to C. The visual nesting can be reconstructed from those parent relationships without storing the whole thread as one giant JSON document.
This arrangement allows a focused edit to C without rewriting every sibling and ancestor. It also supports direct retrieval when someone follows a link to one comment.
The service must still protect structural rules. A comment should not become its own ancestor, and a parent reference should belong to the appropriate discussion. Validation prevents malformed relationships from producing cycles or disconnected content.
There are several ways to accelerate tree reads, such as maintaining child indexes or derived traversal information. Each introduces additional update work. Start with the operations the interface needs before selecting a more elaborate hierarchy representation.
Reddit's Published Comment Storage Stack
Reddit's modernisation account identifies PostgreSQL as the backing datastore for comment data, Memcached as a cache and Redis as an event store involved in generating change-data-capture events. It also describes moving comment endpoints into a Go microservice. These are facts from that published migration, not an exhaustive platform inventory. Reddit's comment backend migration
The roles are more instructive than the product names. Durable storage preserves accepted comments. A cache avoids repeating expensive reads. Change events inform other systems that data has changed.
Our own discussion service could use different technologies while retaining those responsibilities. What matters is knowing which component is authoritative, which can be rebuilt and which carries work that must not be lost.
Loading a Thread Should Be Bounded
A popular discussion may contain more comments than a reader can usefully load at once. Returning the entire tree increases response size, server work and browser rendering cost.
Our teaching interface might fetch a bounded set of top-level comments with a limited number of replies beneath each. Expanding a branch requests more children for that branch.
This is not the same as ordinary flat pagination. A single top-level comment can contain thousands of descendants. Limiting only the number of roots does not necessarily limit the total returned data.
Define limits in terms of total work as well as visible roots. The API can provide continuation information for incomplete branches, allowing the user to expand the discussion without forcing every request to traverse it completely.
Sorting Changes a View, Not the Underlying Conversation
The same thread can be explored by time or by a ranking derived from votes and other signals. The parent relationships remain the conversation's structure, while ordering determines which siblings appear first.
In our example, “newest” can use a timestamp and stable tie-breaker. A score-based view requires a computed ranking value. Neither should overwrite the identity or parent relationship of the comment.
Ranking can change between page requests. A comment may move above or below a pagination boundary after receiving votes. The service needs a policy for duplicates, missing results or a stable snapshot of the ranking.
There is a practical tradeoff between always showing the freshest order and maintaining a perfectly stable browsing session. A product should choose deliberately rather than letting accidental database ordering determine the reader's experience.
Votes Are Operations, While Scores Are Derived State
Suppose a reader changes an upvote to a downvote. Treating that as simply adding another vote would produce the wrong score. The system needs to understand the reader's current vote state and the transition being requested.
A teaching model can store a unique vote relationship between a user and a comment. A derived score then supports fast ranking and display.
The authoritative vote and the displayed aggregate need not use the same read path. Recalculating every score from all individual votes on each page view would be expensive. But a cached aggregate needs a way to converge after retries, updates or missed processing.
This also highlights why counters deserve care. “Increment by one” is not naturally safe to retry after an ambiguous timeout. Recording the intended vote state or a uniquely identified operation is easier to reconcile than blindly repeating increments.
Popular Threads Create Cache Pressure
Most discussions receive modest traffic. A breaking-news post or widely shared question can receive many simultaneous reads of the same comments.
Caching those records or rendered fragments can reduce repeated database work. The cache key must include the dimensions that change the answer, such as sort mode and relevant visibility context.
Our example should avoid building one enormous cache entry for an entire unbounded thread. A small change would invalidate a large result, and rebuilding it could be expensive. Smaller reusable pieces can isolate updates, although assembling them requires careful batching.
Cache misses are part of normal operation, not an exceptional case. Load tests should include a popular thread with a cold cache. Otherwise, the system may appear healthy until a restart removes the memory layer it quietly depended on.
Edits and Moderation Need Clear Visibility Rules
Editing a comment changes its content without necessarily changing its place in the tree. Removing content raises a different question: what happens to replies beneath it?
In a conceptual discussion product, a placeholder can preserve the relationship so replies still have context in the hierarchy. Physically deleting the parent row immediately might instead leave children with broken references.
Different visibility states should be explicit. A user deletion, a moderator removal and a pending-review state can have different serving rules even if all hide the body from an ordinary reader.
Those rules must apply across caches, direct links, search and notifications. Hiding a comment in the main thread is insufficient if an old search snippet still reveals content that should no longer be served.
Change Events Connect the Main Store to Other Views
Reddit's migration article highlights the importance of delivering change events to downstream consumers. That makes sense because a comment update can affect more than its primary record.
For our teaching system, consumers might update a search index, a notification view or an analytical dataset. A durable processing boundary allows those tasks to recover independently of the request that created the comment.
Each event needs enough identity and version information to handle duplicates and out-of-order delivery. An old body update should not overwrite a newer edit, and a delayed create event should not resurrect removed content.
Monitoring should include event age and consumer progress. Successfully writing the comment is one promise; making every dependent view current is another. A single success counter cannot describe both.
Safe Migrations Compare Behaviour, Not Just Row Counts
Reddit's published migration used comparisons between old and new endpoints, with separate stores to validate new write behaviour without corrupting production data. It also encountered differences related to database access and concurrent updates.
The broader lesson is that moving code to another language does not guarantee identical storage behaviour. Serialisation, defaults, query patterns and timing can all change.
In our example, comparing total comment counts would miss a bug that swapped two visibility states or ordered replies differently. Verification should include representative records, edge cases and the observable API response.
Concurrent edits complicate comparisons. If one side reads version five and the other reads version six, a mismatch may reflect timing rather than a defect. Comparing explicit versions helps separate those cases and prevents engineers from chasing misleading differences.
Deep Nesting Requires Defensive Limits
Recursive structures invite recursive code, but an unusually deep conversation can exceed stack limits or cause excessive processing. A malicious or accidental chain should not take down the thread service.
Our design can use iterative traversal where appropriate and impose limits on depth and total nodes processed per request. It should return a useful continuation or bounded result rather than attempting unlimited work.
The browser needs similar care. Even if the server returns valid data, rendering thousands of nested elements can freeze the interface. Incremental expansion makes the result usable as well as cheaper to transport.
These limits should be part of the API contract. An undocumented truncation that silently drops replies is harder to diagnose than a response that clearly indicates more content is available.
Search Has to Preserve Discussion Context
A search result matching one sentence may be misleading without its parent or the post title. A good conceptual search representation therefore stores enough references to reconstruct context without copying the whole thread into every indexed document.
The index should point back to stable comment and post IDs. When a user opens the result, the service can retrieve current content and visibility state.
Edits and removals make freshness important. Search may temporarily lag, but the final serving path should not assume an old index grants permission to show a currently hidden comment.
This is the same separation seen in other large content systems: the index finds candidates, while authoritative records and access rules determine what can actually be presented now.
Building a Smaller Discussion Platform
Start with posts and comments as separate records, explicit parent references and indexes for the reads you need. Use stable ordering with a tie-breaker, bounded responses and an intentional policy for removed parents.
Test two replies arriving simultaneously, an edit during a cached read, a vote retry after a timeout and a parent being removed while someone replies. Also test a discussion far larger and deeper than the typical one.
Add derived scores, caches and search when the workload justifies them. Each additional representation creates a synchronisation and cleanup obligation, so it should solve a measured problem.
For operations, watch the busiest discussions separately from averages. A platform can have healthy overall throughput while one popular thread consistently times out because its data shape or traffic concentration differs from everything else.
The Big Picture
Reddit's comment storage problem combines durable records, a changing tree, multiple ranking views and an ongoing lifecycle of edits and moderation. Its published backend migration shows how caches and change events surround the core database.
The transferable lesson is to preserve stable identity and relationships while treating presentation as a view. A comment remains the same contribution even when its score, text or visible position changes.
Once that distinction is clear, pagination, caching and indexing become easier to reason about. The system can show a useful part of a large discussion without losing the structure that makes the conversation understandable.
