A social media application looks like one product, but its data serves several different jobs. Accounts need reliable identity rules. Posts need flexible content. Feeds need quick retrieval. Likes can generate bursts of writes, while photographs and videos consume most of the storage.
Choosing a database by asking whether social data is "structured" misses these differences. A more useful approach is to examine the queries, consistency requirements, and operational costs of each part. SQL and NoSQL describe broad families, not a fixed divide between correctness and scale.
Introduction
A useful database decision starts with the product's promises. Can usernames collide? Can a user like the same post twice? How quickly must a removed follower lose access to private content? Can a feed take a few seconds to include a new post? Each answer constrains the storage and serving design more directly than a database category does.
This article considers a hypothetical social application with accounts, follows, posts, likes, comments and a home feed. It begins with a practical small-team design, then explores why particular workloads might justify prepared views or another store. The objective is a design that can evolve without maintaining several overlapping sources of truth.
The term NoSQL includes document databases, key-value systems, wide-column stores and graph databases, which have different query and transaction capabilities. SQL systems also vary in replication, distribution, JSON support and operational model. Compare specific products and deployment choices when those details become decisive.
For the initial version, assume that public content can tolerate some asynchronous propagation while account ownership, uniqueness and private-content access require explicit correctness rules. These are example requirements to make the trade-offs concrete. A real application should replace them with its own promises and measure their cost.
The design should also cover the unglamorous paths: deleting an account, rebuilding a feed, restoring a backup and investigating a failed write. A database that makes the first timeline query elegant can still be expensive if routine moderation requires searching every partition or updating millions of embedded copies.
The best starting point is usually the smallest set of systems that satisfies the known workload and recovery requirements. Growth can justify specialisation later. Introducing another database should solve a measured problem with a clear ownership boundary, because every additional store brings its own backups, credentials, upgrades and failure behaviour.
Identify the Data and Access Patterns
Start with a small set of concrete operations:
- Create an account without allowing duplicate usernames.
- Publish a post and retrieve an author's recent posts.
- Follow another account and list the accounts someone follows.
- Add one like per user and post.
- Retrieve the next page of a home feed.
Add moderation, account deletion, reporting, and recovery. These less visible operations often expose weaknesses in a model optimised only for the main feed.
Store media bytes in object storage and keep their identifiers and metadata in the database. The database choice for post records does not need to be the storage choice for the associated video files.
Separate Facts, Relationships and Prepared Views
An account, post and follow relationship are authoritative facts. A home feed is usually a prepared way of finding relevant posts, and a like count may be an aggregate of individual like facts. Distinguishing these roles prevents a fast derived view from accidentally becoming the only surviving record of user actions.
For each feature, identify the write that establishes truth and the reads that consume it. Publishing a post might commit one post record and a durable event. Feed workers then create recipient entries. If the feed store becomes unavailable, the post still exists and the entries can be rebuilt from reliable inputs.
A useful worksheet looks like this:
| Data | Main access pattern | Important rule |
|---|---|---|
| Accounts | Lookup by identity or username | Unique identity and controlled access |
| Posts | Read by identifier; page by author | Ownership and current visibility |
| Follows | Membership checks and paged lists | One relationship per account pair |
| Likes | Membership check; count | One active like per user and post |
| Comments | Ordered pages under a post | Moderation and bounded retrieval |
| Feed entries | Page by recipient and position | Rebuildable references with safe filtering |
Write down result sizes, not only query names. "List followers" can mean 30 rows for a profile page or millions of identifiers for fan-out. The latter should normally be a paged background operation rather than an unbounded request that holds a database connection until every follower has been loaded.
Estimate Work Before Choosing Capacity
Suppose, purely for illustration, 200,000 daily active users publish one post each and open their feed ten times per day. That implies about 200,000 daily post writes and two million feed requests before comments, likes, pagination and background processing. Peaks and uneven popularity matter more than dividing those totals evenly across a day.
Fan-out can multiply writes dramatically. If an author's post is copied as a reference into 500 recipient feeds, one authoritative post produces 500 derived entries. A few authors with millions of followers change that distribution. This is a feed-design issue regardless of whether the destination uses SQL or NoSQL.
Media changes the storage estimate again. A short post row may be small compared with several image variants or a transcoded video. Keep upload state, content type, dimensions, checksums and object identifiers in metadata records, while object storage and a delivery layer serve the bytes. Plan orphan cleanup when an upload succeeds but post creation never completes.
Use these estimates to identify which workload needs optimisation. An application dominated by feed reads may benefit more from a prepared feed than from moving account records to a distributed database. A system dominated by large uploads needs a media pipeline, even if its relational tables are performing well.
Where a Relational Database Fits
A relational model might use Users, Posts, Follows, and Likes tables. Foreign keys express relationships, while unique constraints enforce rules such as one (UserId, PostId) like per user.
This makes an initial implementation approachable. Joining posts to authors, answering moderation queries, and updating related records within a transaction all use familiar tools. PostgreSQL's constraint documentation explains the mechanisms that enforce these relationships independently of application code.
The trade-off appears when expensive joins or heavily contended writes dominate a workload. Indexes, query changes, caching, and read replicas may provide substantial room before sharding becomes necessary. A relational database is not automatically limited to a small application, but distributing relational work can require careful design.
Model the Core Rules Directly
A simplified relational model can express important identities without relying on a previous application lookup:
CREATE TABLE follows (
follower_id bigint NOT NULL REFERENCES users(user_id),
followed_id bigint NOT NULL REFERENCES users(user_id),
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (follower_id, followed_id),
CHECK (follower_id <> followed_id)
);
CREATE INDEX ix_follows_followed
ON follows (followed_id, follower_id);
CREATE TABLE likes (
user_id bigint NOT NULL REFERENCES users(user_id),
post_id bigint NOT NULL REFERENCES posts(post_id),
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (user_id, post_id)
);
CREATE INDEX ix_likes_post
ON likes (post_id, user_id);
The primary key on follows supports checking and enumerating the accounts one user follows. The reverse index supports enumerating an author's followers. These are different directions through the same relationship, so one index order does not automatically serve both efficiently.
The like identity prevents two concurrent requests from creating duplicate active likes. A prior SELECT can improve the user-facing flow, but the database constraint remains the final guard when two requests race. The application must handle the specific conflict according to the API's semantics.
Username uniqueness needs a precise definition. If the product treats Alice and alice as the same name, store or index an appropriately normalised comparison form. Consider renames and reserved names as business rules. A generic unique constraint only enforces the comparison semantics actually configured.
Foreign keys protect relationships but do not decide every deletion policy. Cascading through a very large social graph in one transaction can create long locks and heavy work. A staged account-deletion workflow may first prevent access, then remove content and relationships in bounded batches while maintaining the intended visibility rules.
Match Indexes and Queries to the Feature
An author's recent-posts query benefits from a composite index aligned with author, publication time and a tie-breaking identifier. Use a stable cursor rather than continually increasing offsets for deep histories. The database can then retrieve a bounded slice rather than repeatedly discarding earlier pages.
A relational schema also supports investigative queries that were not anticipated on day one. Moderators may need to join a report, its post, its author and previous enforcement actions. That flexibility has practical value when the product's reporting needs are still evolving.
Flexibility does not make every query cheap. A feed that joins millions of candidate posts, applies complex ranking and sorts a large result on each request can be expensive. Inspect the plan, candidate count and row widths. Fetch only the fields required for the page and avoid performing one author lookup for every returned post.
Read replicas can absorb eligible reads, but decide which reads require current data. Immediately reading a newly created post through a lagging replica can produce a confusing not-found result. Route those operations appropriately or adopt an explicit read-after-write strategy with a documented fallback.
Where a Document or Key-Value Model Fits
A document database can store a post's text, media references, and bounded presentation settings together. If the application usually loads these fields as one unit, this can align storage with its reads.
Keep unbounded relationships separate. Embedding every comment or follower inside one growing document creates size, contention, and pagination problems. MongoDB's embedding guidance describes retrieving related data together and the document-size constraints that must be considered.
A key-value model can suit a prepared feed: use the recipient as part of the partition key and a sortable position as part of the item key. The application can retrieve a page without reconstructing every relationship at request time.
The cost is maintaining that prepared view. Editing a post, removing a blocked author's content, or changing visibility may affect many stored entries. Fast reads often require additional writes and explicit repair processes.
Embed Data That Has a Shared Lifecycle
A post document might contain text, a bounded array of media references, presentation settings and an edit version. These fields are commonly loaded and changed together, making the document a useful unit of work. The model can also avoid a join for details that have no independent lifecycle.
An illustrative document could look like this:
{
"postId": "post-842",
"authorId": "user-27",
"text": "A walk along the coast",
"visibility": "Followers",
"version": 3,
"media": [
{
"assetId": "asset-981",
"altText": "Waves below a cliff path",
"position": 0
}
]
}
The array is appropriate only if the product places a sensible bound on media per post. Comments, reactions and followers can grow without that bound and are normally separate collections or items. Embedding them all makes unrelated writers contend on one object and prevents efficient independent pagination.
Document flexibility does not remove schema design. Validate required fields, agree how versions change and define how old documents are read after deployments. An application that accepts arbitrary shapes can still fail when a background worker assumes every record contains a field introduced last week.
Denormalised display data needs a policy. Embedding an author's display name in every post can avoid a lookup, but a rename may require updates or tolerate stale historical names. Decide whether the copied name is a snapshot of publication time or a cache of the current profile. Both can be valid, but mixing them accidentally causes confusing results.
Design Key-Value Access from Known Questions
A prepared feed can use the recipient as a partition component and a sortable feed position as a key component. Reading the next page becomes a targeted ordered query. Store post identifiers and minimal ranking context, then resolve the current authoritative content as needed.
This works well when the important access patterns are known. It is less convenient for a new administrative query that asks for every feed entry containing a particular post unless a reverse access path was designed. Such paths require additional indexes, duplicated items or a background scan, each with a cost.
Partition design is part of the model. A recipient with exceptional feed activity may create a hot logical key. A status-only index can funnel many writes into one destination even when the base table is balanced. Evaluate the distribution of actual traffic, including secondary indexes and bulk maintenance work.
Do not interpret a fast point-read benchmark as proof that the complete feature is cheaper. Include the fan-out writes, hydration requests, filtering, pagination state and repair pipeline. A model optimised for one page read can still require substantial total work elsewhere.
Choose Consistency Per Feature
A like count that catches up shortly after an action may be acceptable. Showing a private post to someone whose access has been removed is a different requirement.
Define the source of truth for each decision. A cached feed can suggest candidate posts, while the serving path applies current authorisation rules before returning restricted content. Explain how caches and replicas participate in that guarantee, including their invalidation and failure behaviour.
Do not assume NoSQL means transactions are unavailable. MongoDB supports multi-document transactions, with configuration and performance considerations. Equally, reading from an asynchronous replica of a relational database can return stale data. The relevant question is what the chosen operations and deployment actually guarantee.
Use Different Guarantees for Different Decisions
A like animation can respond immediately to the user's successful action while a public aggregate count catches up later. The membership fact, "this user has liked this post", can be authoritative even when the displayed total is eventually updated. Keeping those concepts separate avoids making a hot counter part of every correctness-critical write.
A private-content decision deserves a stronger path. Suppose Alice removes Bob as a follower while Bob's feed still contains one of Alice's post identifiers. The feed entry should remain only a candidate. Before returning protected content, the serving path must apply the current access policy through a source and cache strategy capable of meeting the promised revocation behaviour.
Define that promise precisely. If the product requires revocation to apply to requests beginning after a successful removal, a stale relationship cache cannot satisfy it without an appropriate invalidation, version or authoritative check. A database brand alone does not determine the result.
Failure behaviour matters as much as freshness. If the authorisation source is unavailable, decide how the service handles private content. Returning cached private data merely because the feed can still be read may violate the intended guarantee. Public content can have a different availability policy.
Handle Races at the Source of Truth
Two simultaneous likes should resolve to one active relationship through a uniqueness or conditional-write mechanism. A count derived from accepted relationship changes can then be rebuilt if an update is missed. Incrementing a counter for every incoming HTTP attempt counts retries rather than likes.
A follow and a block can race too. Define which state transition wins and where it is enforced. For example, the transaction accepting a follow may need to check a current block relationship, with an appropriate concurrency mechanism. A background feed worker should also apply block rules when delivering content so a delayed event cannot override a newer restriction.
Post edits and deletions benefit from versions. A feed projection receiving version 4 after version 5 should not overwrite the newer state. A deletion tombstone can prevent delayed updates from recreating visible content. These rules are useful whether the projection is another SQL table or a separate document store.
Transactions provide atomicity within a supported boundary; they do not make every cache and search index immediately current. Draw the path from authoritative write to each derived view and state its expected delay. Users and operators should be able to tell when a view is behind without treating the authoritative action as lost.
Model a Feed Without Choosing Everything Twice
For an early version, generating the feed from indexed posts and follow relationships may be enough. Measure the real query plan and request latency before introducing another database.
If feed construction becomes the bottleneck, prepare feed entries asynchronously when authors publish. Keep the authoritative post separate and store lightweight references in each recipient's feed. A popular author may justify generating some entries on read to avoid copying every post to a very large audience.
This is a design choice, not a rule that SQL handles accounts and NoSQL handles feeds. A relational table can hold prepared feed entries; a document database can store account records with appropriate constraints. Pick a second system only when its benefits justify its operational cost.
Start with a Measurable Read Path
An early feed can find followed authors, retrieve their eligible recent posts and return an ordered page. Keep the candidate window bounded and examine the actual query plan with realistic follow counts. A test account following three people does not reveal what happens when an active user follows several thousand.
Use stable ordering with an identifier to break ties, and make cursor semantics clear. Ranking complicates pagination because a score can change between requests. A snapshot or ranking-session identifier can provide a stable sequence if the product needs it; otherwise document the feed as a live view that may change.
Set a concrete improvement trigger. For example, if representative feed requests exceed the agreed latency objective because candidate construction dominates database work, prepared entries may be justified. The exact threshold belongs to the product's requirements and measured environment, not a general rule about user count.
Introduce Prepared Entries with Clear Ownership
On publication, commit the post and an outbox event together. A worker reads the author's followers in bounded pages and inserts feed references. Give entries an identity such as (recipientId, postId) so repeated events do not create duplicates. Preserve enough progress to resume a large fan-out after a worker crashes.
The feed store remains derived. If it loses data, rebuild entries from authoritative posts and relationships, subject to the intended historical semantics. A rebuild should not automatically deliver every old post to every current follower unless that is the desired product behaviour.
Hydrate returned candidates in batches rather than issuing a separate database call for each post. Apply visibility, moderation and deletion checks before responding. If filtering removes many entries, fetch more candidates within a bounded work budget so the request cannot scan an unlimited feed trying to fill a page.
For a very popular author, writing one entry per follower for every post can be inefficient. A hybrid design can merge that author's recent posts at read time while ordinary authors use fan-out. This reduces extreme write amplification at the cost of a more complex merge and pagination path.
Avoid Unnecessary Duplication Between Stores
If a relational feed table already meets the latency and throughput needs, keep it until another system offers a concrete benefit. Moving it introduces change propagation, a new backup process and another operational dependency. The benefit might be predictable partitioned access or independently scalable capacity, but it should be measured.
If a document database is already the authoritative store and supports the required account constraints, there is no automatic need to add SQL for accounts. The same principle applies: evaluate the actual rules, queries and team capabilities, not an architectural slogan.
Search can be a justified separate view when full-text retrieval and relevance requirements exceed the primary store's appropriate capabilities. It should still point to authoritative content, respect deletion and visibility, and support rebuilds. Search results are candidates, just as feed entries are.
Include Failure and Maintenance Costs
Whenever data appears in two stores, decide how updates reach both reliably. A durable event or transactional outbox can drive feed changes after the authoritative record commits. Consumers need retry handling and duplicate protection.
Monitor delay, support rebuilding derived views, and define deletion propagation. A restored backup should not silently reintroduce content that users deleted later.
Compare backup recovery, schema changes, indexing, capacity planning, and the team's familiarity alongside read latency. A benchmark for a single query says little about the cost of operating the complete product.
Follow a Write Through Failure
Suppose a post commits but the feed system is unavailable. A durable event should remain pending, the author should still be able to retrieve the authoritative post, and the interface should not claim every follower has received it. When the feed service recovers, duplicate-safe workers resume the backlog.
Now suppose a worker writes feed entries and crashes before acknowledging the event. On retry, unique entry identities prevent duplicates. For a large follower list, checkpointing must avoid skipping recipients; repeating a bounded page is often easier to make safe than guessing how far the previous attempt reached.
A post deletion creates a more sensitive sequence. First make the authoritative content unavailable according to the access policy, then propagate removal to feeds, caches, search and media delivery as required. Removing only feed entries is insufficient because somebody may retain a direct post URL.
Keep deletion work discoverable and repeatable. A queue event can be lost through an implementation mistake, a consumer can remain offline, or a projection can be restored from an old backup. Reconciliation against authoritative deletion state helps find remnants that ordinary event delivery did not remove.
Plan Restoration Across Authoritative and Derived Data
Define recovery point and recovery time objectives for each store. A derived feed can sometimes be rebuilt instead of restored, but the rebuild may take hours and require spare capacity. The authoritative post and relationship data need a recovery strategy that supports that reconstruction.
Restoring different stores to unrelated points in time can produce contradictions. A restored search index may contain a post that the primary database deleted later. Serving checks and deletion reconciliation must prevent stale copies becoming valid content merely because they reappeared in a backup.
Schema evolution also spans stores. Add event fields compatibly, retain readers for old versions and deploy consumers before requiring new data. An event created before a deployment can arrive afterwards during backlog recovery. Schemaless storage does not make that compatibility problem disappear.
Compare the Full Operating Cost
Include storage, indexes, replicas, backups, cross-region transfers, request charges and background processing in the estimate. Denormalisation can increase both stored bytes and write volume. A query that appears cheap in isolation may trigger many additional reads to assemble a complete response.
Include human operating cost as well. The team needs to understand query plans, partition balance, recovery procedures and access controls for every system it owns. Familiarity is not the only selection criterion, but a small theoretical latency advantage can be outweighed by a database nobody can confidently restore during an incident.
Use realistic benchmarks with uneven popularity, large accounts and concurrent maintenance work. Measure end-to-end feature latency and correctness under failures, not only database calls under ideal conditions. Keep an exit path: exportable authoritative records and rebuildable projections make future changes less risky.
A Practical Starting Architecture
For the hypothetical application, a reasonable first design is one relational database for accounts, relationships, posts and the initial feed, object storage for media, and a cache only where repeated reads justify it. Add an outbox when committed changes must reliably drive asynchronous work.
Introduce a prepared feed when measured construction cost warrants it. Keep that view in the existing database if it meets the requirements, or choose a separate store after evaluating its access patterns, consistency and operating cost. Add search or analytical storage for those specific workloads when needed.
This is a starting decision under the stated assumptions, not a universal ranking of databases. A team with established document infrastructure and well-defined partitioned queries may choose differently. The quality of the decision lies in explaining the guarantees, failure recovery and growth path with the same precision as the happy-path query.
Summary
Choose a database around the application's queries and correctness requirements. Relational systems are a practical starting point for connected data and enforceable rules; document and key-value models can suit particular content and retrieval patterns.
Keep authoritative records clear, treat media storage separately, and introduce prepared views when measurements justify them. The strongest design is one the team can explain, recover, and evolve as the social application's workload changes.
