Overview
The Social Media Activity Feed API uses a relational database schema designed to model social graphs, content interactions, and activity feeds efficiently. The schema leverages composite primary keys, self-referencing relationships, and strategic indexing for optimal performance.Entity Relationship Diagram
The complete database schema is documented in the ERD diagram:
Reference: Social-Media-Activity-Feed-ERD.drawio.png in the source repository
Composite Primary Keys
One of the key architectural decisions is using composite primary keys (CPKs) for relationship and interaction tables.Why Composite Keys?
From the README:- Prevents duplicates at the database level: No “double follow” or “double like” rows possible
- Supports efficient existence checks: Database can quickly verify if a relationship exists
- Simplifies application logic: The database enforces the invariant, reducing edge cases
Examples from the Codebase
Follow Table (User.cs:84)
- User A can only follow User B once
- Lookups like “Does A follow B?” use the primary key index
- Deletes like “A unfollows B” are efficient key-based operations
BlockedAccount Table (User.cs:95)
CloseFriend Table (User.cs:106)
SavedPost Table (Post.cs:30)
PostLike Table (Post.cs:64)
Self-Referencing Relationships
The social graph uses self-referencing many-to-many relationships where users relate to other users.User Model (User.cs:7)
User-to-User Relationships
- Follow: Bidirectional follower/following graph
- Block: Users can block accounts that block them
- Close Friends: Subset of followed users with special access
Indexing Strategy
Strategic indexes optimize the most frequent query patterns.User Table Indexes
Unique Index on UserName (User.cs:6)
- Enforces username uniqueness
- Speeds up login queries:
WHERE UserName = @username - Supports profile lookups by username
Post Table Indexes
Index on InitiatorID (Post.cs:5)
- Speeds up queries filtering posts by user(s)
- Critical for feed assembly:
WHERE InitiatorID IN (followed user IDs) - Supports user profile post lists
Reverse Indexes on Relationship Tables
From the README:ForFollow,BlockedAccount, andCloseFriend, an additional index exists in the reverse direction of the composite PK.
Why Reverse Indexes?
Composite primary keys optimize lookups by the PK’s leading column order. For example, a PK on(FollowerID, FollowedUserID) efficiently answers:
- “Who does User A follow?” →
WHERE FollowerID = A
- “Who follows User B?” →
WHERE FollowedUserID = B
(FollowedUserID, FollowerID) optimizes the second pattern.
Configuration in DbContext:
Additional Indexes
SavedPost (Post.cs:30)
- PK
(SaverID, PostID): “What posts did User A save?” - Index
(PostID, SaverID): “Who saved Post B?”
Notification (Interactions.cs:53)
- Index on
(ReceivingUserID, InitaiatorID): User’s notification feed - Index on
NotificationType: Filter by notification type
Tables Overview
Core Tables
Users
Stores user account information, authentication credentials, and profile metadata. Key Fields:UserID(PK): Primary identifierUserName(Unique): Login usernamePasswordHash: Hashed password (PBKDF2)FollowersCount,FollowingCount: Denormalized countersAccountDeleted,DeletedAt: Soft delete support
UserProfile
One-to-one extension of User for settings and preferences. Key Fields:UserID(PK, FK): References UserPushNotifications,AccountPrivacy,Verified: Boolean settingsWebsite,Gender: Optional profile fields
Posts
User-generated content items. Key Fields:PostID(PK): Primary identifierInitiatorID(FK, Indexed): Creator user IDCaption: Post text content (max 800 chars)CreatedAt: Timestamp for sorting/paginationLikeCount: Denormalized counterisDeleted,DeletedAt: Soft delete support
Relationship Tables (Composite PKs)
Follow
(FollowerID, FollowedUserID) - User follows another user
BlockedAccount
(BlockingUserID, BlockedAccountId) - User blocks another user
CloseFriend
(AddingUserID, CloseFriendAccountId) - User adds another as close friend
Interaction Tables (Composite PKs)
PostLike
(LikerID, PostID) - User likes a post
- Includes
CreatedAtfor activity timeline
SavedPost
(SaverID, PostID) - User saves a post for later
Comment
User comments on a post (uses auto-increment PK)commentID(PK): Unique identifierPostID(FK, Indexed): Post being commented onCommenterID(FK): User who commentedCommentText: Comment content (max 1000 chars)LikeCount: Comments can be likedisDeleted,DeletedAt: Soft delete support
Supporting Tables
PostMedia
Media attachments for posts (images/videos)PostMediaID(PK)PostID(FK, Indexed)MediaType: Enum (Image, Video)MediaURL: Link to media resource
Notification
Activity notifications for usersNotificationID(PK)ReceivingUserID(FK, Indexed)InitaiatorID(FK)NotificationType: Enum (Follow, Like, Comment)CreatedAt: Timestamp
FeedContent
Materialized view of feed items (fan-out on write pattern)UserID(PK)FollowerID,FollowedUserID: Denormalized relationshipPostID(FK): Post in feed
Message
Direct messages between usersMessageID(PK)SenderID,ReceiptientID(FKs)MessageContent: Message textCreatedAt: Timestamp
Design Patterns
Soft Deletes
User and Post entities support soft deletion:- Preserve data for audit/analytics
- Support “restore” functionality
- Maintain referential integrity
Denormalized Counters
Frequently accessed counts are stored directly:- Pro: Fast reads (no
COUNT(*)queries) - Con: Must maintain consistency during updates
- Solution: Use transactions for atomic counter updates (see post.action.cs:186)
Inverse Properties
EF Core navigation properties clarify bidirectional relationships:Future Considerations
From the README:SQLite to PostgreSQL Migration
Current: SQLite for local development simplicity Future: PostgreSQL for production- Better indexing strategies (partial indexes, expression indexes)
- Advanced concurrency control (MVCC)
- Superior query planner
- Native JSON support for polymorphic data
Feed Scaling
Current: Fan-in on read (query followed users’ posts at request time) Future: Hybrid approach- Fan-out on write for active users (materialize to FeedContent table)
- Fan-in on read for less active users
- Ranking algorithms for feed relevance
- Filtering by close-friends-only posts