3 분 소요


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.

댓글남기기