Designing a Scalable Commenting System (Disqus-Like Platform)

JS Enthusiast
Introduction
A commenting platform (in the style of Disqus) is a third-party system that websites can integrate to enable discussion on their content. It allows users to post comments on articles or posts, reply to others in threaded conversations, and vote on or react to comments. Such a system must handle potentially millions of users and comments, which demands a robust, scalable, and efficient architecture. In this guide, we will design a Disqus-like commenting system from the ground up, assuming the reader is new to system design. We’ll cover requirements, high-level architecture (with diagrams), component breakdowns, API design, data modeling, scaling strategies, and key considerations like moderation, security, and trade-offs. By the end, you should understand how to design a production-ready commenting platform suitable for an interview discussion.
Requirements
Functional Requirements
Threaded Comments (Nested Replies): Users can post comments on a content item (e.g. a blog post) and reply to other comments, forming nested threads. The system should display replies indented under their parent comment to reflect the conversation structure. This requires handling hierarchical data relationships (each comment may have a parent comment). There may be a limit on reply depth to avoid infinitely deep threads (for example, Disqus often limits nesting to a few levels for usability).
User Authentication & Profiles: Users can create accounts, log in/out, and have basic profiles. An authenticated user model is needed to associate comments with user identities. Profiles might include a display name, avatar, and reputation score. (Anonymous commenting could be allowed as a feature, but we’ll assume registered users for moderation and tracking.)
Comment Posting & Display: Users can submit new comments which are stored in a database and then displayed under the relevant article or content in chronological or specified order. The system should support displaying a list of comments for a given content item (often paginated, with options to sort by time or popularity). Each comment includes information like author name, timestamp, and content.
Voting and Reactions: Users can upvote or downvote comments (and potentially react with likes or other emojis). The system must track votes and update each comment’s score or reaction counts in real-time to reflect community feedback. Each user’s vote should be counted once per comment (preventing duplicate voting), and the UI should highlight if the user already voted.
Moderation Tools: Administrators or moderators need the ability to manage the discussion. This includes deleting or hiding inappropriate comments, flagging or banning abusive users, and possibly filtering certain keywords. Moderation actions should propagate to all users (e.g. a deleted comment should disappear or be marked removed). A spam filter should automatically detect and filter out obvious spam before it hits the page.
Real-Time Updates: New comments and votes should appear to users without requiring a page reload. When someone posts a comment or a reply, other users viewing the discussion should see it insert into the thread in real-time. This improves engagement by making the conversation feel live. (For example, Disqus introduced a realtime system to push new comments to browsers, significantly increasing user engagement.) The system should handle broadcasting new comment events to many concurrently connected clients.
Integrability: The commenting system is typically embedded into third-party websites (as a widget or via JavaScript). Thus, the design should expose clear APIs or embed scripts to integrate with different frontends. While our focus is on system design, it’s worth noting that real-world services like Disqus provide a JavaScript embed that loads an iframe or script to display the comments interface on any site.
Non-Functional Requirements
Scalability: The system must scale to millions of users and comments. We should design for high throughput: e.g., imagine 100 million daily active users posting 1 billion comments per day. The design should accommodate heavy read traffic (fetching comments) and write traffic (posting/voting) by scaling horizontally. Both storage and application layers should handle growth (potentially hundreds of millions of total comments stored over time).
High Availability: The service should be reliable and available 24/7. Techniques like database replication and server redundancy are needed to avoid downtime. Even under failures (server crashes, network issues), the system should keep functioning (perhaps in a degraded mode) and gracefully recover. For an interview scenario, assume we aim for 99.9% or higher uptime.
Low Latency: Posting a comment or loading existing comments should feel instantaneous. The system should provide fast API responses (in the order of a few hundred milliseconds or less) for a smooth user experience. Real-time updates should propagate with minimal delay (Disqus’s real-time goal was under 200ms end-to-end latency). To achieve this, we will use caching and efficient data access patterns.
Consistency: In a distributed system, perfect consistency can be hard to achieve at scale. It’s acceptable to allow eventual consistency for some aspects — for example, a new comment might immediately show for the poster but take a second or two to appear for others. The system will converge so that all users see the same set of comments after a short delay. We will aim for read-your-writes consistency for individual users (you see your comment right after posting) and eventual consistency globally, which is common in large comment systems (e.g., YouTube comments often don’t instantly appear to others).
Security: The platform must be secure against common web vulnerabilities. This includes preventing Cross-Site Scripting (XSS) since users are submitting rich text that will be displayed to others, as well as guarding against bots and spam attacks. We need to sanitize or filter user input to avoid malicious scripts, and implement measures (like rate limiting, CAPTCHA, email verification) to thwart spam bots. We’ll discuss these in detail in later sections.
Efficiency: Given the scale, the system should use resources efficiently. Avoid redundant processing – for example, format or compute a new comment notification once and distribute it to all subscribers, rather than doing duplicate work per subscriber. Use caching to reduce expensive operations, and design the data model and indexes for optimal query performance. Efficiency ensures we can handle high load with reasonable infrastructure cost.
With the requirements clarified, let’s proceed to the high-level design of the system.
High-Level Architecture

High-level architecture of the Disqus-like commenting system (clients, services, and data stores).
At a high level, our commenting platform follows a multi-tier architecture with decoupled components for scalability and maintainability. The major pieces include:
Clients (Web/Mobile): User devices running the commenting interface (could be a web widget or a mobile app). Clients interact with the system via HTTP(S) APIs. They fetch comment threads, submit new comments, and open a real-time channel to receive live updates.
Load Balancer: An entry point that distributes incoming requests across multiple application servers to handle large numbers of users. The load balancer (e.g., Nginx, AWS ELB) ensures no single server is overwhelmed and helps in scaling stateless services horizontally.
Application Servers (Backend API): A cluster of stateless servers that host the core application logic (comment APIs, voting logic, etc.). These servers, which can be built with a web framework (e.g., Django, Node.js, etc.), handle requests like fetching a page of comments or posting a new comment. Being stateless, they can be duplicated easily behind the load balancer to scale out as traffic grows. They read from and write to the database, and also interface with cache and message queues as needed. (We might implement the backend as a monolithic service initially. In a more advanced design, this could be broken into microservices – e.g., a Comment Service, User Service, etc. – but splitting is optional at this stage.)
Database (Persistent Storage): A database cluster for storing persistent data: comments, user profiles, etc. This could be a relational database (like PostgreSQL or MySQL) or a NoSQL store, or a combination of both. The database must handle the high write volume of new comments and the large total data size. We will likely need replication (for read scaling and backup) and possibly sharding/partitioning to handle billions of records over time. For example, Disqus in production has used PostgreSQL for core transactions and Cassandra (NoSQL) for scalable storage, along with caches.
Cache Layer: A fast in-memory cache (such as Redis or Memcached) to store frequently accessed data and reduce database load. For instance, the cache might store the latest N comments of popular threads, or caches of rendered comment HTML, so that repeated fetches can be served quickly. By caching read-heavy endpoints, we can dramatically reduce latency and database load. We’ll ensure to invalidate or update cache entries when new comments arrive (to avoid stale content).
Real-Time Messaging (Push Service): To support live updates, we include a real-time push component. This could be implemented via WebSockets or Server-Sent Events. In our design, when a new comment is posted, the application server publishes this event to a message queue or pub-sub system (e.g., using a Redis pub/sub channel, or a Kafka topic). A Push Service (one or multiple dedicated servers) subscribes to these events and immediately broadcasts the new comment to all connected clients viewing that thread. The push servers maintain persistent connections (WebSocket) with clients to push data. This decoupling (via a queue) ensures that broadcasting to many users (fan-out) doesn’t slow down the core posting transaction – the write to the database and the enqueuing are quick, and the heavy lifting of fan-out happens asynchronously. (Disqus followed a similar approach: their “realtimes” pipeline queued new comments and used Nginx PushStream module to deliver to clients, allowing 165k messages/sec with ~0.2s latency.) We’ll discuss the real-time component more shortly.
Authentication Service: Although user authentication can be part of the main app, it’s often handled by a dedicated service or component (especially if supporting OAuth or single sign-on). Users’ login, registration, password management could be a separate module. In our architecture diagram, we have an Auth service (which could simply be the part of the backend that deals with user login). It stores and verifies user credentials and issues session tokens. The comment system’s backend would call the Auth service (or module) to verify tokens on each request to ensure the user is authenticated.
Moderation & Admin Tools: Administrators (site owners or community moderators) might use a separate admin interface (or the same website with elevated permissions) to review and moderate comments. For example, a moderator might log into a moderation dashboard (which calls admin APIs on the backend) to fetch flagged comments, delete or ban users, etc. These admin actions go through the same backend but require special authorization (admin role) and may trigger additional workflows (e.g. notifying other mods, or logging the action).
All these components interact to fulfill the user-facing use cases. In summary, a user’s browser will fetch an initial page of comments via the backend API (possibly served through a CDN and cache for speed). When the user posts a comment, the app server writes it to the database, then publishes an event to the real-time service which pushes the new comment to other users immediately. Meanwhile, caches are updated or invalidated. Moderators can remove content, which propagates similarly (marking a comment as deleted in DB and pushing an update or simply not showing it on next fetch).
This high-level design ensures that we can scale each part independently: we can add more app servers to handle more traffic, add read replicas or shard the database for more data, and scale the push service to handle more concurrent websocket connections. Next, we’ll break down each component in more detail.
Component Breakdown
Client-Side (Frontend)
On the client side, the commenting system is typically delivered as an embedded widget on web pages (or as part of a mobile app UI). For a web integration, one common approach is to include a snippet of JavaScript provided by the comment platform. This JS either renders an <iframe> or dynamically builds the comment UI and communicates with the backend via APIs. In our design:
The frontend UI displays the list of comments for a given article, handles user input (new comments, votes), and updates the view when new data arrives. It could be built with any modern framework (React, Vue, etc.), but it must be lightweight if it’s an embed on external sites.
The client calls RESTful API endpoints (discussed later) to load comments and submit actions. For example, when the page loads, it might call
GET /articles/{id}/commentsto fetch the first page of comments. When a user posts a comment, the client might callPOST /commentwith the content and thread ID.For real-time updates, the client would open a WebSocket connection or subscribe to an EventSource stream after loading the initial comments. For instance, after loading comments for thread X, the client opens a WebSocket to the push service (perhaps a URL like
wss://comments.example.com/threads/Xor it subscribes with thread ID). The server will then push any new comment event to that socket. The client JavaScript will listen for these events and update the DOM (inserting the new comment into the thread without a refresh).The client is also responsible for some local state: tracking which comments are expanded/collapsed, which comment the user is replying to, etc. It should also reflect user actions immediately (optimistic UI) – e.g., show the new comment in the list as soon as the user posts (perhaps marked as “pending” or with a temporary ID) so the UI feels responsive, even if the server processing takes a moment.
Because this system may be embedded on many different websites, the client component should be asynchronous and sandboxed (so it doesn’t conflict with host page styles/scripts). Disqus, for example, provides a <script> tag that loads their client code asynchronously and creates a <div id="disqus_thread"></div> where the comments are injected. For our design, we assume we provide a similar embed script for integration. We might also offer a direct REST API for mobile apps or custom integration.
In summary, the frontend’s role is to present comments attractively, capture user interactions, and maintain a live connection for updates. The heavy lifting (data storage, auth, filtering) happens on the back end, but a well-designed client improves user experience.
Backend Application Servers
The backend consists of application servers that implement the business logic and APIs for the commenting system. Key points about the backend design:
It will expose RESTful endpoints for all core functionality: posting a comment, fetching comments, voting, etc. (We’ll detail the API endpoints in the next section.) These servers validate requests (check auth tokens, rate limits, input format), interact with the database and cache, and produce responses in JSON (or GraphQL or gRPC, but JSON/REST is simplest).
The servers are stateless – meaning each request contains all info (like user session token, parameters) needed to process it, and no session data is kept solely in one server’s memory. Stateless design allows horizontal scaling: if we need to handle more load, we just add more server instances behind the load balancer. Any server can handle any request (they are interchangeable).
We may implement the backend initially as a single service (monolith) to keep it simple: the same application handles comments, votes, user profiles, etc. However, logically we have components:
Comment Service (handles creating and retrieving comments, managing threads and replies),
User (Auth) Service (handles login, signup, and user info lookup),
Notification/Realtime Service (handles pushing updates, though could be separate process/servers),
Moderation Service (handles admin actions like deletions, possibly combined with the Comment service).
In a more advanced system, these could be separate microservices communicating via internal APIs or message queues. For example, when a comment is created, the Comment Service could emit an event that the Realtime Service picks up to notify listeners. Disqus’s architecture evolved in this direction – they have a pipeline of services for real-time updates rather than one giant app. For our design (and in many interview scenarios), it’s acceptable to keep it as one service and just conceptually separate concerns.
Data access: The backend servers will read/write to the primary database. To avoid hitting the DB for every read, they first check the cache (for example, try to get comments for thread X from Redis; if cache miss, then query DB and then populate cache). For writes, they’ll likely write to the DB and also invalidate or update relevant cache keys.
Performance: Because each page view might load dozens of comments, these APIs should be optimized (e.g., using efficient DB queries with proper indexes, limiting payload sizes, and compressing responses if large). Pagination is important to avoid sending thousands of comments at once. Also, the servers should offload intensive tasks. For instance, sending notifications or running spam classification might be done asynchronously (e.g., via background jobs or message queue) so the API response isn’t slowed down.
Concurrent updates: The backend should handle concurrent actions gracefully. If two users try to reply at the same time, both should succeed with distinct comment IDs. If two users upvote at the same time, both votes should count (and perhaps the second request might get a slightly updated vote count after the first). We’ll rely on atomic DB operations (or transactions) to maintain integrity (e.g., incrementing a vote count or inserting a comment). In a distributed setting, some eventual consistency might occur (one user might see 10 votes vs 11 for a moment), but that’s acceptable as long as it settles quickly.
Tech stack considerations: We can choose any web stack, but note that Disqus originally built their backend in Python (Django). Python made development fast, though for real-time aspects they later incorporated technologies like gevent (for concurrency) and even moved some components to Go for performance. In our design, choice of language/framework is secondary to the architecture, but one trade-off to mention: high-level frameworks (like Django) speed development but may introduce overhead; Disqus prioritized developer productivity and used caching/database optimizations to achieve scale rather than writing everything in low-level code. This is a common trade-off: use a proven framework vs. build custom solutions – we should be aware of it but we can assume our backend is built on a robust framework and then optimized as needed.
Overall, the backend application layer is the brains of the system coordinating data from storage, applying business rules (like “a user can only vote once”), and ensuring the system’s invariants (like consistency of comment threads) are maintained.
Database and Storage
For storing the comments and related data, we need a storage solution that can handle a very large volume of data and high query rates. Let’s break down the data we need to store and how to model it, then discuss scaling the storage:
Data Model: We have a few main entities to store:
Comment: Each comment record contains the text content, the author (user reference), timestamp, and references to where it is in the thread hierarchy (which article/thread it belongs to, and which comment it is replying to, if any). It may also store counts (like upvote_count, downvote_count) or we can compute those from a Votes table.
Thread/Discussion: This represents the content item under which comments are posted (for example, a blog post or video). It could be as simple as an ID tying to the article. (In Disqus, a “thread” is identified by a unique ID for each page/discussion, and a “forum” represents the site. Here we can assume one site, or incorporate a site ID if designing multi-tenant for many sites.)
User: Stores user profile data (username, email, hashed password, join date, avatar URL, reputation score, etc.).
Vote/Reaction: If we allow upvote/downvote, we need to keep track of which user voted on which comment (to prevent multiple votes and possibly to display “you liked this”). This can be a separate table linking User and Comment with a value (up or down). Alternatively, one could store vote counts on the comment and a record of users who voted (but for efficiency, a separate table or in-memory set is typical).
Moderation flags: We might also have tables for flags or moderation actions (e.g., a “flag” entity if users can flag comments, or a record of deleted comments, etc.), but we can omit details for brevity.
We will discuss schema shortly (with an example diagram), but essentially a relational model fits well (Comments, Users, etc. with foreign keys). A sample ER diagram is shown below.
Entity-relationship (ER) diagram of the core data model for comments, users, threads, and votes. Each Comment belongs to a Thread (content item) and is authored by a User. A comment may also reference a parent comment (if it’s a reply, parent_id points to another comment). Votes by users on comments are stored separately, linking a User and Comment (with a vote type), which allows tallying upvotes/downvotes and enforcing one vote per user.

In terms of storage technology:
A relational database (SQL) like PostgreSQL or MySQL is a straightforward choice: it offers strong consistency for transactions (important for things like counting votes accurately) and supports relationships (which we have: comments to users, replies, etc.). We can use indexes on
thread_id(to quickly fetch all comments for a given thread, sorted by timestamp) and perhaps onparent_id(to find replies for a comment). SQL also allows flexibility in querying (e.g., to fetch all comments by a user, or search within comments if needed).However, a single SQL DB instance will eventually become a bottleneck if the data size or QPS (queries per second) grows extremely large. We anticipate possibly billions of comments stored over years. For example, 1 billion comments a day at ~100 bytes each would accumulate ~93 GB/day of new data (~3 TB/month), reaching hundreds of TB over years. Managing that in one SQL node is impractical. So we must plan for partitioning (sharding) the data or using a distributed database.
Sharding in SQL: We could shard the comments table by Thread ID or by some hash of comment ID. For instance, comments for thread A go to shard 1, for thread B go to shard 2, etc. This ensures write load is split and each shard handles a subset of data. The application or a middleware would route queries to the correct shard. The downside is added complexity in querying across shards (but in our use-case, most queries are partitionable by thread).
NoSQL approach: Another approach is to use a horizontally scalable NoSQL store (like Apache Cassandra, MongoDB, or DynamoDB). These systems automatically distribute data across nodes. For example, Cassandra can use
thread_idas a partition key, storing all comments of a thread in the same partition (which is good for range querying by time within that thread). NoSQL can handle huge volumes and high write throughput easily, at the cost of weaker consistency (often eventual consistency) and limited cross-partition queries. Disqus in fact incorporated Cassandra to handle certain data at scale alongside PostgreSQL, leveraging Cassandra’s ability to scale writes and storage horizontally. We could design similarly: perhaps use SQL for critical data (recent comments, or metadata) and use a NoSQL cluster to archive or handle older comments or high-volume writes.
Choosing SQL vs NoSQL: As a design trade-off, one might start with a single relational DB (for simplicity and consistency). As traffic grows, first scale vertically (add CPU/RAM, use read replicas for heavy reads). When that’s exhausted, introduce sharding or move to a NoSQL solution for the comment data. Many real systems use a combination: e.g., store the latest or most interactive data in SQL and offload older or less-relational data to NoSQL. For our design, we can assume an initial relational DB with careful indexing and then mention scaling strategies (next section covers scaling).
Additional storage considerations:
Indexes & Queries: We will primarily query comments by thread (and sort by time or score), so an index on
(thread_id, timestamp)is important. If we support sorting by score (upvotes), we might maintain a separate index or even a different column for “hotness”. If full-text search in comments is needed (not in requirements here), we might integrate a search engine (Elasticsearch or similar) to allow keyword search.Data retention: Comments are typically kept indefinitely, but some rarely accessed old threads could be archived to cheaper storage if needed. This could be a long-term consideration (like moving very old comments to a data warehouse or static storage for backup).
CDN/Blob storage for media: If the commenting system allows images or media in comments (not explicitly asked here), we’d store those in a blob store (like S3) and serve via CDN, rather than in the database. For now, assume comments are text only (plus maybe links).
In summary, our data storage must efficiently handle relationships (user-comment, comment-thread, comment-comment for replies) and scale to large volumes. A normalized relational schema will work and can be scaled out via replication and sharding when needed. Caches will complement the database by storing common query results in memory for fast access.
Caching Layer
To meet the low-latency requirements and reduce load on the database, we will use a caching layer. A cache is a high-speed storage (usually in-memory) that stores the results of expensive or frequent queries so subsequent requests can be served faster. Here’s how caching comes into play in our comment system:
Page cache / HTML cache: If the comment widget is loaded on extremely high-traffic pages, one approach is to cache the rendered HTML of the comment section for anonymous users or cache the JSON response of the “get comments” API. For example, if an article’s comments don’t change often, we could cache the first page of comments for a short time. However, because we want real-time updates, heavy caching of comment data might conflict with freshness. We’ll likely use a short TTL (time-to-live) for comment list caches, or use an approach where posting a new comment explicitly invalidates the cache for that thread.
Object cache: More commonly, we cache individual objects or small data pieces. Examples: caching a comment count for each article (so the page can quickly show “💬 150 Comments” without counting each time), caching user profile info (user ID -> username & avatar) to avoid a DB join, or caching the set of top N comments (if we have a concept of top comments by score).
Implementation: We can use Redis or Memcached as our cache store. Both are in-memory, Redis also offers pub/sub and persistence options which could be handy (Disqus used Redis both for caching and for their realtime queue via a library called Thoonk). We’ll assume a Redis cluster. The application server, on a request, will first check Redis for a cached result:
E.g., a request for
GET /article/123/comments?page=0might translate to a Redis keycomments:article123:page0. If present, return the cached JSON immediately. If not, query the DB, get results, then store in Redis with an expiration (maybe 30 seconds or a minute, or even just 5 seconds if we want near-real-time).When a new comment is posted to article 123, we can either invalidate the cache key for that thread’s comments or proactively update it (e.g., push the new comment into the cached list if it’s still cached). Simpler is to invalidate so that next reader triggers a DB refresh. This ensures users eventually see the new comment even if they aren’t on realtime.
Cache for read scaling: At scale, reads (people viewing comments) far outnumber writes. The requirement assumed a 100:1 read:write ratio. If an article is very popular, many users will be fetching the same comments. Caching those results means the database may only be hit once, and then 99 subsequent requests are served from cache. This dramatically reduces DB load and improves response times.
Consistency and staleness: We must handle that cache can serve stale data. As mentioned, on any write (new comment, edit, delete, vote), relevant caches should be invalidated immediately to avoid showing outdated info. Additionally, we might set short TTLs so even if an invalidate is missed, the data doesn’t stay stale for long. Using an event-driven approach is ideal: e.g., when a comment is posted, the server not only enqueues a realtime event but also sends an invalidation message or uses Redis pub/sub to notify other app servers to drop that cache entry.
User-specific caching: Some data is user-specific (e.g., whether I have upvoted a comment). We usually don’t cache full responses that include user-specific state unless keyed by user. It might be too fine-grained, so likely we avoid caching personalized data and focus on shared data.
CDN Caching: If we provide our widget JavaScript or CSS, we will serve those via a CDN (Content Delivery Network) so that they load quickly for users globally. The CDN can also cache API responses if configured, but since our content is dynamic and personalized (and small), we may not put the API behind a CDN caching layer, except maybe for anonymous GETs. The primary CDN use is static assets (script files, images like user avatars possibly, etc.).
In essence, caching is a critical piece for performance. It acts as a short-term memory of the system. Proper use of caching can make our comment fetch API respond in ~10ms (from memory) instead of, say, 100ms from database. Disqus’s team noted that often slow database queries were the main cause of slowness, and caching at the application level was a key strategy to alleviate that. We just have to implement cache invalidation carefully to balance speed with data freshness – a classic design consideration.
Real-Time Updates Component
One of the standout features of our system is real-time comment updates: new comments appear live without a page reload. Achieving this requires a publish-subscribe model and a way to push data to clients. Here’s how we design it:
WebSockets (or Server-Sent Events): The client establishes a persistent connection to the server after initial page load. WebSocket is a popular choice as it allows bi-directional communication. Alternatively, Server-Sent Events (EventSource) could be used for a simpler one-way stream (from server to client) which is often sufficient for comments. Disqus in 2013 actually used a technique with Nginx’s Push Stream Module (essentially long-lived HTTP connections) to push comments. We’ll assume WebSockets for flexibility.
Subscription model: The client likely needs to subscribe to a particular comment thread channel. For instance, if you are on article 123, your WebSocket connection could subscribe to a topic like “comments_article_123”. The backend (push service) will then only send you events for that thread (and perhaps global announcements if needed).
Message Queue / Broker: When a user posts a new comment via the REST API, the application server will save it to DB and then publish an event to a message broker. This could be as simple as a Redis publish (
PUBLISH comments_article_123 "new comment data") or a more robust system like RabbitMQ or Kafka. The message contains the new comment’s data (id, content, author, timestamp, etc.), or an identifier that allows the push service to fetch the data.Push Service: We have dedicated push servers (could be the same machines as app servers, but often separated for specialization) that maintain the WebSocket connections with clients. These servers subscribe to the message broker for relevant channels. For example, a push server might subscribe to
comments_article_123channel on Redis. When a new comment message arrives, the push server immediately forwards it to all connected clients subscribed to that thread (essentially acting as a real-time distributor). This architecture decouples the expensive operation of pushing to many users from the core app logic. It’s needed because a single comment might need to be sent to thousands of clients – a high fan-out scenario. If the app server tried to do that synchronously, it would slow down the posting request. By using a pipeline (App -> Queue -> Push servers -> Clients), we handle fan-out efficiently.Scalability of realtime: Each push server can handle a certain number of open connections (depending on implementation and hardware, possibly tens of thousands per server). We can run multiple push servers and use a load balancer or IP hash to distribute client connections among them. The message broker (like Redis or Kafka) can deliver each message to all push servers subscribed. For extremely large scale (millions of concurrent sockets), a more partitioned approach might be needed (e.g., sharding channels by server), but the idea remains: scale horizontally by adding push servers. Disqus’s real-time system (codenamed “realertime”) was tested with 1.5 million concurrent users and 165k messages per second using a pipeline of Redis and Nginx push modules. They achieved this by making sure the formatting of messages was done once and by using event-driven, non-blocking servers to handle many connections efficiently.
Fallbacks: Not all clients may support WebSockets (older browsers) or they might be behind firewalls. It’s common to have fallbacks like long-polling: the client opens an HTTP request that the server holds open until an event is available. This is less efficient but ensures broad compatibility. Implementation detail: many real-time libraries (like Socket.IO) handle this gracefully by falling back to HTTP long-polling if WebSocket fails.
Ordering and reliability: We should ensure that messages (new comments) appear in the correct order. Typically, if they are all published through one channel per thread, they will generally be ordered, but network hiccups could reorder events. We can include timestamps and have the client sort or discard out-of-order messages if needed. Also, if a user is offline for a minute and then reconnects, they might miss some comments via push. The client should probably do a refresh (calling the GET comments API) when reconnecting to sync any missed data. This way, real-time is eventually consistent with the source of truth (the database).
Filtering: The push service might also handle filtering out events. For example, if a comment is moderated (deleted) right after posting, perhaps the push service gets both “new comment” and “delete comment” events in succession. It should handle both (maybe broadcasting a removal message). If using a robust pub/sub, this logic can be managed either in the application or the push layer.
In conclusion, the real-time component greatly enhances user experience by keeping everyone’s view up-to-date. It introduces complexity (persistent connections and asynchronous event handling) but uses well-known patterns (publish-subscribe and socket servers). This is often a discussion point in interviews – you can mention simpler approaches first (polling) and then explain how long polling or WebSockets improve it for scale. In fact, an early naive approach is periodic polling (e.g., client asks every 5 seconds for new comments). Polling is easy but doesn’t scale well for huge user bases or deliver true instant updates (Disqus’s original implementation was polling memcache keys and it “did not scale at all,” only 10% users could use it due to load). That justifies why we need a push system for a robust solution.
Moderation and Spam Filtering
Moderation is crucial in any public commenting platform to maintain quality of discussions and prevent abuse. Our system should include both manual moderation tools and automated spam filtering:
Moderation Interface & Roles: We will have an admin role (or site owner) who can perform actions such as:
Delete or hide a comment (this could either hard-delete it from the database or mark it as removed so it’s not shown). The API for this might be a
DELETE /admin/comment/{id}endpoint, which our design includes.Ban a user or IP (prevent them from posting further).
Edit a comment (less common, but possibly to remove profanity or personal info while retaining the comment).
View a queue of flagged comments – if we allow users to flag others’ posts, those flags can put comments into a “Pending” state until reviewed.
Set filters (e.g., disallow certain words, or auto-hold comments with links).
In the backend, these admin actions are protected (require admin auth). When a moderator deletes a comment, we update its status in the DB (and possibly keep the content for audit). The system should propagate this removal to users: e.g., if a comment is currently visible to users, once deleted it should disappear (the next GET API call won’t include it, and a real-time “delete” event could be sent to remove it from open UIs).
Automated Spam Detection: Given the scale, manual moderation can’t catch everything. We will integrate automated spam filtering:
Third-party spam filter: A well-known service is Akismet, which is used by many commenting systems (including Disqus) to detect spam content. We can send new comments through Akismet’s API which uses heuristics and machine learning trained on millions of spam vs ham comments. If Akismet flags a comment as spam, we can either reject it outright or put it in a moderation queue (not publicly visible until approved).
Machine Learning & Rules: We can also develop our own filters. For example, any comment with too many links or certain blacklisted keywords can be auto-marked as spam. Disqus combines Akismet with network-wide signals like user reputation, previous spam, votes, etc., to judge a comment’s likelihood of being spam. In our design, we could maintain a “reputation score” for users (which increases with upvotes, decreases if their comments are flagged) and use that as a factor. New users or users with low reputation get more stringent filtering.
Rate limiting (discussed separately later) is also a spam mitigation: bots that attempt to post 100 comments in a minute can be stopped by the system automatically.
Email or phone verification: Ensure accounts are tied to a valid email (Disqus requires email verification, which “stops many fake accounts”). This raises the bar for spammers, especially if we also use captchas for suspicious activity.
Moderation queue: If a comment is identified as likely spam, instead of rejecting, it might be placed in a “pending moderation” state. Moderators can review these and approve or reject. This helps avoid false positives (legitimate comments caught by the filter). Disqus even allows commenters who were mistakenly flagged to request review.
Community moderation: In some systems, regular users can assist by flagging inappropriate content. Our design could include a flag endpoint (e.g.,
POST /comment/{id}/flag). A certain number of flags could auto-hide a comment until review, or at least bring it to moderators’ attention with high priority.Spam bot mitigation: Aside from content spam, bots might try to flood the system with comments to overwhelm it or advertise. To combat this:
We enforce login (bots then have to also create accounts, which we can track).
Implement CAPTCHA challenges for suspicious behavior (e.g., unverified users posting too frequently).
IP throttling: If dozens of accounts originate from the same IP or network in a short time, flag or block that IP temporarily.
Use bot detection techniques (fingerprinting, checking user-agent, etc.) to identify non-human patterns.
Keep an eye on metrics like comments per minute per user/IP – our rate limiter (below) will help restrict that.
Audit and Logs: All moderation actions can be logged. If a user complains their comment was unfairly removed, logs help track what happened (e.g., “Deleted by admin X for spam at 12:00” or “Removed by spam filter”). This is more an operational detail but important in real systems.
Disqus’s approach to moderation includes advanced tools like a “toxicity filter” powered by machine learning to flag hateful or toxic language, and user reputation systems. For our design, it’s sufficient to mention integrating a spam detection service and providing admin capabilities.
In summary, moderation and spam filtering ensure the health of the community. They introduce additional complexity (extra checks on submission, admin workflows), but are essential at scale: Disqus processes ~1.8 million comments daily and catches about 15k spam comments (0.8%) automatically. We aim for a system that can similarly nip spam in the bud and empower admins to manage content effectively.
Authentication and User Profiles
Since our commenting platform is user-centric (each comment is tied to a user account), we need a robust authentication system and user profile management:
User Registration & Login: Users should be able to register (sign up with email or via third-party OAuth like Google/Facebook if we allow) and then log in to create comments. Authentication can use sessions (cookies) or token-based (e.g., JWT) for API calls. Given this is often a standard component, we might rely on existing authentication services or libraries. In system design, one might mention using an OAuth 2.0 service or a identity provider. However, to keep scope focused, we’ll assume a simple in-house auth: the user provides email/password, we hash the password and store it, and maintain a session token.
User Profiles: Each user has a profile with username, display name, avatar, join date, etc. This data is stored in the database. When showing comments, we often display the commenter’s name and avatar – that means our comment fetch API needs to join or fetch user info. We can optimize by caching common user profile data (as discussed in caching). For example, store a mapping of user_id to username and avatar URL in Redis for quick access.
Authentication Service Design: If this were a large-scale multi-service system, an Auth service could issue tokens and validate them. The comment service would then trust those tokens (e.g., JWT that includes user ID and is signed). In our simpler approach, the comment server can handle auth by checking a session cookie or token in each request (possibly by querying a user session table or in-memory store).
Scaling auth: User accounts can be millions in number. A single database for users is usually okay (user records are much lighter than comments). Read traffic for user data is also not as heavy (you typically fetch a user on login, or fetch small bits of profile info when showing comments – and that can be cached). So the user table could reside in a SQL DB (possibly the same as comment DB or separate). If separate, we’d have a User Service that the Comment Service queries (or the comment DB has foreign keys to a user DB).
Security: We must store passwords securely (hashed with salt, e.g., bcrypt), protect user data, and have measures against account hijacking (rate limit login attempts, possibly 2FA for admins, etc.). Also, since this is a third-party system, some sites might prefer to use Single Sign-On (SSO) – e.g., if a website already has its user accounts, they might want to pass the identity to the comment system. Disqus supports that for paying clients. That would add complexity (trusting external JWTs or APIs). We’ll note it but not design it fully here.
User Reputation: A nice extension is to track user activity – number of comments, average score, etc. This can be used to display badges or to feed into spam detection (as mentioned, trusted users’ comments could bypass some filters). We might maintain counters for each user: total comments, total upvotes received, etc. These can be updated in the database (or via a separate analytics pipeline if heavy).
In summary, authentication and user management underlie everything – we assume it’s handled either by our system or an external module, but it must scale to millions of users as well. Since the question focus is on commenting, we won’t dive deeper except to say it should be secure and scalable, and the design can leverage standard practices (like token auth, caching profile data, etc.).
API Design with Endpoints and Payloads
We will design a set of RESTful API endpoints to allow clients (or third-party integrations) to interact with the commenting system. These endpoints cover commenting functionality (post, reply, fetch) as well as moderation and voting. Below is a list of example endpoints with their purpose and example request/response formats:
POST /comment– Submit a new comment (top-level comment on a thread).
Request: The client provides the content of the comment in the request body (and which thread or article it’s for, likely as a parameter or part of the body). For instance:{ "threadId": "abc123", // ID of the content item or thread "content": "This is a great article! Thanks for writing it." }The user’s identity would be derived from their auth token/session (so not included in the JSON).
Response: The API returns a status and the created comment object (or an error message). For example:{ "status": "success", "message": "", "comment": { "id": "cmt7890", "content": "This is a great article! Thanks for writing it.", "posterId": "user456", "posterName": "Alice", "postTime": "2025-05-16T02:30:00Z" } }This indicates the comment was saved (with a generated ID
cmt7890), and returns the comment data that can be immediately displayed. If there was an error (e.g., not authenticated or content too long),statusmight be "failed" andmessagewould explain why.GET /thread/{thread_id}/comments?cursor={last}&size={N}– Retrieve comments for a given thread (paginated).
Description: This returns a list of comments for the specified thread (e.g., all comments under a particular article or post). We support pagination using a cursor (could be a timestamp or comment ID of the last item on the previous page) and a page size. The comments are typically sorted by time (newest first or oldest first, or possibly by score if we implement ranking – but let’s assume chronological).
Request params:thread_id(in URL path): Which thread’s comments to fetch. This identifies the discussion.cursor(query param, optional): If this call is to get subsequent pages,cursoris the timestamp or ID of the last comment from the previous page. The API will return comments after (or before) that cursor depending on sort order. If not provided, it returns the first page.size(query param, optional): Number of comments to return. Default 20, max maybe 50 for example.
Response: On success, returns the list of comments and some metadata:
{
"status": "success",
"total": 12345, // total number of comments in this thread
"data": [
{
"id": "cmt7890",
"content": "This is a great article! Thanks for writing it.",
"posterId": "user456",
"posterName": "Alice",
"postTime": "2025-05-16T02:30:00Z",
"upvoteCount": 5,
"downvoteCount": 0,
"repliedCommentId": null // null for top-level comment
},
{
"id": "cmt7891",
"content": "I agree with Alice.",
"posterId": "user789",
"posterName": "Bob",
"postTime": "2025-05-16T02:31:00Z",
"upvoteCount": 2,
"downvoteCount": 0,
"repliedCommentId": "cmt7890" // this comment is a reply to comment cmt7890
},
...
]
}
Here, total is the total number of comments in the thread (useful for showing “12345 comments” or for UI logic). The data array contains comment objects. Each comment includes repliedCommentId to indicate if it’s a reply. In this example, the second comment is a reply to the first (hence it has repliedCommentId = cmt7890). The client can use this to build the threaded UI (nest Bob’s comment under Alice’s). If the array is empty, that means no comments yet. (We can also design this endpoint to return comments in a tree structure, but that complicates pagination. It’s simpler to return a flat list sorted by time and let the client arrange replies. For initial version, chronological flat list is fine.)
POST /comment/{comment_id}/reply– Submit a reply to a specific comment.
Request: The{comment_id}in the path is the ID of the comment being replied to (the parent). The body will contain the reply content, similar to posting a comment:{ "content": "Thanks for the clarification!" }The server knows which thread the parent comment belongs to, so it can associate the reply with the same thread.
Response: This would mirror thePOST /commentresponse, returning the newly created reply comment object:{ "status": "success", "message": "", "comment": { "id": "cmt7900", "content": "Thanks for the clarification!", "posterId": "user123", "posterName": "Charlie", "postTime": "2025-05-16T03:00:00Z", "repliedCommentId": "cmt7890" } }This indicates comment cmt7900 is a reply under comment cmt7890. The client could insert this under that comment in the UI.
PUT /comment/{comment_id}/upvote– Upvote a comment. (Similarly,PUT /comment/{comment_id}/downvoteto downvote.)
Request: The user triggers an upvote on a given comment ID. This could be a PUT or POST; using PUT is semantically fine (we are “updating” the vote status). No body is needed besides the URL, since the identity of the voter is the authenticated user. (If we wanted to allow unregistered users to like, that’s another scenario, but we’ll assume only logged-in users can vote.)
Response: A simple acknowledgment of success or failure. For example:{ "status": "success", "message": "" }On success, the server will have recorded the vote (in the Vote table or incremented a counter). The updated vote counts could either be fetched via the GET comments call or we might design the endpoint to return the new count. To keep it simple, we just acknowledge. The client could optimistically update the UI (e.g., increment the count it has by 1) or call GET to refresh the data.
DELETE /admin/comment/{comment_id}– Moderator deletes a comment.
Request: Admin authenticates (perhaps a token with admin privileges) and calls this endpoint to remove a comment. The{comment_id}specifies which comment to remove.
Response: Similar to above, just a status:{ "status": "success", "message": "Comment deleted." }Internally, the system would mark the comment as deleted (so it no longer appears in GET queries) or remove it entirely. It might also publish a real-time event to connected clients to remove that comment from the UI. We may also consider a softer approach: instead of deleting, mark it as
status: removedso that if needed we can show "This comment was removed by a moderator" in place (this can be a design choice and we’d reflect it in the data model).Other endpoints: We could enumerate more, such as:
POST /user(register new user),POST /login(authenticate) – for user management.GET /user/{user_id}/comments– to get all comments by a user (for profile page).POST /comment/{comment_id}/flag– for users to report a comment.GET /thread/{thread_id}/subscribe– maybe to follow a thread and get email notifications for new comments (stretch feature).etc. These are optional and can be mentioned if needed.
The above core endpoints suffice to cover the main interactions. The design aligns with typical REST principles and covers the required functionality (commenting, nested replies via replying to a comment ID, voting, and moderation). The payload examples show the kind of data we store.
Notice that each comment object carries posterName and not the full profile; we include just enough to display (this is likely denormalized from the User profile at time of posting, or we lookup and attach it on the fly – caching helps here). Also, the client can determine the structure from repliedCommentId relationships.
These API endpoints are meant to be used by both the frontend and any external integration. If we were building a third-party platform, we’d secure these (require API keys or OAuth for third-party usage, etc.), but assuming our own widget uses them internally, we just secure them with user auth.
By designing clear endpoints and payloads, we make the system easier to use and test. Documentation would be provided to client developers (like “To get comments, call this GET, to post, call this… and here are possible errors etc.”). In an interview, outlining a few key endpoints as above is usually sufficient.
(Sources: The API design above is informed by typical patterns and is in line with the example from System Design School’s solution.)
Data Models and Schema Design
We have already described the main entities: User, Comment, Thread, and Vote. Now, let’s outline how these might be structured in a database schema:
User:
Fields:user_id(primary key),username,email,password_hash,display_name,avatar_url,created_at,reputation_score, etc.
Each user has a unique ID. We store login credentials (securely) and profile info. Thereputation_scorecan be computed (not mandatory in schema). This table could also track user settings, e.g., whether they are moderators or banned (a boolean flag or a role field).Thread:
Fields:thread_id(primary key),titleorcontent_ref,created_at, etc.
A thread represents a discussion context. In a single-website scenario, thread might correspond to an article or page. We might use a slug or URL as an ID, or our own generated ID that a site can map to content. (Disqus uses a combination of forum + thread identifiers.) This table is basically metadata; the main usage is to group comments. If the commenting system is multi-tenant (many sites), we’d have aforum_id(site identifier) as well.Comment:
Fields:comment_id(PK),thread_id(FK to Thread),user_id(FK to User),parent_id(self-referencing FK to Comment, nullable if top-level),content(text),timestamp(when posted),status(e.g., “active”, “deleted”, “pending”),upvote_count,downvote_count.
This is the biggest table. Each row is a comment.parent_idlinks to another comment in the same thread (we assume no cross-thread replies). Ifparent_idis null, it’s a top-level comment. We could also storeroot_id(the topmost comment in that thread this comment is under) for easier groupings, but that can be derived by following parent links repeatedly. Vote counts are stored here for quick retrieval in the GET API (alternatively, we could compute counts by querying the Vote table, but that’s slower). Maintaining counts means each vote triggers an update on this table (which can be acceptable with proper indexing or done transactionally with the vote insert).We might also have an index on
thread_id(to query all comments by thread) and onparent_id(to query replies by parent quickly). If using MySQL or Postgres, a composite index on(thread_id, parent_id, timestamp)might help fetch comment hierarchies in order. For example, to get all top-level comments:WHERE thread_id = X AND parent_id IS NULL ORDER BY timestamp. Then for each, get repliesWHERE parent_id = that comment. This approach requires multiple queries for nested replies. Alternatively, some databases support recursive CTE queries to fetch a whole tree, but that can be expensive if deeply nested.Note on hierarchical data: SQL doesn’t handle arbitrary-depth trees elegantly. Options include:
Adjacency list model (what we described: each comment has a parent pointer). Simple and flexible, but requires multiple queries or recursive logic to retrieve a full tree.
Materialized path or nested sets: store a path string or left/right indices to represent tree structure, which can allow fetching a whole subtree in one query. But those are complex to maintain on inserts (especially with frequent inserts like comments).
In practice, limiting depth (e.g., max 3 levels of reply) can keep things simpler. Many commenting systems do this.
We will stick with the adjacency list (parent_id) for simplicity. The app can retrieve comments in two stages: fetch all comments of a thread sorted by time, then on the client side or server side, group them by parent_id to build the hierarchy. Since every comment carries its parent, it’s doable. If performance becomes an issue, we might fetch top-level comments first (page by page), and then fetch replies for each on demand (like expand thread when user clicks).
Vote:
Fields:user_id(PK part, FK to User),comment_id(PK part, FK to Comment),vote_type(e.g., +1 or -1, or enum {up, down}).
This table’s primary key is a composite of user and comment, enforcing one vote per user per comment. When a user upvotes, we insert or update this table. Tallying the votes for a comment can be done by counting rows where comment_id = X (and maybe where vote_type = up minus down). However, as mentioned, we might simply update counts in Comment table for quick reads. The Vote table is still useful to prevent double voting and to possibly allow un-voting (we can delete the record if a user “takes back” their vote).If we extend to other reaction types (like “like”, “laugh”, etc.), we could either have separate columns for each reaction count in Comment and allow one of each per user, or have
vote_typebe a string for reaction type. That’s an extension, but the design pattern remains similar.Other tables: We might have a ModerationLog (id, comment_id, action, moderator_id, timestamp, reason) to record deletions or bans. Also a Flag table if users can flag comments (user_id, comment_id, reason). These help the moderation process. We might also have a Session table for user login tokens (if not using stateless JWTs). For brevity, we won’t detail these, but they exist in a full system.
To illustrate relationships: a User can author many Comments (one-to-many), a Thread has many Comments (one-to-many), and a Comment can have many child Comments (self-relation). Also, a Comment can have many Votes (one-to-many, or many-to-many if you see both user and comment sides). The ER diagram above depicted these: for instance, one Comment -> many Votes (from different users)【30†】.
Capacity considerations: We touched on scale earlier – billions of comments, etc. The data model is straightforward, but how do we store billions of rows? This is where database scaling techniques (sharding) come in as discussed. One simple sharding strategy: shard comments by thread_id using a hash or mod of thread_id. That way, all comments for a thread live on the same shard (makes it easy to query a whole thread without cross-shard joins), and different threads distribute across shards. This works well if no single thread is gigantic. If one thread is extremely large (e.g., a live chat-like thread with millions of comments in one day), that shard can be a hotspot – but that’s an edge case. Alternatively, partition by time (like monthly partitions) might spread write load, but querying a thread across partitions is harder. There are trade-offs here and an interviewee could mention a reasonable approach and note the pros/cons.
NoSQL model alternative: If we used a document DB like MongoDB, we might have a document for each comment or possibly store an entire thread’s comments as a big nested document (not ideal if thread is huge). More likely, each comment is a document with a thread_id field, and we index by thread_id. Cassandra would use thread_id as partition key and comment timestamp as cluster key – that naturally orders comments by time per thread and partitions the data. No joins (user info might be duplicated or fetched separately). This sacrifices some consistency (e.g., updating a vote count is no longer a simple transaction across tables) but is highly scalable. The decision can depend on expected scale – but it’s good to demonstrate familiarity with both approaches.
In summary, the schema is designed to capture the necessary relationships while allowing efficient access patterns for common operations (get comments by thread, get replies by parent, count votes, etc.). We would refine indexes and possibly add denormalizations (like storing posterName in comment to avoid join) for performance. The data model is the backbone; combined with caching and proper indexing, it will support the features we need.
Infrastructure and Scaling Strategies
To ensure our system can serve millions of users and handle spikes in traffic, we need to design the infrastructure with scalability and performance in mind. Key strategies include load balancing, replication, sharding, caching, and content distribution:
Load Balancing Application Servers: As described, we will run multiple instances of our backend behind a load balancer. The load balancer can distribute requests using a simple round-robin or more sophisticated algorithms. Because our app servers are stateless, any server can handle any request, which makes scaling out easy (just add more servers). In cloud environments, we might use an auto-scaling group to automatically add instances when load is high. For reliability, we’d have servers in multiple availability zones so that even if one data center zone goes down, others can pick up the traffic.
Database Replication (Master-Slave): For our SQL database, we will set up replication to create read replicas. The primary (master) database handles all writes (inserts for new comments, updates for votes, etc.) and also can handle reads, but heavy read traffic can be offloaded to replicas. The replicas get a continuous stream of updates from the master. For example, we might have one master and several slaves. The application servers can be configured to send write queries to master and read-mostly queries (like fetching comments) to a replica. This scales read throughput almost linearly with number of replicas and also provides a backup if the master fails (one replica can be promoted to master). The caveat is replication lag: a newly posted comment might take some milliseconds to appear on the replica. If a user reads from a replica immediately, they might not see their comment. We can solve this by either using read-your-write consistency (after a user posts, fetch their comment from the master or cache it locally for immediate display), or ensure replication is very fast (within a second, which is likely in LAN). Many systems simply accept the slight delay (eventual consistency for reading your comment from a different node).
Database Sharding: As load grows beyond what a single DB instance (even a beefy one) can handle, we implement sharding (horizontal partitioning) for the comments data. Sharding strategy was discussed: e.g., by thread_id. We might start with, say, 4 shards (4 separate DB servers each holding a subset of threads). A directory or mapping service (or a consistent hash) can determine which shard holds which thread’s comments. The application then directs the query to the correct shard. With sharding, we also likely deploy replication per shard (so each shard has one master and slaves). This gets complex, but it’s the only way to handle extreme scale (billions of rows). In an interview, it’s good to mention that partitioning writes across multiple servers prevents any one server from being the bottleneck. One trade-off is that cross-shard queries (like “total number of comments in the system”) become harder – but we rarely need that globally. Each thread is self-contained.
NoSQL scaling: If using a NoSQL like Cassandra, scaling is a bit different: you add nodes to a cluster, and data auto-distributes. The system handles partitioning under the hood (often by hash of key). This is an alternative approach to achieve horizontal scaling. Cassandra, for instance, would smoothly handle increasing volume by linearly adding nodes, and it replicates data across nodes for resilience. The downside is eventual consistency and a less flexible query model (but for our access patterns, that’s fine).
Caching Strategy: We already covered caching in detail. From an infrastructure perspective, we would have one or more Redis servers (or a cluster). For high availability, Redis can be in a master-slave setup with Sentinel or use a distributed cache like Memcached (where losing a node only loses cache data, not persistent). Large sites often have a dedicated cache cluster. The cache is typically accessed by all app servers (it’s a shared resource). We should ensure the cache has enough memory to hold the hot data (maybe a few GB; comments are text but relatively small per item).
Content Delivery Network (CDN): We would use a CDN for:
The static JavaScript/CSS of the commenting widget (so that when many users load it, it’s served from edge locations near them, reducing latency and load on our servers).
User profile images (avatars) if we host them. Instead of serving from our origin, we’d have them on a storage bucket with CDN in front so that these assets don’t hit our servers each time.
Possibly the API itself could leverage a CDN if we allow caching of GET /comments for a short time. But real-time requirements and auth make CDN caching of APIs trickier. Instead, we rely on our own caching tier.
The CDN will also help with global distribution. If our users are worldwide, having data centers in the US alone might cause high latency for users in Asia. A multi-region deployment could be considered: e.g., replicate the system in two regions (with separate DBs, etc.) and maybe partition users by region. However, then comments on a given article might be split across regions which complicates consistency. Usually, a commenting service would operate in one region but use CDN to accelerate static content globally. For our design, we can assume a primary deployment region and use CDN for static assets. If low latency worldwide is a requirement, we might mention deploying read replicas in different regions, and using a geo-aware load balancer to direct read requests to the nearest replica. But writes still have to go to the master, or use a multi-master distributed DB which is advanced (and comes with eventual consistency).
Scaling the Real-time Server: The push service will also need to scale. Typically, we’d have a cluster of push servers. They might be behind a load balancer as well for new connections. Often, WebSocket load balancing is done with IP hash or a consistent routing so that once a connection is open, the subsequent packets go to the same server (or we use a sticky session). Some modern LB can handle that, or we use a specific endpoint for each server. We ensure that if one push server fails, clients reconnect and get onto another. The message broker (Redis pubsub or Kafka) should deliver each message to all push servers so they can relay to their connected clients. For extreme scale, we might partition channels by server to reduce cross-traffic. For example, hash thread IDs to a specific push server so that only that server subscribes to that thread’s messages. This might reduce duplicate work. However, that complicates failover if a server goes down. An intermediate approach is grouping: e.g., 10 push servers subscribe to all, each only handles subset of clients (duplicate work but easier failover). Since Disqus managed with 5 push servers for their entire network at one point by optimizing (with Nginx Push Stream), we can say our design could scale to many servers as needed and this is a known challenge akin to scaling chat or feed systems (comparable to Twitter’s fan-out problem).
Auto-scaling and Demand: The system should handle daily traffic patterns and spikes. For instance, if a particular article goes viral, the comment section might suddenly see huge load. Auto-scaling can kick in more app servers. The DB might be the choke point in a spike – here caching helps by absorbing read spikes. For write spikes (e.g., a live event receiving thousands of comments a minute), scaling the DB is harder dynamically, but a well-partitioned system can handle more writes (multiple shards). A queue can buffer writes if needed (though for comments we want to write quickly to DB).
Monitoring and Metrics: To maintain scalability, we set up monitoring on key metrics: QPS, response times, DB CPU, cache hit ratios, queue backlogs, etc. This helps identify bottlenecks early. For example, if cache hit rate is dropping, DB load might spike, indicating maybe our cache TTLs are too low or cache is thrashing. Tools like Sentry (Disqus used Sentry for error logging) and other APM tools can be used.
By combining these strategies, our system can grow from a small scale (a single server and DB for development) to a large scale (cluster of app servers, distributed caches, multiple DB shards, etc.). An important aspect of system design is understanding at what point you need each layer. Initially, maybe one database with replication is enough. Once we approach its limits (in terms of writes or storage), we plan sharding or use of NoSQL. The design we presented is ready for large scale – some portions (like multi-region deployment) might not be needed until extremely large scale.
To put it in perspective: Disqus at ~45k requests/sec and billions of page views used a combination of Django app servers, PostgreSQL (with sharding presumably), Cassandra for data, Redis/Memcached for caching, and Nginx for load balancing. Our design stands on similar principles of dividing load and using the right tool for each problem (relational DB for structured data, caches for reads, queue for real-time fanout, etc.). This addresses how we scale both vertically (optimizing single components) and horizontally (adding more machines). Next, we look at how to keep things running reliably and securely.
Fault Tolerance and Reliability
Building a reliable system means preparing for the inevitable failures of components and ensuring the system as a whole continues to function (or at least fails gracefully). Here are key considerations for fault tolerance in our commenting platform:
Redundant Servers: No single point of failure. We run multiple instances of each component:
Multiple app servers, so if one goes down, the load balancer routes traffic to others. If an entire data center AZ goes down, we have others in another AZ (if on cloud).
Multiple push servers, so a failure only drops some WebSocket connections; those clients should automatically reconnect (the client JS can have reconnect logic) and end up on a healthy server. There may be a brief hiccup in real-time updates, but the system recovers.
The cache can be made redundant (Redis primary and replica). If the primary cache dies, either a replica takes over or the app bypasses cache (serving from DB directly). Losing cache is not catastrophic, it might just increase DB load temporarily.
Database redundancy: Having replication not only helps scale reads but provides a hot standby. If the master DB fails (hardware issue, etc.), we can promote a replica to master. This might cause a few seconds of downtime for writes while failover occurs, but it prevents prolonged outage. We should also keep backups (point-in-time backups, etc.) in case of data corruption.
Failover and Heartbeats: We likely have a mechanism to detect failures (health checks). The load balancer should have health checks so it stops sending traffic to a downed app server. Similarly, if a push server fails, the clients detect a broken socket and reconnect (the LB directs them to a good server). For DB, a failure detection system (like etcd/consul or cloud-managed) can automatically promote a replica.
Graceful Degradation: In some failure scenarios, the system should degrade but still function in a basic way:
If the real-time service fails entirely (say the message queue or push servers are down), users can still post and fetch comments (the core functionality). They just won’t get live updates until it’s restored. We might even have the client detect if websocket is unavailable and fall back to polling the GET API every 30 seconds as a temporary measure.
If cache is down, it’s okay – the app servers will hit the database directly. It will be slower and could strain the DB if traffic is high, but functionality is intact. For this reason, our code should always have a fallback (e.g., if Redis query fails, log it but continue without cache).
If the database master is down and failover is in progress, we might return an error for writes for a few seconds. But read requests could be served from read replica if it’s up (though it might be slightly stale). Some systems even switch to read-only mode during failover – in a comment system, a short read-only period might be acceptable (users can’t post new comments for that brief window, but they can still see existing ones).
If the entire database cluster is unreachable (worst case), the site might not be able to load comments at all – at that point, we could have the page show a friendly error like “Comments are temporarily unavailable” rather than hanging. That’s part of graceful degradation from the UX perspective.
Data Integrity and Consistency: Use transactions where needed (e.g., when a comment is posted, we want to ensure the comment row is inserted and, say, a counter of comments in thread is updated together). If using eventual stores, design idempotency – for example, if the comment-posting process crashes after writing to DB but before sending to queue, ensure that on retry we don’t duplicate. Perhaps assign comment IDs client-side or use an UPSERT style.
- Also, the ordering of events: if a user quickly posts then deletes a comment, we need to handle that sequence properly (maybe the delete comes after post in queue, etc.). Ensuring the queue or our logic accounts for such sequences (perhaps by tagging events with a version or timestamp).
Rate Limiting and Throttling: Though often considered a security measure, it is also for reliability. We discuss it separately below, but essentially preventing overly heavy usage (intentional or not) keeps the system stable.
Monitoring & Alerts: From a reliability standpoint, set up monitors on uptime and key stats. If the error rate for any API spikes or if queue backlogs grow, on-call engineers should be alerted. Real-time detection allows quick mitigation (maybe auto-restart a service, or add more capacity if something is overloaded).
Testing and Chaos Engineering: In a very robust setup, one might test failure scenarios (kill a server process to see if LB reacts, etc.). While not in initial design, being mindful that failures will happen leads us to design with redundancy and handle exceptions in code (e.g., catching exceptions from a DB call and not crashing the whole app).
By implementing these, we aim for high availability. For instance, using replication for availability is explicitly mentioned in our requirements. If done right, the system can tolerate the loss of individual components with minimal impact: no single failure should completely take down the service.
One can mention concrete numbers: aim for N+1 redundancy on each tier (if you need 3 servers for capacity, run 4 so one can fail). Use cloud managed services where possible for DB (they handle failover automatically). Have daily backups for disaster recovery (if an admin accidentally wipes a table, we can restore).
Lastly, consider isolating critical services: For example, if the authentication service or identity provider goes down, no one can log in which halts commenting. Maybe allow temporarily reading comments without login, or cache auth tokens so people already logged in can continue. These edge thoughts show depth in reliability planning.
Rate Limiting
Rate limiting is essential to protect the system from misuse, whether malicious (spam bots, DDoS attempts) or accidental (a buggy client spamming requests). In our commenting system, we will apply rate limiting at various levels:
Comment Posting Rate: We can cap how frequently a single user can post comments. For example, no more than 1 comment per 5 seconds sustained rate, with a small burst allowed (maybe 2-3 comments in quick succession, then cooldown). This prevents a user (or bot) from flooding a thread. If the limit is exceeded, the API can return an HTTP 429 Too Many Requests or a structured error indicating “You are commenting too fast, please wait.” This also helps encourage thoughtful posts over spam.
Upvote/Downvote Rate: Similarly, limit how fast a user can cast votes. A user rapidly clicking upvote on hundreds of comments could be a bot or trying to game the system. For instance, limit to, say, 10 votes per minute. (The exact numbers can be tuned.)
API Request Rate per IP/User: More generally, we can throttle API calls to prevent abuse. For example, no more than X requests per second from a single IP or user token. This stops scripts from hammering the API (which could overload servers or DB). Since our main API calls are fetches (which are cacheable and not too heavy) and posts (which are moderated), a reasonable limit might be something like 100 requests/minute per user, which is far above normal usage.
Burst Control: Often implemented with a token bucket algorithm or fixed window counters. We allow some burst (so that, for example, loading a page that triggers 5 quick API calls is fine), but sustain beyond that triggers limiting.
Global rate limits: In extreme cases, if the entire system is under attack (e.g., a botnet hitting the APIs), we might have global throttling or a web application firewall that blocks or filters traffic. But focusing on per-entity limiting is usually enough for design discussion.
Where to implement: Rate limiting can be done at:
API Gateway or Load Balancer level: If we have an API gateway in front (like Kong, Apigee, or cloud LB features), it can track usage and enforce limits before traffic even hits our app servers.
Application level: The app can check a Redis store or in-memory counter for each user/IP and decide to reject if over limit. For example, maintain a key like
user:{id}:recent_posts_countwith expiry.Possibly both – a basic limit at LB and finer logic in app.
Account-specific rules: We could be more lenient for trusted users vs new users (e.g., a new account can only post 5 comments in its first hour). Or if a particular IP is making many new accounts (indicator of spam farm), we flag or slow those down.
Moderation tie-in: If a user consistently hits limits or tries to, that might itself raise a flag to moderators.
Implementing rate limiting ensures no single user (or small group) can overwhelm the system’s capacity, which preserves resources for everyone and also can mitigate spam. It’s also mentioned as a security measure: e.g., prevent brute-force login attempts (though that’s more on auth, but relevant if we had login API).
In design terms, mentioning rate limiting shows that we’re thinking about abuse and system protection, which is a common interview topic. A candidate might say: we can use a distributed counter in Redis to track requests per minute for each key (IP or user). That’s usually sufficient for a design discussion.
Security Considerations
Security is critical, especially since this system handles user-generated content and is embedded on various websites. We will address the specific concerns mentioned (XSS and spam bots) and other general security aspects:
Cross-Site Scripting (XSS): Since users can input text that will be displayed to others, we must prevent any malicious scripts or HTML from being injected. XSS could allow an attacker to steal other users’ info or deface the site. To mitigate XSS:
We will sanitize user input on comment submission. This means removing or encoding any HTML tags or attributes that could be dangerous. We might allow a limited set of safe HTML (like
<b>,<i>, links) but strip out<script>,onerrorhandlers, iframes, etc. Libraries exist in many languages to do this (e.g., OWASP Java HTML Sanitizer, DOMPurify in JS).Alternatively, we store all comments as plain text (escape any HTML characters) and only render text. If formatting is desired (like newlines or basic markdown), we can convert allowed syntax server-side. Many comment systems allow a subset of Markdown for bold, links, etc., which can be safely rendered.
Use output encoding whenever inserting user content into HTML pages. If our widget generates HTML from JSON, we must ensure to
.textContentany dynamic text rather than.innerHTMLwithout sanitization.Also protect against stored XSS via profile fields (if we display user names or avatars that could contain script, though avatars are usually just images by URL, which we could validate).
Periodically, security reviews or use of tools to scan XSS vulnerabilities (especially because an exploit in our system could affect many sites that use it).
Note: Disqus, as a third-party script, had to be extremely careful because any XSS in it would run on all websites that integrated it. They likely employ strict sanitization and content security policies. We should do similarly: possibly serve our widget with a Content Security Policy that disallows inline scripts or unknown sources, to mitigate XSS impact.
Spam Bots: We discussed content spam under moderation, but focusing on the security angle:
Bot detection: We can use CAPTCHA for certain actions (e.g., untrusted signups or comment submissions) to differentiate bots from humans.
Email/SMS verification: Ensure accounts are tied to real inboxes/phones to deter mass fake account creation.
Honeypot fields: A trick where an extra hidden field is in the form – legitimate users won’t fill it (since hidden), but simple bots might, flagging themselves.
User-agent and IP analysis: Block known bad user agents or rate-limit suspicious IP ranges.
Keep our software updated, as spam bots might also try to exploit any vulnerability (like an XSS to hijack user sessions and post spam).
Anti-scraping: Although not asked, sometimes people scrape comments. We could throttle unusual GET patterns too.
Ultimately, spam is a cat-and-mouse game. Our combination of Akismet, rate limits, and verification should catch the majority. The remaining will be handled by moderators and community flagging.
SQL Injection: Our server must use parameterized queries or ORM to avoid injection attacks via API inputs. (This is standard, but worth mentioning that all inputs should be validated/escaped appropriately.)
Authentication Security: Protect login endpoints (rate limit login attempts to prevent brute force, hash passwords properly, use HTTPS so credentials aren’t sniffed). Use secure cookies (HttpOnly, Secure flags) for sessions to prevent XSS from stealing them, and consider CSRF protection for forms (like posting a comment could be a target of CSRF – though if we require a token in header or using same-site cookies, we mitigate that).
Cross-Site Request Forgery (CSRF): Since the widget is loaded on other domains, we need to be conscious of how cookies are handled. If our auth uses cookies, then an attacker site could try to trigger actions using a logged-in user’s cookies. We should include CSRF tokens in forms or use the SameSite cookie attribute to prevent cross-site usage. If our APIs are called via AJAX from the legitimate frontend, setting SameSite=Lax on cookies might suffice. For extra measure, an anti-CSRF token that the frontend JavaScript adds to each request (acquired when loading the widget) can ensure the request is coming from our context.
Content Security Policy (CSP): If we host the commenting iframe ourselves, we can enforce CSP to restrict what it can load (e.g., only allow scripts from our domain, no inline scripts except those we intend). This can limit XSS impact if any slipped through.
Encryption & Privacy: All communications should be over HTTPS to prevent eavesdropping or tampering (especially since session tokens and data pass through). User passwords are hashed in storage. We comply with privacy requirements (if relevant, allow users to delete their data, etc., though beyond pure system design scope).
Permissions: Ensure that moderation APIs can only be called by authorized moderators. Normal users shouldn’t be able to call delete. Use role-based access checks on the backend.
Dependency security: If our system uses third-party libraries or software, keep them updated to patch any vulnerabilities (e.g., frameworks, the Nginx server, etc.).
In essence, the security plan is: validate and sanitize all inputs, encode outputs, use proper auth and session security, and thwart automated abuse. Disqus, as noted, even partners with services and employs ML to keep the platform safe. We can reference their approach to emphasize the seriousness of spam and abuse prevention in such systems. With these measures, our platform can provide a safe and trusted environment for discussion.
Trade-Offs and Design Decisions
In designing this commenting system, we faced several trade-offs. Here we discuss some key decisions and their pros/cons:
Relational DB vs NoSQL: We opted for a relational database initially for its ease of managing relationships (users, comments, replies) and strong consistency (important for things like not losing a comment or counting votes accurately). The trade-off is that scaling a single SQL database can be challenging beyond a certain point (vertical scaling has limits, sharding adds complexity). A NoSQL store (like Cassandra or DynamoDB) would scale writes and storage horizontally with less effort, but we’d sacrifice some consistency (eventual consistency) and have to denormalize data (since joins aren’t easy). Disqus in fact uses both: PostgreSQL for core and Cassandra for scalability, showing that a hybrid approach can work. In an interview, one could say: start with SQL for simplicity, monitor scale; if writes or data volume exceed what a single node can handle, either shard SQL or transition hot data to a NoSQL. Each has maintenance costs – SQL sharding is complex, NoSQL tuning and multi-datacenter consistency is also complex. It’s a trade-off of consistency & query flexibility vs. seamless scalability.
Real-Time Updates vs Simplicity: Providing real-time updates (via websockets, push servers, etc.) greatly enhances user engagement but adds complexity and resource usage (keeping connections alive, additional servers and pub/sub logic). A simpler approach could be polling: e.g., every 10 seconds the client asks for new comments. Polling is easier to implement and doesn’t require persistent connections, but it either introduces latency (comments might appear up to 10s late) or high load (if polling very frequently). We chose the realtime push for the best UX. The trade-off is more complex architecture. In some scenarios, a hybrid might be used: for less active threads, rely on polling; for very active ones, upgrade to realtime. But that complicates the client. We decided that real-time is a core requirement, so we accepted the added complexity. This is justified by the increase in user interaction observed when realtime is enabled (people stay on page longer, comment more). Another consideration: using third-party services like Pusher or Socket.io cloud could offload the realtime infrastructure at a cost, but for our design we did in-house.
Eventual Consistency (Performance) vs Strong Consistency: Throughout the design, we allowed for slight delays in consistency for the sake of performance and availability. For example, after posting a comment, replicas might lag a bit (so another user might not see it immediately unless via realtime push). Or caching might show stale data for a few seconds. The trade-off is between freshness of data and system throughput. We lean on eventual consistency in non-critical areas (e.g., comment counts might not update instantly, as often seen on YouTube). If we insisted on strict consistency (everyone sees everything at once exactly), we’d likely have to forego certain optimizations (like widespread caching, or we’d need to push invalidations everywhere which is complex). Thus, we accept eventual consistency in exchange for better performance and simpler scaling. As long as the delays are small (seconds), it’s usually a good trade-off in a social application.
Caching Strategy – Freshness vs Speed: We know caching improves speed massively, but the downside is data can become outdated (and cache invalidation is famously hard to get perfect). We chose to cache aggressively on reads and invalidate on writes. The risk is always that an invalidation might be missed or delayed (e.g., if multiple caches or if a server crashes before invalidating). That could show a user an outdated state (like a comment count off by one, or a comment that was deleted a second ago still showing). The trade-off is acceptable in our case because the alternative (no caching) would mean every page load hits the DB, which at large scale would reduce performance and increase cost. We mitigate the downside with short TTLs and thorough testing of our cache invalidation paths. In essence, we favor speed and scalability with caching at the cost of occasional minor consistency issues, which is a typical approach.
Monolithic vs Microservices architecture: We briefly touched on possibly separating services (auth, comment, moderation). The monolithic approach (all-in-one) is simpler for a small team and ensures consistency (e.g., one codebase, no network calls between services). Microservices can offer better isolation, independent scaling, and clarity of ownership (e.g., the Auth team vs Comments team). We initially lean monolith for a beginner-friendly design, but acknowledge that as the system and team grows, splitting into microservices could be beneficial. The trade-off here is simplicity vs. scalability/maintainability. Monoliths can become unwieldy as features add up, but microservices introduce overhead (DevOps complexity, dealing with partial failures between services, etc.). Disqus’s own story: they started as a Django monolith, then carved out the realtime pipeline as a separate component for performance reasons, and likely have since modularized further. So an evolutionary approach is best: start monolithic (fast to develop), identify bottlenecks or modules that need independent scaling, and split them out (like they did with realtime or maybe analytics).
Moderation approach – Pre vs Post: Another trade-off is whether comments appear immediately or only after approval. We chose to let comments go live immediately (except obvious spam that gets auto-filtered), which is the default for most interactive platforms because it encourages fluid conversation. The risk is that some bad content might be visible for a short time until a moderator removes it. The alternative (pre-moderation, where every comment must be approved before others see it) ensures no bad content slips through, but it severely slows down discussion and burdens moderators. Disqus and similar systems almost always use post-moderation with robust filtering. So we took the same approach: trust users by default, but have the tools to quickly remove and learn (e.g., if a user consistently posts bad stuff, the filters get stricter or they get banned). The trade-off is community engagement vs. absolute safety. We bias toward engagement while striving to catch most bad stuff automatically.
Voting model – immediate count vs eventual count: When a user clicks upvote, do we update the count immediately (optimistically) or wait for server confirmation? We likely update immediately in the UI (optimistic) for responsiveness, but the true count comes from server eventually (especially if multiple users are voting concurrently). The trade-off is user experience vs accuracy. If two users upvote at same time, both might momentarily see, say, “5” -> “6” individually, but the final count might be “7”. If our realtime system pushes vote count updates, we could correct it. This is a minor trade-off but highlights synchronous vs asynchronous handling. We prefer a snappy UI (optimistically increment) and then adjust if needed when the server returns the updated count or via realtime.
Use of Third-Party Services vs In-House: We discussed using Akismet for spam – that’s an example of leveraging a third-party. The benefit is not reinventing the wheel (Akismet has huge data to detect spam better than we likely can initially). The downside is external dependency and possibly cost. We think it’s worth it for quality. Similarly, one could consider using a managed real-time service or a cloud database, etc. Each external service offloads work but can limit flexibility or add vendor risk. In our design we use Akismet (for spam) because that’s a well-acknowledged trade-off where the benefits are high. For core features (like our own database, core logic), we keep in-house control.
Every design decision comes with such trade-offs. It’s important to articulate why we chose one approach over another and under what conditions we might re-evaluate. In a system design interview, showing awareness of these trade-offs (and being able to argue for one given the requirements) is key.
Comparison with Disqus’s Solution
Finally, let’s compare our proposed design with how Disqus, a real-world large-scale commenting platform, handles similar challenges:
Real-Time Updates: We designed a WebSocket-based pub-sub system for realtime comments. Disqus originally started with a polling approach (clients polling memcache keys), which didn’t scale beyond 10% of their network usage. They evolved to a pipeline using Redis pub/sub and eventually the Nginx PushStream module to push updates to clients. Their solution involved a “glue” service formatting messages and Nginx handling fan-out to many long-lived HTTP connections, reducing overhead per message. In essence, Disqus’s final architecture for realtime achieved similar goals to our design: decoupling via queues, a dedicated push channel, and using an event-driven server for many connections. One difference: they leveraged existing web server modules (Nginx) for scalability, whereas we might use a custom WebSocket server cluster. Also, Disqus managed to support 1.5 million concurrent users and ~165k messages/sec with ~0.2s latency by these optimizations, which validates that our approach (and the need for it) is real. Modern implementations might use technologies like WebSocket servers in Go or Node which can similarly handle large concurrency. Disqus even moved parts of their stack to Go later for efficiency (as hinted by them “trying out Go” in updates).
Data Storage and Scalability: We recommended using relational DB with possible sharding and/or NoSQL. Disqus’s data stack (as of 2013) included PostgreSQL, Cassandra, Redis, and Memcached. This implies:
They used Postgres for core transactional data (likely user accounts, core comment data).
Cassandra was used to handle large scale data where eventual consistency is okay – possibly for analytics, or storing the huge volume of comment text in a distributed way (maybe older comments or certain indexes). It’s not publicly detailed, but the presence of Cassandra suggests they hit limits of single-node DB and needed horizontal scaling for some parts.
Redis and Memcached for caching and fast data structures. Redis also for their queue (Thoonk) and possibly ephemeral data like realtime subscription info.
This matches our design which allows for mixing storages: SQL for structured needs, NoSQL for scale. They likely sharded Postgres as well (8 billion page views and associated comments is massive for one DB). Our design’s approach to partition by thread is something Disqus could do because each “forum” (site) and “thread” can be an obvious shard key (e.g., site ID mod N).
Additionally, Disqus serves millions of websites, meaning multi-tenancy. Our design mostly discussed one site, but to compare: in Disqus, each site (forum) has its own moderation settings, and data is partitioned logically by forum. Our design could extend to that by adding a site_id in Thread and scoping data by site. That’s an extension but not a fundamental change (just another level of partitioning).
Moderation & Spam: Disqus uses a blend of automated filtering and admin tools. Notably, they integrate Akismet for spam detection (same as we propose). On top of that, they leverage their entire network’s data: a user’s reputation and history across all sites helps determine if their comment is likely spam. For example, if a user spammed on one site, they might get flagged more quickly on another. In our design, if we were focusing on one site, we use reputation but if multi-site, we could centralize it like Disqus. They also added advanced toxicity filters and machine learning for detecting harassment or hate speech (Disqus announced a “Toxicity mod filter”). This goes beyond spam into content moderation. Our design mentions filters and maybe blacklists, but Disqus’s approach is more sophisticated with ML. The takeaway: as the community grew, Disqus invested in smarter moderation tools – something our system could adopt as well when basic keyword filters aren’t enough.
On the manual side, Disqus provides a moderator dashboard where all comments across one’s site can be managed. We also described an admin interface and APIs for deletion, etc. One difference: Disqus allows community flagging and has a concept of “trusted users” and “ban lists” that moderators can configure. Our design is aligned, and we could implement similar features (trusted users bypass filters, banned users auto-blocked).
User Authentication: Disqus offers its own single sign-on but also allows users to log in via popular OAuth providers. We didn’t dive deep into third-party login, but that’s something a platform like Disqus does to reduce friction (users can comment with Google, Facebook, etc., which Disqus then associates to a Disqus account). Designing that would involve integrating with OAuth flows which is an extension of our auth service. The core idea of having a central user identity per platform is the same. Another difference: Disqus is an “intrinsic network” connecting sites. That means your Disqus profile accumulates your activity across the web, which is an added value (you can follow users, see their comment history on any site). Our design did not explicitly cover that aspect, but it could be an add-on (like a user’s all comments and a following system). This wasn’t requested, so we focused on per-site functionality. But it’s worth noting as a difference: Disqus effectively built a mini social network on top of commenting.
Performance and Infrastructure: Disqus at scale had to fine-tune performance. They mention that slowness often came from interactions with other services (DB, cache) rather than the Python code. So they heavily used caching patterns (they show code where they fetch from cache or query DB then cache). Our design also emphasizes caching at multiple levels. They run on bare metal servers (at least in 2014, they mentioned not using EC2), likely to squeeze the maximum performance for cost. We didn’t specify cloud vs metal, but our design could be on either. In interviews, one might mention cloud (for elasticity) or mention they could optimize on hardware if needed. Another part: they handled ~45k req/sec with 18 engineers – indicating the system was fairly optimized. They scaled Django by doing a lot outside of Django (caching, moving some tasks to other components). Our design follows similar philosophy: use web framework for simplicity but offload heavy lifting to caches, queues, and specialized servers.
Feature set: We covered threaded replies, votes, realtime, auth, moderation – which aligns with Disqus’s feature set (Disqus also has social sharing, notifications to users, and analytics for publishers). We did not cover email notifications (e.g., “someone replied to your comment” emails) due to scope, but that’s something Disqus provides. Implementing that would require a background job system scanning for new replies and sending emails, or a push notification system. It’s an additional component one could mention if time permits.
Trade-offs chosen: Disqus clearly values eventual consistency where needed (e.g., the YouTube example in our source references the same idea that not everything is immediate for sake of performance). They focus on engagement and performance over strict guarantees, which our design also does. They encountered trade-offs like Python vs Go (they introduced Go later to improve throughput of certain components). In our design, we didn’t specifically pick languages, but one might say “if Python isn’t enough, consider using Go for the realtime server or performance-critical sections”, mimicking Disqus’s path of gradually optimizing bottlenecks.
In conclusion, our design is largely consistent with what a scaled-out version of Disqus looks like, with the differences mostly in specific implementation choices or extended network features. We designed with the same principles: partitioning workload, caching heavily, layering for realtime, and ensuring moderation and security at scale. By understanding how Disqus tackled these challenges – from realtime pipelines to spam fighting – we validate that our approach is on the right track and identify areas where the product could evolve (like ML moderation or social features) if we were truly building a Disqus competitor.
This comprehensive system design should provide a beginner with a solid blueprint of a Disqus-like commenting platform. It covers the end-to-end aspects – from client experience to backend architecture – and explains how to keep the system fast, scalable, and secure. By comparing with Disqus, we also ground the discussion in real-world practices. The reader can now use this as a study guide for designing large-scale web discussion systems or as a reference in system design interviews.
