데이터베이스 설계 방법 : 2026 비개발자가 축제와 아티스트를 연결한 과정

비개발자의 데이터베이스 설계 방법, 축제와 아티스트 연결
비개발자의 바이브 코딩 도전기 5편 데이터베이스 설계

페스티봄을 만들기 시작했을 때 데이터베이스는 크게 고민하지 않았습니다. 축제 이름과 날짜, 장소를 저장한 뒤 화면에 불러오면 된다고 생각했습니다.

그런데 기능을 하나씩 붙이자 처음 생각한 구조로는 부족했습니다. 페스티벌 화면에 라인업을 보여주는 데서 끝내지 않고, 아티스트 상세 화면에서 다른 출연 페스티벌까지 보여주고 싶어졌기 때문입니다.

결국 축제 정보만 넣으려던 데이터베이스에 아티스트와 출연 정보가 따로 생겼습니다. 이번 글은 그 과정에서 제가 이해하게 된 데이터베이스 설계 방법과 SQL, JSON의 역할을 다룹니다.

데이터베이스 설계 방법, 정보를 먼저 나눠봤다

처음에는 데이터 구조라는 말부터 어렵게 느껴졌어요. 직접 만들면서 보니 결국 세 가지를 정하는 일이었습니다.

  • 어떤 정보를 한곳에 모을지
  • 무엇을 따로 나눌지
  • 나눈 정보끼리 어떻게 연결할지

페스티봄에 필요한 정보도 이 기준으로 나눠봤습니다.

  • 축제: 축제명, 개최 날짜, 장소
  • 아티스트: 이름, 이미지, 인스타그램 주소
  • 출연 정보: 어떤 축제에 누가 나오는지, 출연 날짜와 시간, 무대

축제 안에 아티스트 이름을 바로 넣는 방법도 떠올릴 수 있습니다. 다만 같은 아티스트가 여러 축제에 출연하면 이름과 사진을 축제마다 반복해서 저장해야 합니다. 사진을 바꿀 때마다 여러 축제를 찾아 수정하는 일도 생기겠죠.

그래서 축제와 아티스트를 별도 테이블로 분리했습니다. 두 테이블 사이에는 festival_artists라는 출연 정보 테이블을 두었습니다.

축제(festivals)
   ↓
출연 정보(festival_artists)
   ↓
아티스트(artists)

한 축제에는 여러 아티스트가 나오고, 한 아티스트도 여러 축제에 출연합니다. 데이터베이스에서는 이런 연결을 다대다 관계라고 부릅니다. 중간의 출연 정보 테이블이 양쪽을 이어주는 구조입니다.[1]

아티스트 테이블이 필요해진 이유. 데이터베이스 설계 과정

아티스트 테이블을 따로 만든 계기는 화면 하나였습니다.

아티스트를 눌렀을 때 이름과 사진만 보여주는 게 아니라, 그 아티스트가 어느 페스티벌에 출연하는지도 함께 보여주고 싶었어요. 이 화면을 만들려면 특정 아티스트를 기준으로 출연 정보를 찾은 다음 연결된 축제 정보를 가져와야 했습니다.

아티스트 1번
→ artist_id가 1인 출연 정보 찾기
→ 각 출연 정보와 연결된 축제 찾기

테이블이 늘어나니 처음에는 더 복잡해진 것처럼 보였습니다. 역할을 나누고 나서는 오히려 이해하기 편했습니다.

아티스트 자체의 정보는 artists, 축제 정보는 festivals에 둡니다. 출연 날짜와 시간, 무대처럼 두 대상이 만날 때마다 달라지는 값은 festival_artists가 맡습니다.

출연 날짜를 아티스트 테이블에 넣으면 어떻게 될까요? 같은 아티스트라도 축제마다 출연일이 다르기 때문에 하나의 날짜로 정할 수 없습니다. 출연 정보가 중간 테이블에 들어간 이유가 여기에 있었습니다.

SQL로 연결된 축제를 찾아봤다

SQL은 데이터베이스에 질문하고 규칙을 만드는 언어입니다. 테이블을 만들거나 데이터를 수정할 때도 쓰고, 필요한 정보만 골라 가져올 때도 사용합니다.

특정 아티스트가 출연하는 축제를 찾는 과정을 SQL로 표현하면 다음과 같습니다.

SELECT
  festivals.name,
  festival_artists.performance_date,
  festival_artists.performance_time,
  festival_artists.stage_name
FROM festival_artists
JOIN festivals
  ON festivals.id = festival_artists.festival_id
WHERE festival_artists.artist_id = 1
ORDER BY festival_artists.performance_date;

코드가 길어 보여도 역할을 나누면 읽을 만했습니다.

  • SELECT: 가져올 정보를 정합니다.
  • FROM: 어느 테이블에서 찾기 시작할지 정합니다.
  • JOIN: 연결된 축제 테이블을 함께 봅니다.
  • ON: 두 테이블을 연결할 기준을 지정합니다.
  • WHERE: 원하는 아티스트만 남깁니다.
  • ORDER BY: 공연 날짜 순으로 정렬합니다.

실제 페스티봄 화면에서는 SQL 문장을 그대로 작성하지 않고 Supabase의 조회 문법을 사용합니다. 그래도 하려는 일은 같습니다. 출연 정보에서 아티스트 번호가 일치하는 행을 찾고, 축제 번호로 연결된 이름과 날짜, 장소를 함께 가져오는 과정입니다.[2]

이 부분을 이해하고 나니 아티스트 상세 페이지에 출연 페스티벌이 나타나는 흐름도 보이기 시작했습니다. 화면끼리 바로 연결되는 것이 아니라 데이터베이스의 관계를 따라 정보를 찾고 있었던 셈이죠. 좋은 데이터베이스 설계란 바로 이 관계를 얼마나 단순하면서도 알아보기 좋게 연결하는 것이 아닐까? 라는 생각도 하게 됐습니다.

같은 아티스트가 여러 날짜에 출연한다면?

처음 만든 구조에는 놓친 조건이 하나 있었습니다. 한 아티스트가 한 축제에 한 번만 출연한다고 가정했던 셈입니다.

당시에는 축제 번호와 아티스트 번호의 조합으로 출연 한 건을 구분했습니다. 하루짜리 축제라면 별문제가 없어 보였습니다.

여러 날 열리는 축제에서는 상황이 달랐습니다. 같은 아티스트가 날짜를 달리해 두 번 출연할 수도 있었거든요.

축제 10번 + 아티스트 3번 + 8월 22일
축제 10번 + 아티스트 3번 + 8월 23일

두 출연은 축제 번호와 아티스트 번호가 같습니다. 기존 구조에서는 서로 다른 일정으로 구분하기 어려웠습니다.

그래서 출연 정보마다 별도의 id를 주기로 했습니다. 실제로 적용한 핵심 SQL은 다음과 같습니다.

아래 코드는 페스티봄의 실제 018_allow_multi_day_artist_lineup.sql 마이그레이션에서 구조 변경에 해당하는 부분을 옮긴 것입니다.

ALTER TABLE public.festival_artists
  ADD COLUMN IF NOT EXISTS id BIGINT
  GENERATED BY DEFAULT AS IDENTITY;

ALTER TABLE public.festival_artists
  DROP CONSTRAINT IF EXISTS festival_artists_pkey;

ALTER TABLE public.festival_artists
  ADD CONSTRAINT festival_artists_pkey PRIMARY KEY (id);

CREATE INDEX IF NOT EXISTS festival_artists_festival_artist_idx
  ON public.festival_artists (festival_id, artist_id);

핵심은 출연 한 건마다 고유한 번호를 갖게 한 부분입니다. 축제와 아티스트가 같더라도 id가 다르면 별개의 출연으로 저장됩니다. 날짜와 시간, 무대도 각각 기록할 수 있게 됐습니다.

마지막의 INDEX는 축제 번호와 아티스트 번호로 출연 정보를 찾는 작업을 돕습니다. 세부 원리를 전부 이해한 것은 아니지만, 자주 검색하는 기준에 색인을 만들어둔다는 식으로 받아들였습니다.

JSON으로 넘어오니 데이터 모양이 보였다

SQL과 함께 자주 마주친 것이 JSON입니다. 둘 다 데이터를 다루지만 역할은 다릅니다.

SQL이 데이터를 저장하고 찾기 위한 언어라면, JSON은 조회 결과를 웹앱에서 주고받기 좋은 형태로 표현합니다. 아티스트 상세 화면에 필요한 정보를 단순화하면 아래와 같은 모양입니다.

{
  "artist": {
    "id": 3,
    "name": "아티스트 A",
    "image_url": "/artist-a.jpg"
  },
  "festivalRows": [
    {
      "performance_date": "2026-08-22",
      "performance_time": "18:00",
      "stage_name": "메인 스테이지",
      "festivals": {
        "id": 10,
        "name": "페스티벌 A",
        "start_date": "2026-08-22",
        "end_date": "2026-08-23",
        "location": "서울"
      }
    },
    {
      "performance_date": "2026-08-23",
      "performance_time": "20:00",
      "stage_name": "서브 스테이지",
      "festivals": {
        "id": 10,
        "name": "페스티벌 A",
        "start_date": "2026-08-22",
        "end_date": "2026-08-23",
        "location": "서울"
      }
    }
  ]
}

중괄호 {} 안에는 하나의 대상을 설명하는 값이 들어갑니다. 대괄호 []는 여러 항목을 담는 목록이고요. 여기서는 아티스트 한 명이 하나의 객체가 되고, 여러 출연 일정은 festivalRows 배열에 담겼습니다.

같은 페스티벌이 두 번 나오지만 공연 날짜와 시간이 다릅니다. 앞에서 데이터 구조를 바꾼 결과가 JSON에서도 두 개의 출연 정보로 나타난 것이죠. 화면에서는 이 목록을 날짜순으로 반복해 보여주면 됩니다.

제가 이해한 흐름을 한 줄씩 정리하면 이렇습니다.

SQL: 데이터를 어떻게 저장하고 찾을지 정한다
JSON: 찾은 데이터를 웹앱이 사용할 모양으로 전달한다
화면: JSON을 읽어 사용자가 볼 수 있게 보여준다

처음에는 SQL과 JSON이 모두 낯선 코드처럼 보였어요. 어디에서 쓰이는지를 연결하고 나니 차이가 조금씩 눈에 들어왔습니다.

기능을 설명하니 데이터 구조가 따라왔다

이번 경험에서 배운 것은 데이터 구조, 데이터베이스 설계를 처음부터 완벽하게 만들기 어렵다는 점입니다. 특히 비개발자의 입장에서는 데이터베이스 설계가 더 어렵습니다.

처음에는 축제만 저장하면 된다고 봤습니다. 아티스트 상세 화면을 만들고 싶어지자 별도의 아티스트와 출연 정보가 필요해졌습니다. 여러 날짜 출연이라는 실제 상황을 만나고 나서는 출연 한 건을 구분하는 기준까지 바꿔야 했습니다.

돌아보면 화면에서 필요했던 기능이 그대로 데이터베이스의 조건이 됐습니다.

  • 아티스트를 누르면 출연 축제를 보여준다.
  • 한 축제에는 여러 아티스트가 출연한다.
  • 한 아티스트는 여러 축제에 출연한다.
  • 같은 축제에서도 날짜가 다르면 여러 번 출연할 수 있다.

이 문장들을 먼저 정리하니 AI에게 무엇을 바꿔달라고 해야 할지도 선명해졌어요. 막연하게 데이터베이스를 고쳐달라는 요청보다, 같은 축제와 아티스트 조합에 여러 출연 날짜를 저장해야 한다고 말하는 편이 훨씬 구체적이었습니다.

비개발자인 제게는 SQL 문법을 외우는 일보다 무엇과 무엇이 연결되는지 이해하는 일이 먼저였습니다. 관계를 정리한 뒤에야 SQL이 규칙을 만들고 JSON이 결과를 화면까지 전달한다는 흐름도 따라갈 수 있었습니다.

페스티봄의 데이터베이스는 한 번에 완성한 설계도가 아닙니다. 만들고 싶은 기능을 추가하고, 맞지 않는 부분을 발견할 때마다 고쳐온 기록에 더 가깝습니다. 여러 날짜 출연 문제는 그 과정에서 만난 첫 번째 구조 변경이었죠.

참고 자료

  1. Supabase Tables and Data — 테이블 관계와 외래키 설명에 참고했습니다.
  2. Supabase Querying Joins and Nested Tables — 연결된 테이블을 조회하는 방식에 참고했습니다.