Instagram schema design을 진행한 과정 + 과정 중의 고민들을 정리해보았다.
하나의 user는 여러개의 post를 작성 할 수 있다.
하나의 post는 하나의 유저만 작성할 수 있다.
하나의 post에 여러개의 media(사진 또는 영상)를 올릴 수 있다.
(regram 가능할 경우) 하나의 media는 여러개의 post에 올라갈 수 있다.
post : media = 1 : N
하나의 post에 여러개의 media(사진 또는 영상)를 올릴 수 있다.
(regram 불가능할 경우) 하나의 media는 하나의 post에만 올라갈 수 있다.
하나의 post는 하나의 textcontent를 작성할 수 있다.
하나의 textcontent는 하나의 post에만 사용될 수 있다.
하나의 post는 여러개의 comment가 달릴 수 있다.
하나의 comment는 하나의 post에만 달 수 있다.
하나의 post는 여러개의 hashtag를 가질 수 있다.
하나의 hashtag는 여러개의 post에 달릴 수 있다.
하나의 post는 여러명의 tagged_user를 가질 수 있다.
하나의 tagged_user는 여러개의 post에 태그될 수 있다.
하나의 user는 여러개의(여러 포스트에) like를 할 수 있다.
하나의 like는 하나의 user(like를 한 주체)만 가질 수 있다.
하나의 post(또는 comment)는 여러개의 like를 받을 수 있다.
하나의 like는 하나의 post(또는 comment)에만 보내질 수 있다.
하나의 user는 여러명을 following 할 수 있다.
하나의 following(된 대상)은 여러 user로부터 팔로잉 받을 수 있다.
하나의 user는 여러명을 block 할 수 있다.
하나의 block(대상)은 여러 user로부터 block 당할 수 있다.
dbdiagram 프로그램을 이용해 스키마를 작성해보았다.
하나의 post는 여러개의 media를 가질 수 있다는건 알겠는데, 하나의 media가 여러개의 post에 올라갈 수 있는가에 대한 주제로 고민이 많았다.
처음에는 하나의 media는 하나의 post에만 올라갈 수 있다고 생각했다가 인스타그램의 리그램 기능이 떠올랐다. 리그램(인용하기) 기능을 고려한다면 하나의 media는 여러개의 post에 올라갈 수 있다. 그렇게 스키마 작성까지 마쳤다가 왠지 찝찝한 부분을 버릴 수 없어서 인스타그램의 리그램 기능에 대해 검색해보았다.
자세히 찾아보니 인스타그램 자체에서 리그램 기능을 지원하는 것이 아닌 외부 어플을 통해 Repost 기능을 적용하는 것을 사용자들 사이에서 Regram(repost + instagram)이라고 부르는 거였다. 리그램을 도와주는 어플들은 다양한데 전체 로직은 모두 같았다.
그냥 해당 포스트 정보를 긁어와서 다시 올리는데 포스트 작성자 정보를 반드시 포함시키게 해놓는 식으로 리그램을 구현한거다. 그렇다면 인스타그램의 데이터베이스에서 리그램 된 포스트는 전혀 별개의 포스트로 인식하는 거라고 생각했다. 원래의 인스타그램을 그대로 따라갈까 하다가 인스타그램의 데이터베이스에서 리그램이 가능하도록 설계해서 해당 기능을 인스타그램 자체의 기능으로 제공하는게 더 편안한 사용자 경험을 제공할 수 있을거라고 생각해서 하나의 media가 여러개의 post에 올라갈 수 있도록 설계했다. 대신 media 정보 자체에 해당 media를 처음 업로드한 user 정보를 creator_id로 같이 저장해서 저작권이 보호되도록 구성했다.
사실 likes가 처음 만난 고비였다.
하나의 좋아요는 하나의 post(또는 comment)에 적용될 수 있다.
하나의 post(또는 comment)는 여러개의 좋아요를 받을 수 있다.
고려해야 하는 부분은 2가지 였다.
likes가 적용되는 대상을 처리하기 위해 likes 테이블에 target_id field와 target_type field를 추가해서 target_type (0: post, 1: comment) 에 따라 target_id를 참조해 데이터를 얻어올 수 있게 구성했다.
likes는 주체인 user와 N:1 관계를 갖는다. 이 부분은 likes 테이블에 user_id라는 참조키를 주어서 구성했다.
user와 follower, following의 관계에 대한 부분이 가장 어려웠다.
하나의 user는 여러 명을 following할 수 있다.
그렇다면 following의 입장에서는? following과 follower와 user가 뒤섞여서 쉽게 정리가 되지 않아서 생각을 바꾸어봤다.
어차피 인스타그램을 사용하는 어떤 user가 following하는 것은 그 사람이 올리는 post들을 구독하겠다는 의미와 같다. 그래서 user를 구독자로 following을 채널로 치환해서 생각해보면 쉽게 답이 나온다.
하나의 구독자는 여러 채널을 구독할 수 있다.
하나의 채널은 여러 구독자를 가질 수 있다.
이제 구독자를 user로, 채널 구독을 following으로 다시 치환해주자.
하나의 user는 여러 following을 할 수 있다.
하나의 following은 여러 user를 가질 수 있다.
이렇게되면 user : following = N : M 의 관계임을 알 수 있다.
follower 가 아닌 following을 사용한 이유는 user의 피드에 바로바로 데이터를 보내줘야 하는 부분은 user의 follower 가 아닌 following들이 올린 post들이기 때문에 user에 직접적으로 연결되어 있는 데이터를 following으로 설정하는 것이 더 나을 것이라 판단했기 때문이다.
인스타그램의 차단기능인 block도 일단 following 구조와 동일하게 짜놓긴 했는데 더 효율적인 구조가 있을 것 같은데 어떻게 처리해야 할 지 쉽게 명쾌한 답이 나오질 않는다. 언제든 더 나은 해답을 찾게되면 업데이트하러 와야지. ㅇㅅㅇ.
// dbdiagram
Table users {
id int [pk, increment] // auto-increment
name varchar
email varchar
password password
created_at timestamp [default: `now()`]
}
Table follow {
user_id int
following_id int
}
Table block {
user_id int
block_id int
}
Table posts {
id int [pk, increment]
user_id int
textcontent varchar
created_at timestamp [default: `now()`]
}
Table medias {
id int [pk, increment]
creator_id int
content_type int // 0:image, 1:video
content_url varchar
}
Table post_media {
media_id int
post_id int
}
Table comments {
id int [pk, increment]
post_id int
user_id int
content varchar
created_at timestamp
}
Table tagged_user {
target_id int
target_type int // 0: post(textcontent), 1: comment, 2: media
taggedUser_id int
}
Table likes {
id int [pk, increment]
target_id int
target_type int // 0: post, 1: comment
user_id int [not null] // user - click likes btn
created_at timestamp [default: `now()`]
}
Table post_hashtag {
post_id int
hashtag_id int
}
Table comment_hashtag {
comment_id int
hashtag_id int
}
Table hashtags {
id int [pk, increment]
hashtag varchar
}
Ref: posts.user_id > users.id
Ref: "users"."id" < "likes"."user_id"
Ref: "posts"."id" < "post_hashtag"."post_id"
Ref: "hashtags"."id" < "post_hashtag"."hashtag_id"
Ref: "hashtags"."id" < "comment_hashtag"."hashtag_id"
Ref: "posts"."id" < "comments"."post_id"
Ref: "users"."id" < "comments"."user_id"
Ref: "comments"."id" < "likes"."target_id"
Ref: "posts"."id" < "likes"."target_id"
Ref: "comments"."id" < "comment_hashtag"."comment_id"
Ref: "post_media"."post_id" < "posts"."id"
Ref: "post_media"."media_id" < "medias"."id"
Ref: "users"."id" < "medias"."creator_id"
Ref: "tagged_user"."taggedUser_id" < "users"."id"
Ref: "tagged_user"."target_id" < "medias"."id"
Ref: "tagged_user"."target_id" < "comments"."id"
Ref: "tagged_user"."target_id" < "posts"."id"
Ref: "users"."id" < "follow"."user_id"
Ref: "users"."id" < "follow"."following_id"
Ref: "block"."user_id" < "users"."id"
Ref: "block"."block_id" < "users"."id"