템플릿 둘러보기/인스타그램 (Instagram)

인스타그램 (Instagram)

사용자 프로필, 다중 미디어가 포함된 사진 및 동영상 게시물, 시청자 추적이 가능한 인스타그램 스토리, 릴스 숏폼 동영상, 댓글 스레드, 좋아요, 해시태그 검색, 팔로우 관계, 친한 친구 목록, 스레드 기반 다이렉트 메시지 및 하이라이트 컬렉션을 특징으로 하는 인스타그램 스타일의 시각적 소셜 플랫폼용 전체 데이터베이스 스키마입니다.

PostgreSQL17개 테이블소셜
photosocialstoriesreelsinfluencer
ERD Studio로 제작

이 스키마에 대하여

이 스키마는 계정을 users 와 profiles 로 나눕니다. users 는 들여보내는 데 필요한 것 — 자격 증명, 전화번호, is_private — 을 담고, profiles 는 남에게 보이는 것과 팔로워·팔로잉·게시물 수를 담습니다. 수를 세지 않고 저장하는 이유는 프로필을 볼 때마다 읽히지만 바뀌는 일은 훨씬 드물기 때문입니다.

콘텐츠는 한 테이블이 아닙니다. posts 는 그리드, stories 는 하루 뒤 만료, reels 는 posts 의 플래그가 아니라 자기 테이블입니다. 공유하는 것이 거의 없어서 나눴습니다 — 스토리에는 만료와 시청자 목록이 있고, 릴스에는 재생 수가 있고, 게시물에는 캐러셀과 위치가 있습니다.

팔로우 그래프는 is_pending 을 가진 방향성 테이블 하나입니다. 공개 계정을 팔로우하면 승인된 상태로 생기고, 비공개 계정이면 승인 전까지 대기 상태로 남습니다. 둘이 같은 행이라 요청과 관계가 어긋날 일이 없습니다.

눈여겨볼 관계

users → profiles
각각 한 행씩, 누가 읽느냐로 나눴습니다. 로그인은 users 를, 프로필 조회는 profiles 를 건드립니다. 팔로우가 인증에 쓰이는 행에 쓰기를 일으키지 않습니다.
posts → post_media
캐러셀은 JSON 배열이 아니라 position 으로 정렬되는 여러 행입니다. 각 항목이 자기 width·height·duration 을 들고 있어서, 파일이 내려오기 전에 클라이언트가 자리를 잡을 수 있습니다.
follows → users (두 번)
follower_id 와 following_id 가 모두 users 를 가리켜 행에 방향이 있습니다. 팔로워 목록과 팔로잉 목록은 같은 테이블을 양쪽에서 읽는 것입니다.
stories → highlight_items
하이라이트는 스토리를 복사하지 않고 가리킵니다. 그래서 24시간이 지나도 남습니다 — expires_at 은 피드에 보일지를 정하지 행의 수명을 정하지 않습니다.
comments → comments
parent_id 로 답글을 자기참조로 풉니다. 최상위 댓글은 null 이고, 스레드 하나는 재귀 CTE 한 번으로 읽힙니다.

설계 판단

릴스를 게시물과 따로 둔다

합쳐도 될 만큼 닮았습니다 — 둘 다 캡션과 좋아요 수가 붙은 사용자의 미디어입니다. 그런데 릴스에는 재생 수가 있고 세로 비율이 고정이며 캐러셀도 위치도 없습니다. 게시물은 정반대입니다. 합치면 타입 플래그에 따라 절반이 null 인 테이블이 되고, 모든 피드 질의가 그 플래그를 걸러야 합니다. 나눠 두면 둘을 함께 보여 주는 드문 화면에서만 union 이 듭니다.

수는 세지 않고 저장한다

follower_count, post_count, like_count, comment_count 는 전부 계산할 수 있는 값입니다. 저장하는 이유는 프로필 한 장이 넷을 읽고 하나도 쓰지 않기 때문입니다. 수백만 행짜리 팔로우 테이블을 세는 질의를 화면 뜰 때마다 돌릴 수는 없습니다. 대가는 카운터와 실제 행이 어긋나는 것이고, 주기적으로 맞춰 주는 편이 매번 정확한 것보다 쌉니다.

스토리 조회는 숫자가 아니라 행

story_views 는 몇 명이 봤는지가 아니라 누가 봤는지를 적습니다. 주인에게 시청자 목록을 보여 주기 때문입니다. 카운터였다면 다른 질문에 답하는 셈이고, 이 테이블은 스토리가 만료되면 같이 지울 수 있어서 무한히 자라지도 않습니다.

대기 중인 팔로우도 follows 에 산다

비공개 계정 팔로우 요청을 따로 테이블로 두고 승인 시 옮길 수도 있습니다. is_pending 으로 두면 승인이 삭제+삽입이 아니라 갱신 한 번이고, 거절은 어느 쪽이든 삭제입니다. "이 사람이 나를 팔로우하나" 도 두 곳을 보지 않고 한 번에 끝납니다.

이 템플릿의 테이블

users사용자
9 cols
profiles프로필
11 cols
posts게시물
9 cols
post_media게시물 미디어
9 cols
hashtags해시태그
4 cols
post_hashtags게시물 해시태그 연결
4 cols
follows팔로우
5 cols
stories스토리
8 cols
story_views스토리 시청 기록
4 cols
highlights하이라이트
6 cols
highlight_items하이라이트 항목
5 cols
comments댓글
8 cols
post_likes게시물 좋아요
4 cols
dm_threadsDM 스레드
5 cols
dm_participantsDM 참여자
5 cols
direct_messages다이렉트 메시지
8 cols
reels릴스
10 cols

자주 묻는 것

users 와 profiles 를 왜 나눴나요?
읽는 주체가 다르기 때문입니다. 로그인은 자격 증명 행만 있으면 되고, 프로필 조회는 표시용 필드와 카운터가 필요합니다. 나눠 두면 follower_count 를 올리는 팔로우가 인증이 읽는 행을 건드리지 않고, 자주 읽히는 프로필 행도 좁게 유지됩니다.
릴스를 게시물에 합쳐도 되나요?
됩니다. 대신 타입 컬럼이 생기고 한쪽에서는 늘 null 인 컬럼들이 남습니다. 릴스가 그냥 세로 게시물이라면 합칠 만합니다. 릴스에 고유 지표나 고유 피드 랭킹, 고유 검수 규칙이 생기는 순간 합친 대가가 커집니다 — 대개 그 방향으로 갑니다.
스토리는 24시간 뒤에 어떻게 지워지나요?
expires_at 은 피드에서 내려갈 시각을 말할 뿐 스키마가 지우지는 않습니다. 하이라이트가 가리키고 있고 주인은 자기 보관함에서 계속 볼 수 있어서 대개 한동안 남겨 둡니다. 어느 하이라이트도 가리키지 않는 오래된 행만 작업이 정리합니다.
follows 에 유니크 제약이 없는데 괜찮나요?
있어야 합니다. (follower_id, following_id) 에 걸어야 두 번 눌렀을 때 팔로우가 둘 생기고 카운터가 두 배가 되는 일을 막습니다. 카운터가 운영에 올라가기 전에 넣으세요 — 나중에 맞추려면 모든 프로필을 다시 세야 합니다.
피드는 어디서 만드나요?
여기가 아닙니다. 이 스키마는 무엇이 올라왔고 누가 누구를 팔로우하는지를 담고, 피드는 그 둘의 조인에 랭킹을 얹은 것이라 규모가 커지면 바깥에서 미리 만듭니다. posts 와 follows 를 조인하는 당겨오기 방식은 이 구조로 바로 됩니다 — 팔로워가 아주 많은 계정이 생기기 전까지는요.