🗄️ 데이터베이스

Firebase

Google의 모바일/웹 앱 개발 플랫폼으로, 백엔드 인프라 없이도 인증, 데이터베이스, 스토리지, 호스팅, 서버리스 함수 등을 통합 제공하는 BaaS(Backend-as-a-Service)입니다.

📖 상세 설명

Firebase는 2011년 실시간 데이터베이스 서비스로 시작해 2014년 Google에 인수된 후 종합적인 앱 개발 플랫폼으로 발전했습니다. 스타트업과 MVP 개발에 특히 인기 있으며, 백엔드 서버 없이도 모바일/웹 앱의 전체 기능을 구현할 수 있습니다.

Firebase 핵심 서비스

📄
Firestore
NoSQL Document DB, 실시간 동기화
🔐
Authentication
소셜 로그인, 이메일, 전화번호
Cloud Functions
서버리스 백엔드 로직
🌐
Hosting
글로벌 CDN 정적 호스팅
📦
Storage
파일 업로드/다운로드
📊
Analytics
사용자 행동 분석

Firestore vs Realtime Database

특성 Firestore Realtime Database
데이터 모델 Document-Collection JSON Tree
쿼리 복합 쿼리, 인덱싱 지원 제한적 (정렬 1개만)
확장성 자동 수평 확장 수직 확장 (샤딩 수동)
오프라인 다중 탭, 영구 캐시 단일 탭
과금 읽기/쓰기 횟수 대역폭/저장 용량

💻 코드 예제

1. Firebase 초기화 및 Firestore 기본 CRUD

JavaScript (Web)
// Firebase SDK 초기화 import { initializeApp } from 'firebase/app'; import { getFirestore, collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, query, where, orderBy, limit, onSnapshot } from 'firebase/firestore'; const firebaseConfig = { apiKey: "your-api-key", authDomain: "your-app.firebaseapp.com", projectId: "your-project-id", storageBucket: "your-app.appspot.com", messagingSenderId: "123456789", appId: "1:123456789:web:abc123" }; const app = initializeApp(firebaseConfig); const db = getFirestore(app); // CREATE - 문서 추가 async function addUser(userData) { const docRef = await addDoc(collection(db, 'users'), { name: userData.name, email: userData.email, createdAt: new Date(), status: 'active' }); console.log('Document ID:', docRef.id); return docRef.id; } // READ - 단일 문서 조회 async function getUser(userId) { const docSnap = await getDoc(doc(db, 'users', userId)); if (docSnap.exists()) { return { id: docSnap.id, ...docSnap.data() }; } return null; } // READ - 컬렉션 쿼리 (조건 검색) async function getActiveUsers(minAge) { const q = query( collection(db, 'users'), where('status', '==', 'active'), where('age', '>=', minAge), orderBy('age'), limit(10) ); const snapshot = await getDocs(q); return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); } // UPDATE - 문서 수정 async function updateUser(userId, updates) { await updateDoc(doc(db, 'users', userId), { ...updates, updatedAt: new Date() }); } // DELETE - 문서 삭제 async function deleteUser(userId) { await deleteDoc(doc(db, 'users', userId)); }

2. 실시간 리스너 (Realtime Updates)

JavaScript
// 실시간 채팅 리스너 function subscribeToChatRoom(roomId, onMessage) { const messagesRef = collection(db, 'rooms', roomId, 'messages'); const q = query(messagesRef, orderBy('timestamp', 'desc'), limit(50)); // onSnapshot으로 실시간 변경 감지 const unsubscribe = onSnapshot(q, (snapshot) => { snapshot.docChanges().forEach((change) => { if (change.type === 'added') { onMessage({ type: 'new', message: { id: change.doc.id, ...change.doc.data() } }); } if (change.type === 'modified') { onMessage({ type: 'updated', message: { id: change.doc.id, ...change.doc.data() } }); } if (change.type === 'removed') { onMessage({ type: 'deleted', messageId: change.doc.id }); } }); }, (error) => { console.error('Realtime listener error:', error); }); // 컴포넌트 언마운트 시 구독 해제 return unsubscribe; } // React 컴포넌트에서 사용 function ChatRoom({ roomId }) { const [messages, setMessages] = useState([]); useEffect(() => { const unsubscribe = subscribeToChatRoom(roomId, (event) => { if (event.type === 'new') { setMessages(prev => [event.message, ...prev]); } }); return () => unsubscribe(); }, [roomId]); }

3. Firebase Authentication

JavaScript
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, signInWithPopup, GoogleAuthProvider, onAuthStateChanged, signOut } from 'firebase/auth'; const auth = getAuth(app); const googleProvider = new GoogleAuthProvider(); // 이메일/비밀번호 회원가입 async function signUp(email, password) { try { const result = await createUserWithEmailAndPassword(auth, email, password); // Firestore에 사용자 프로필 생성 await addDoc(collection(db, 'users'), { uid: result.user.uid, email: result.user.email, createdAt: new Date() }); return result.user; } catch (error) { if (error.code === 'auth/email-already-in-use') { throw new Error('이미 사용 중인 이메일입니다.'); } throw error; } } // Google 소셜 로그인 async function signInWithGoogle() { const result = await signInWithPopup(auth, googleProvider); return result.user; } // 인증 상태 감지 onAuthStateChanged(auth, (user) => { if (user) { console.log('로그인됨:', user.email); } else { console.log('로그아웃 상태'); } });

4. Cloud Functions (서버리스 백엔드)

Node.js (functions/index.js)
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); // HTTP 트리거 함수 exports.api = functions.https.onRequest(async (req, res) => { if (req.method !== 'POST') { return res.status(405).send('Method Not Allowed'); } const { userId, action } = req.body; // 비즈니스 로직 처리 res.json({ success: true }); }); // Firestore 트리거 - 문서 생성 시 exports.onUserCreated = functions.firestore .document('users/{userId}') .onCreate(async (snap, context) => { const userData = snap.data(); // 환영 이메일 발송 (외부 서비스 호출) await sendWelcomeEmail(userData.email); // 통계 업데이트 await admin.firestore() .collection('stats') .doc('users') .update({ totalCount: admin.firestore.FieldValue.increment(1) }); }); // 스케줄 함수 - 매일 자정 실행 exports.dailyCleanup = functions.pubsub .schedule('0 0 * * *') .timeZone('Asia/Seoul') .onRun(async (context) => { const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); // 30일 이상 된 로그 삭제 const snapshot = await admin.firestore() .collection('logs') .where('createdAt', '<', thirtyDaysAgo) .get(); const batch = admin.firestore().batch(); snapshot.docs.forEach(doc => batch.delete(doc.ref)); await batch.commit(); console.log(`Deleted ${snapshot.size} old logs`); });

5. Firestore Security Rules

firestore.rules
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // 사용자 프로필 - 본인만 읽기/쓰기 match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; } // 게시글 - 인증 사용자만 작성, 모두 읽기 match /posts/{postId} { allow read: if true; allow create: if request.auth != null && request.resource.data.authorId == request.auth.uid; allow update, delete: if request.auth != null && resource.data.authorId == request.auth.uid; } // 채팅방 - 참가자만 접근 match /rooms/{roomId} { allow read, write: if request.auth != null && request.auth.uid in resource.data.members; match /messages/{messageId} { allow read: if request.auth != null && request.auth.uid in get(/databases/$(database)/documents/rooms/$(roomId)).data.members; allow create: if request.auth != null && request.auth.uid in get(/databases/$(database)/documents/rooms/$(roomId)).data.members; } } } }

💬 현업 대화 예시

스타트업 CTO
"MVP는 Firebase로 빠르게 출시하고, MAU 10만 넘으면 커스텀 백엔드 검토하자. 초기 비용 거의 없고 확장도 어느 정도는 돼."
프론트엔드 개발자
"Firestore onSnapshot으로 실시간 채팅 구현했는데, 메시지 딜레이 거의 없어요. 웹소켓 서버 직접 구축하는 것보다 훨씬 편하네요."
시니어 개발자
"Security Rules 꼼꼼히 작성해야 해. 클라이언트에서 직접 DB 접근하니까 규칙 잘못 짜면 데이터 유출돼. 테스트도 꼭 해봐야 하고."
DevOps 엔지니어
"Firestore 읽기 횟수 비용이 생각보다 커질 수 있어요. 무한 스크롤에 실시간 리스너 달면 청구서 폭탄 맞을 수도 있으니까 캐싱 전략 잘 짜세요."

⚠️ 주의사항

Vendor Lock-in 주의

Firebase의 독자적 API와 데이터 모델에 종속되면 이전이 어렵습니다. 핵심 비즈니스 로직은 분리하고, 인터페이스 레이어를 두는 것이 좋습니다.

비용 모니터링 필수

Firestore는 읽기/쓰기 횟수 기반 과금입니다. 복잡한 쿼리, 실시간 리스너, 대용량 데이터에서 예상치 못한 비용이 발생할 수 있습니다. Budget Alert를 반드시 설정하세요.

Security Rules는 서버 검증이 아님

Security Rules는 클라이언트 요청을 필터링하지만, 복잡한 비즈니스 검증이나 트랜잭션 보장에는 한계가 있습니다. 중요한 로직은 Cloud Functions로 처리하세요.

쿼리 제한 사항

Firestore는 복합 쿼리 시 인덱스가 필요하고, OR 조건 쿼리, 전문 검색(Full-text search) 등이 제한됩니다. 복잡한 검색에는 Algolia 등 외부 서비스 연동이 필요합니다.

🔗 관련 용어

📚 더 배우기