1인 개발

Firebase Realtime Database 데이터 설계하는 법 – 처음부터 깔끔하게!

withmilk 2025. 3. 30. 18:12

안녕하세요 😀

 

Firebase Realtime Database(Firebase RTDB)를 사용할 때, 코딩보다 더 중요한 게 바로 데이터 구조 설계예요.
앱을 만들다 보면 데이터를 어떻게 저장하고 불러올지 고민하게 되는데, 처음부터 구조를 잘 설계하지 않으면, 나중에 복잡해지고 느려지고… 아주 큰 후회를 하게 됩니다 😢 이번 글에서는 Firebase RTDB를 쓸 때 데이터를 어떻게 설계하면 좋은지, 초보자도 이해할 수 있도록 친절하게 알려드릴게요!


Firebase RTDB를 사용하려고 docs 페이지에 들어가보면, 데이터베이스 구조화를 잘 해야한다고 설명하고 있는 것을 확인할 수 있어요. 저도 처음에 이 페이즈를 보았을 때 어떻게 해야하는지에 대해서 정확하게 잘 모르고 진행했었어요. 제가 RTDB를 사용해서 개발하면서 이해한 것들에 대해서 풀어서 설명해보려고 해요.

 

데이터베이스 구조화에 대해서 설명되어 있는 Firebase 공식 문서

 

📦 RTDB는 "트리(Tree)" 구조예요

RTDB는 JSON 형태로 데이터를 저장하는데, 이건 마치 나무처럼 뻗어 있는 구조예요.

{
  "users": {
    "user1": {
      "name": "철수",
      "age": 9
    },
    "user2": {
      "name": "영희",
      "age": 10
    }
  }
}

위처럼 계단식으로 내려가는 구조예요. 그래서 잘못 설계하면 데이터가 너무 깊어져서
한 번에 많은 걸 불러오게 되고, 속도도 느려지고, 관리도 어려워져요!

 

CRUD에서 READ를 한다고 했을 때, users가 너무 크면 그만큼 데이터를 한 번에 큰 데이터를 읽게되고 그만큼 DB 사용량이 기하급수적으로 늘어나게 돼요.


좋은 데이터 설계의 기본 원칙

1️⃣ 정규화(Normalization)처럼 설계하기

  • 하나의 정보는 하나의 위치에만 저장하기
  • 중복 데이터 금지

2️⃣ 데이터는 평평하게(Flat하게)

  • 너무 깊은 중첩은 피하기
  • 필요한 키는 직접 만들어서 관리하기

3️⃣ ID 기반으로 접근하기

  • 유저 ID, 게시글 ID 등을 키로 사용하면 검색과 접근이 쉬워져요.

1. 나쁜 예 vs 좋은 예 비교

❌ 나쁜 설계 예시 (중첩이 너무 깊어요.)

{
  "users": {
    "user1": {
      "name": "철수",
      "age": 9,
      "posts": {
        "post1": {
          "title": "첫 글",
          "content": "안녕하세요"
        },
        "post2": {
          "title": "두번째 글",
          "content": "안녕히계세요"
        }
      }
    },
    "user2": {
      "name": "영희",
      "age": 10,
    }
  }
}

 

  • 유저의 정보만 읽어오고 싶을 때에도, 유저가 작성한 포스트도 모두 한 번에 읽어야 해요. 유저가 작성한 포스트가 많다면 받아야 하는 데이터가 엄청 커질 거에요. RTDB에서는 데이터를 다운로드한 만큼 과금되기 때문에 최대한 줄여야해요.
  • 모든 포스트를 불러와서 포스트 목록을 보여주기 위해서는 모든 유저를 돌아야 해요. 매우 이상한 일이에요.
  • 유저 안에 포스트가 있기 때문에 유저 탈퇴 시 글도 같이 지워질 수 있어요.

 

✅ 좋은 설계 예시 (관계를 분리하고 ID로 연결)

{
  "users": {
    "user1": {
      "name": "철수",
      "age": 9
    },
    "user2": {
      "name": "영희",
      "age": 10
    }
  },
  "posts": {
    "post1": {
      "userId": "user1",
      "title": "첫 글",
      "content": "안녕하세요"
    },
    "post2": {
      "userId": "user1",
      "title": "두번째 글",
      "content": "안녕히계세요"
    }
  }
}

 

  • 글 목록을 쉽게 불러올 수 있어요.
  • 유저 정보랑 글 정보를 따로 관리할 수 있어요. 유저의 회원 정보를 보여주고 싶을 때는 유저 정보만 읽을 수 있고, 글 정보를 보여주고 싶을 때는 글 정보만 읽으면 되기 때문에 데이터 다운로드 크기를 최적으로 사용할 수 있어요.
  • 확장성이 좋아요!

 

위처럼 설계했을 때 실제 Firebase 콘솔에서는 아래처럼 보여요.

RTDB 데이터를 잘 설계한 예시

 

확장성에 대한 예시

"posts": {
  "postId456": {
    "userId": "userId123",
    "title": "제목",
    "content": "내용",
    "createdAt": 1710000000000
  }
},
"comments": {
  "commentId789": {
    "postId": "postId456",
    "userId": "userId123",
    "text": "좋아요!",
    "createdAt": 1710000000000
  }
}

 

각각의 글에 대한 댓글 데이터를 추가하고 싶을 때에는 새로 comments 라는 레퍼런스를 사용하면 돼요. 그리고 댓글을 단 글의 postIduserId를 comment 데이터에 저장하면 되니까요.

  • postId와 userId를 같이 저장 → 관계 파악이 쉬워요
  • createdAt(작성 시간)은 정렬할 때 유용해요

 


ID를 키로 설정해서 검색이 쉽게 하자!!

오늘의 첫번째 핵심이에요. RTDB를 사용할 때에는 검색하고 싶은 ID를 키로 사용하는 것이 최고의 방법이에요. 예를 들면 아래와 같아요.

{
  "users": {
    "user1": {
	  "name": "철수"
    },
    "user2": {
      "name": "영희"
    }
  },
  "groups": {
    "group1": {
      "name": "최고의 그룹",
      "members": {
      	"user1": true,
        "user2": true
      }
    },
    "group2": {
      "name": "철수만 가입하는 그룹",
      "members": {
      	"user1": true
      }
    }
  }
  "user_group_map": {
    "user1": ["group1", "group2"],
    "user2": ["group1"]
  }
}

user1과 user2라는 유저가 있고 2개의 그룹이 있어요. user1은 group1, group2에 모두 가입했고 user2는 group1에만 가입했어요.

 

  • 1️⃣ group 밑에 members라는 레퍼런스가 있을 때 members의 키를 user id로 사용했기 때문에 각각의 그룹에 누가 가입했는지 쉽게 알 수 있어요. 내가 그룹1에 가입했는지 알고 싶다면, ref('groups/group1/members/user2').get()을 호출해서 데이터가 있는지 확인하면 빠르게 확인할 수 있죠.
  • 2️⃣ user_group_map이라는 데이터를 읽었을 때에 각각의 유저가 몇개의 그룹에 가입했는지 빠르게 알 수 있어요. 내가 몇개의 그룹에 가입했는지 알고 싶다면, ref('user_group_map/user2').get() 으로 빠르게 확인해볼 수 있죠.

 

데이터를 여러번 저장하는 것으로 내가 만드는 앱에서 필요한 데이터를 빠르게빠르게 읽어올 수 있어요.

중복 저장? 해도 돼요!

Firebase RTDB를 사용할 때에 또한 핵심적으로 생각해야하는 것은 중복 데이터 금지 를 크게 신경 쓰지 않고 DB를 설계해야 하는 거에요.

Firebase Realtime Database는 SQL이 아니기 때문에, 검색이 느린 대신 읽기는 빠르다는 특징이 있어요. SQL이 아니기 때문에 데이터를 필터링하고, 정렬하거나 간단한 연산을 할 수가 없어요.


그래서 가끔은 groups, user_group_map의 예시처럼 데이터를 중복해서 저장해도 괜찮아요!

"postLikes": {
  "postId456": {
    "userId123": true,
    "userId456": true
  }
},
"postLikeCount": {
  "postId456": 2
}

이처럼, 누가 눌렀는지도 저장하고 숫자도 별도로 저장해서 목록에서 빠르게 보여줄 수 있어요


마무리하며..

Firebase RTDB를 사용하기 위해 데이터를 설계할 때 다음 체크리스트를 활용하면서 해보세요.

  • ✅ 데이터는 평평하게
  • ✅ 너무 깊은 구조 ❌
  • ✅ 관계는 ID로 연결
  • ✅ 중복 저장은 읽기 성능을 위해 허용
  • ✅ 시간 정보(createdAt) 활용
  • ✅ 읽기 패턴을 먼저 생각하기 (어디서 자주 쓰는지)

처음에는 어떤 구조로 데이터를 저장해야 할지 막막하고 어려울 수 있어요. 하지만 걱정하지 마세요. 누구나 처음은 다 그렇고, 중요한 건 ‘지금’ 이 글을 보며 배우고 있다는 것 자체가 성장의 증거예요. 데이터를 깔끔하고 효율적으로 설계하는 습관은 단순히 앱을 잘 만드는 걸 넘어, 당신이 진짜 개발자로 나아가는 첫걸음이 될 거예요.

 

앞으로 앱에 다양한 기능을 추가하면서 더 복잡한 데이터 구조를 마주하게 될 거예요. 그럴 때마다 오늘 배운 원칙들을 떠올려보세요. "데이터는 평평하게", "ID로 연결", "중복 저장은 OK!" 이런 기준을 바탕으로 천천히 구조를 만들어 나가면, 어떤 앱이든 자신 있게 도전할 수 있을 거예요. 그리고 나중에 추가될 기능들을 생각하면서 미리 고민해서 어떻게 새로운 관계를 지을지, 어떤 것들을 인덱싱해서 중복 저장할지에 대해서 생각하고 설계해두면 더 좋을거에요. 아직 배울 게 많지만, 너무 조급해하지 않아도 괜찮아요. 저와 함께 하나씩 차근차근 익혀가면 어느새 멋진 앱을 스스로 만들고 있는 자신을 발견하게 될 거예요.