[TIL] Day 75 Firestore 데이터베이스 연동 오류

현서·2026년 3월 16일

[TIL] Flutter 9기

목록 보기
87/102

Firestore 데이터베이스 연동 오류 수정

개요

Firestore에 데이터를 저장할 때 cloud_firestore/not-found 에러가 발생했다. Firestore 경로 설정과 문서 생성 시점의 오류를 분석하여 데이터 저장 기능을 복구했다.


1단계: 문제 분석

발생한 에러

Exception: [cloud_firestore/not-found] 
No document found at the specified path.

에러 발생 상황

  • 앱에서 사용자 데이터를 Firestore에 저장하려 할 때 발생
  • 저장 기능 완전히 마비 상태
  • 특정 컬렉션 경로에서만 발생

원인 추측

  1. Firestore 경로가 잘못되었을 가능성
  2. 문서가 존재하지 않아 업데이트 실패
  3. 컬렉션 구조가 제대로 생성되지 않음
  4. 권한 설정 문제

2단계: 원인 파악

Firestore 경로 문제

// 문제가 있던 코드
Future<void> saveUserData(String userId, Map<String, dynamic> data) async {
  await FirebaseFirestore.instance
      .collection('users')
      .doc(userId)
      .update(data); // ❌ 문서가 없으면 에러 발생
}

문제점:

  • update() 메서드는 문서가 이미 존재할 때만 작동
  • 새로운 문서를 생성할 때는 실패

컬렉션 구조 미생성

문서를 직접 생성하지 않으면 Firestore에 컬렉션 경로가 존재하지 않는다.

❌ Firestore 구조가 비어있는 상태
┌─ collections/
   ├─ users (존재하지 않음)
   └─ ...

✓ 문서 생성 후의 구조
┌─ collections/
   ├─ users/
   │  ├─ userId1/
   │  │  └─ (데이터)
   │  └─ userId2/
   │     └─ (데이터)

3단계: 해결 전략

전략 1: set() 메서드 사용

update() 대신 set()을 사용하여 문서가 없으면 생성, 있으면 업데이트하도록 변경

// set() 사용: merge 옵션으로 기존 데이터 유지
Future<void> saveUserData(String userId, Map<String, dynamic> data) async {
  await FirebaseFirestore.instance
      .collection('users')
      .doc(userId)
      .set(data, SetOptions(merge: true)); // ✓ 문서 없으면 생성
}

전략 2: 에러 처리 추가

Future<void> saveUserData(String userId, Map<String, dynamic> data) async {
  try {
    await FirebaseFirestore.instance
        .collection('users')
        .doc(userId)
        .set(data, SetOptions(merge: true));
    print('데이터 저장 성공');
  } catch (e) {
    print('저장 실패: $e');
    // 사용자에게 알림
  }
}

사용 예시

// 1. 단일 데이터 저장
final firestoreService = FirestoreService();

await firestoreService.saveUser(
  userId: 'user123',
  userData: {
    'name': '현서',
    'email': 'user@example.com',
    'createdAt': FieldValue.serverTimestamp(),
  },
);

// 2. 중첩된 데이터 저장
await firestoreService.saveGame(
  gameId: 'game001',
  gameData: {
    'title': '숲코몬',
    'region': 'Seoul',
    'score': 1000,
    'timestamp': FieldValue.serverTimestamp(),
  },
);

// 3. 배치 쓰기
await firestoreService.saveBatch([
  {'id': 'item1', 'name': '아이템1'},
  {'id': 'item2', 'name': '아이템2'},
  {'id': 'item3', 'name': '아이템3'},
]);

주의사항

set() vs update()

메서드문서 없을 때문서 있을 때사용 시기
set()생성덮어쓰기신규/업데이트 둘 다
update()에러 발생부분 업데이트문서가 있을 때만
set() + merge생성병합기존 데이터 유지 필요

SetOptions(merge: true) 중요성

// merge: false (기본값) - 전체 덮어쓰기
await _db.collection('users').doc('user1').set({
  'name': '현서',
}); // age는 삭제됨

// merge: true - 기존 데이터와 병합
await _db.collection('users').doc('user1').set({
  'name': '현서',
}, SetOptions(merge: true)); // age는 유지됨

Firestore 보안 규칙

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 사용자는 자신의 데이터만 읽/쓰기 가능
    match /users/{userId} {
      allow read, write: if request.auth.uid == userId;
    }
    
    // 모두 읽기 가능, 인증된 사용자만 쓰기
    match /games/{document=**} {
      allow read: if true;
      allow write: if request.auth != null;
    }
  }
}

검증 및 테스트

저장 기능 테스트

void testSaveUser() async {
  final service = FirestoreService();
  
  try {
    // 1. 새 문서 생성
    await service.saveUser('test_user_1', {
      'name': 'Test User',
      'email': 'test@example.com',
    });
    print('✓ 새 문서 생성 성공');
    
    // 2. 기존 문서 업데이트
    await service.saveUser('test_user_1', {
      'age': 25,
    });
    print('✓ 문서 업데이트 성공 (기존 데이터 유지)');
    
    // 3. 데이터 검증
    final doc = await FirebaseFirestore.instance
        .collection('users')
        .doc('test_user_1')
        .get();
    
    assert(doc.data()?['name'] == 'Test User'); // 기존 데이터 유지
    assert(doc.data()?['age'] == 25); // 새 데이터 추가
    print('✓ 데이터 검증 성공');
    
  } catch (e) {
    print('✗ 테스트 실패: $e');
  }
}

성능 최적화

배치 쓰기로 성능 개선

// 나쁜 예: 루프마다 개별 저장 (느림)
for (var item in items) {
  await _db.collection('items').doc(item['id']).set(item);
}

// 좋은 예: 배치 쓰기 (빠름)
WriteBatch batch = _db.batch();
for (var item in items) {
  batch.set(_db.collection('items').doc(item['id']), item);
}
await batch.commit();

인덱싱 활용

복잡한 쿼리는 Firestore에서 인덱스를 자동 생성하도록 안내한다.

// 인덱스가 필요한 쿼리
_db.collection('users')
    .where('region', isEqualTo: 'Seoul')
    .where('level', isGreaterThan: 10)
    .orderBy('score', descending: true)
    .get();

결론

cloud_firestore/not-found 에러는 주로 잘못된 저장 메서드 사용 또는 경로 오류에서 발생한다. set() 메서드와 SetOptions(merge: true)를 올바르게 사용하면 안정적인 데이터 저장이 가능하다. 경로를 상수화하고 에러 처리를 추가하면 유지보수성도 높아진다.

0개의 댓글