Designing a Social Network Graph Schema
코드를 작성하기 전에 그래프 스키마를 신중하게 설계해야 한다. 소셜 네트워크는 근본적으로 연결에 관한 것이다—사용자가 다른 사용자와 연결되고, 콘텐츠를 만들고, 그 콘텐츠와 상호작용한다. 이것은 그래프 구조에 자연스럽게 매핑된다.
Before writing code, you need to carefully design your graph schema. Social networks are fundamentally about connections—users connecting with other users, creating content, and interacting with that content. This maps naturally to a graph structure.
1. 핵심 엔티티
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
}
}
세 번째 엔티티인 사용자 간의 관계가 그래프 데이터베이스와 관계형 데이터베이스의 차이점이다. 조인 테이블 대신 관계가 일급 시민(first-class citizen)이다.
The third entity—relationships between users—is where graph databases differ from relational ones. Instead of join tables, relationships are first-class citizens.
2. 관계 모델링
2. Modeling Relationships
소셜 네트워크에는 두 가지 주요 관계 패턴이 있다: 대칭(친구)과 비대칭(팔로우).
Social networks have two main relationship patterns: symmetric (friends) and asymmetric (follows).
비대칭: 팔로우
Asymmetric: Follows
@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<>();
}
앨리스가 밥을 팔로우하면 앨리스에서 밥으로 향하는 단일 FOLLOWS 엣지가 생긴다. 방향이 중요하다—앨리스는 밥의 게시물을 볼 수 있지만, 밥은 자동으로 앨리스의 게시물을 보지 못한다.
When Alice follows Bob, a single FOLLOWS edge points from Alice to Bob. Direction matters—Alice can see Bob's posts, but Bob doesn't automatically see Alice's.
대칭: 친구
Symmetric: Friends
친구 관계는 더 복잡하다. 두 가지 옵션이 있다:
Friendship is more complex. You have two options:
옵션 A: 두 개의 방향성 엣지 (쿼리가 간단, 저장 공간 더 필요)
Option A: Two directional edges (simpler queries, more storage)
(alice)-[:FRIENDS_WITH]->(bob)
(bob)-[:FRIENDS_WITH]->(alice)
옵션 B: 상태가 있는 하나의 무방향 엣지 (저장 공간 절약, 쿼리가 약간 복잡)
Option B: One undirected edge with status (saves storage, slightly complex queries)
(alice)-[:FRIENDS_WITH {status: 'ACCEPTED', since: datetime()}]->(bob)
옵션 B가 프로덕션에서 더 일반적이다. 관계 프로퍼티가 상태를 추적한다:
Option B is more common in production. 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. 콘텐츠 관계
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, you have to choose between using relationships or nodes.
관계 접근 방식 (더 단순, 기본 사용에 권장):
Relationship approach (simpler, recommended for basic use):
(user)-[:LIKED {at: datetime()}]->(post)
노드 접근 방식 (좋아요에 자체 프로퍼티나 쿼리가 필요할 때):
Node approach (when likes need their own properties or queries):
(user)-[:CREATED]->(like:Like)-[:ON]->(post)
대부분의 소셜 네트워크에서는 관계 접근 방식으로 충분하다. 자체 생명주기가 필요한 복잡한 상호작용에만 노드를 사용한다.
For most social networks, the relationship approach is sufficient. Only use nodes for complex interactions that need their own lifecycle.
4. 인덱싱 전략
4. Indexing Strategy
인덱스가 없으면 Neo4j는 모든 노드를 스캔한다. 소셜 네트워크에서 필수적인 인덱스:
Without indexes, Neo4j scans all nodes. Essential indexes for a social network:
@Node
public class User {
@Id
@GeneratedValue
private Long id; // 자동 인덱싱 / auto-indexed
// 스키마를 통해 유니크 제약 추가 / add unique constraints via schema
}
Neo4j에서 인덱스 생성:
Creating 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 with common query patterns:
CREATE INDEX post_visibility_created IF NOT EXISTS
FOR (p:Post) ON (p.visibility, p.createdAt);
5. Spring Data Neo4j 설정
5. Spring Data Neo4j Configuration
엔티티 스캔과 트랜잭션 관리를 설정한다:
Configure entity scanning 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 계층
6. Repository Layer
Repository는 Neo4jRepository를 확장하고 커스텀 쿼리를 추가한다:
Repositories extend Neo4jRepository and add 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. 스키마 시각화
7. Schema Visualization
전체 스키마는 다음과 같다:
The complete schema looks like this:
┌──────────┐
│ User │
└────┬─────┘
│
┌────────┼────────┬──────────┐
│ │ │ │
▼ ▼ ▼ ▼
FOLLOWS FRIENDS AUTHORED LIKED
│ WITH │ │
│ │ ▼ │
│ │ ┌──────┐ │
└────────┴───│ Post │◄──────┘
└──────┘
이 스키마는 핵심 소셜 네트워크 기능을 처리한다. Comment, Interest, Location 같은 추가 노드는 필요에 따라 동일한 패턴을 따라 추가할 수 있다.
This schema handles core social network functionality. Additional nodes like Comment, Interest, Location can be added following the same patterns as needed.
8. 결론
8. Conclusion
소셜 네트워크의 그래프 스키마 설계는 관계를 올바르게 모델링하는 것이 핵심이다. 비대칭 관계(팔로우)에는 방향성 엣지를 사용하고, 상태가 있는 대칭 관계(친구)에는 관계 프로퍼티를 사용한다. WHERE 절과 ORDER BY에서 사용되는 필드에 인덱스를 충분히 생성한다. 여기서 확립된 스키마는 다음 포스트에서 친구 요청, 피드, 추천을 구현하기 위한 기반이 된다.
Designing a graph schema for social networks is all about modeling relationships correctly. Use directional edges for asymmetric relationships (follows) and relationship properties for symmetric relationships with state (friends). Create sufficient indexes on fields used in WHERE clauses and ORDER BY. The schema established here serves as the foundation for implementing friend requests, feeds, and recommendations in the next post.