소셜 네트워크 그래프 스키마 설계 Designing a Social Network Graph Schema
Designing a Social Network Graph Schema
Before writing any code, the graph schema needs careful thought. A social network is fundamentally about connections—users connect to other users, create content, and interact with that content. This maps naturally to a graph structure.
1. Core Entities
A minimal social network needs three node types:
@Node
public class User {
@Id
@GeneratedValue
private Long id;
private String username;
private String email;
private String displayName;
private String bio;
private UserStatus status;
private LocalDateTime createdAt;
public enum UserStatus {
ACTIVE, SUSPENDED, DELETED
}
}
@Node
public class Post {
@Id
@GeneratedValue
private Long id;
private String content;
private Visibility visibility;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public enum Visibility {
PUBLIC, FRIENDS_ONLY, PRIVATE
}
}
The third entity—relationships between users—is where graph databases differ from relational ones. Instead of a join table, relationships are first-class citizens.
2. Relationship Modeling
Social networks have two primary relationship patterns: symmetric (friendships) and asymmetric (follows).
Asymmetric: Following
@Node
public class User {
// ...
@Relationship(type = "FOLLOWS", direction = Direction.OUTGOING)
private Set<User> following = new HashSet<>();
@Relationship(type = "FOLLOWS", direction = Direction.INCOMING)
private Set<User> followers = new HashSet<>();
}
When Alice follows Bob, there’s a single FOLLOWS edge from Alice to Bob. The direction matters—Alice can see Bob’s posts, but Bob doesn’t automatically see Alice’s.
Symmetric: Friendship
Friendships are trickier. Two options exist:
Option A: Two directed edges (simpler queries, more storage)
(alice)-[:FRIENDS_WITH]->(bob)
(bob)-[:FRIENDS_WITH]->(alice)
Option B: One undirected edge with status (less storage, slightly complex queries)
(alice)-[:FRIENDS_WITH {status: 'ACCEPTED', since: datetime()}]->(bob)
Option B is more common in production. The relationship properties track state:
@RelationshipProperties
public class Friendship {
@Id
@GeneratedValue
private Long id;
@TargetNode
private User friend;
private FriendshipStatus status;
private LocalDateTime since;
private LocalDateTime requestedAt;
public enum FriendshipStatus {
PENDING, ACCEPTED, REJECTED, BLOCKED
}
}
3. Content Relationships
Users create content. This is a simple outgoing relationship:
@Node
public class User {
// ...
@Relationship(type = "AUTHORED", direction = Direction.OUTGOING)
private List<Post> posts = new ArrayList<>();
}
For interactions like likes, the design choice is whether to use a relationship or a node.
Relationship approach (simpler, recommended for basic use):
(user)-[:LIKED {at: datetime()}]->(post)
Node approach (when likes need their own properties or querying):
(user)-[:CREATED]->(like:Like)-[:ON]->(post)
For most social networks, the relationship approach suffices. Reserve nodes for complex interactions that need their own lifecycle.
4. Indexing Strategy
Without indexes, Neo4j scans all nodes. For a social network, these indexes are essential:
@Node
public class User {
@Id
@GeneratedValue
private Long id; // Auto-indexed
// Add unique constraints via schema
}
Create indexes in Neo4j:
CREATE CONSTRAINT user_username IF NOT EXISTS
FOR (u:User) REQUIRE u.username IS UNIQUE;
CREATE CONSTRAINT user_email IF NOT EXISTS
FOR (u:User) REQUIRE u.email IS UNIQUE;
CREATE INDEX user_status IF NOT EXISTS
FOR (u:User) ON (u.status);
CREATE INDEX post_created IF NOT EXISTS
FOR (p:Post) ON (p.createdAt);
CREATE INDEX post_visibility IF NOT EXISTS
FOR (p:Post) ON (p.visibility);
Unique constraints automatically create indexes. Composite indexes help for common query patterns:
CREATE INDEX post_visibility_created IF NOT EXISTS
FOR (p:Post) ON (p.visibility, p.createdAt);
5. Spring Data Neo4j Configuration
Configure the entity scan and transaction management:
@Configuration
@EnableNeo4jRepositories(basePackages = "com.example.social.repository")
@EnableTransactionManagement
public class Neo4jConfig {
@Bean
public Neo4jTransactionManager transactionManager(Driver driver,
DatabaseSelectionProvider databaseSelection) {
return new Neo4jTransactionManager(driver, databaseSelection);
}
}
6. Repository Layer
Repositories extend Neo4jRepository with custom queries:
public interface UserRepository extends Neo4jRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
@Query("""
MATCH (u:User {username: $username})
OPTIONAL MATCH (u)-[:AUTHORED]->(p:Post)
OPTIONAL MATCH (u)-[:FOLLOWS]->(following:User)
OPTIONAL MATCH (u)<-[:FOLLOWS]-(follower:User)
RETURN u,
count(DISTINCT p) as postCount,
count(DISTINCT following) as followingCount,
count(DISTINCT follower) as followerCount
""")
UserStats findUserWithStats(String username);
}
public interface PostRepository extends Neo4jRepository<Post, Long> {
@Query("""
MATCH (author:User {username: $username})-[:AUTHORED]->(p:Post)
RETURN p, author
ORDER BY p.createdAt DESC
SKIP $skip LIMIT $limit
""")
List<Post> findByAuthor(String username, int skip, int limit);
}
7. Schema Visualization
The complete schema looks like this:
┌──────────┐
│ User │
└────┬─────┘
│
┌────────┼────────┬──────────┐
│ │ │ │
▼ ▼ ▼ ▼
FOLLOWS FRIENDS AUTHORED LIKED
│ WITH │ │
│ │ ▼ │
│ │ ┌──────┐ │
└────────┴───│ Post │◄──────┘
└──────┘
This schema handles the core social network features. Additional nodes like Comment, Interest, or Location can be added as needed, following the same patterns.
8. Conclusion
Graph schema design for social networks centers on modeling relationships correctly. Use directed edges for asymmetric relationships (follows), and relationship properties for symmetric ones with state (friendships). Index heavily on fields used in WHERE clauses and ORDER BY. The schema established here forms the foundation for implementing friend requests, feeds, and recommendations in the following posts.
댓글남기기