오프라인으로 데이터에 액세스

Cloud Firestore는 오프라인 데이터 지속성을 지원합니다. 이 기능은 기기가 오프라인 상태일 때 앱에서 데이터에 액세스할 수 있도록 앱에서 자주 사용하는 Cloud Firestore 데이터의 사본을 캐시합니다. 캐시된 데이터를 쓰고, 읽고, 리슨하고, 쿼리할 수 있습니다. 기기가 다시 온라인 상태가 되면 Cloud Firestore는 앱이 적용한 로컬 변경사항을 Cloud Firestore 백엔드에 동기화합니다.

오프라인 지속성을 사용하기 위해 Cloud Firestore 데이터에 액세스할 때 사용하는 코드를 변경할 필요는 없습니다. 오프라인 지속성을 사용 설정하면 Cloud Firestore 클라이언트 라이브러리는 온라인 및 오프라인 데이터 액세스를 자동으로 관리하고 기기가 다시 온라인 상태가 되면 로컬 데이터를 동기화합니다.

오프라인 지속성 구성

Cloud Firestore를 초기화할 때 오프라인 지속성을 사용 설정하거나 중지할 수 있습니다.

  • Android 및 Apple 플랫폼에서는 오프라인 지속성이 기본적으로 사용 설정됩니다. 지속성을 사용 중지하려면 PersistenceEnabled 옵션을 false로 설정하세요.
  • 웹에서는 오프라인 지속성이 기본적으로 사용 중지됩니다. 지속성을 사용 설정하려면 enablePersistence 메서드를 호출하세요. Cloud Firestore의 캐시는 세션 간에 자동으로 삭제되지 않습니다. 따라서 웹 앱에서 중요한 정보를 처리하는 경우 지속성을 사용 설정하기 전에 신뢰할 수 있는 기기인지 사용자에게 확인해야 합니다.

Web version 9

// Memory cache is the default if no config is specified.
initializeFirestore(app);

// This is the default behavior if no persistence is specified.
initializeFirestore(app, {localCache: memoryLocalCache()});

// Defaults to single-tab persistence if no tab manager is specified.
initializeFirestore(app, {localCache: persistentLocalCache(/*settings*/{})});

// Same as `initializeFirestore(app, {localCache: persistentLocalCache(/*settings*/{})})`,
// but more explicit about tab management.
initializeFirestore(app,
  {localCache:
    persistentLocalCache(/*settings*/{tabManager: persistentSingleTabManager()})
});

// Use multi-tab IndexedDb persistence.
initializeFirestore(app,
  {localCache:
    persistentLocalCache(/*settings*/{tabManager: persistentMultipleTabManager()})
  });
  

Web version 8

firebase.firestore().enablePersistence()
  .catch((err) => {
      if (err.code == 'failed-precondition') {
          // Multiple tabs open, persistence can only be enabled
          // in one tab at a a time.
          // ...
      } else if (err.code == 'unimplemented') {
          // The current browser does not support all of the
          // features required to enable persistence
          // ...
      }
  });
// Subsequent queries will use persistence, if it was enabled successfully
Swift
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
let settings = FirestoreSettings()
settings.isPersistenceEnabled = true

// Any additional options
// ...

// Enable offline data persistence
let db = Firestore.firestore()
db.settings = settings
Objective-C
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init];
settings.persistenceEnabled = YES;

// Any additional options
// ...

// Enable offline data persistence
FIRFirestore *db = [FIRFirestore firestore];
db.settings = settings;

Kotlin+KTX

val settings = firestoreSettings {
    isPersistenceEnabled = true
}
db.firestoreSettings = settings

Java

FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
        .setPersistenceEnabled(true)
        .build();
db.setFirestoreSettings(settings);

Dart

// Apple and Android
db.settings = const Settings(persistenceEnabled: true);

// Web
await db
    .enablePersistence(const PersistenceSettings(synchronizeTabs: true));

캐시 크기 구성

지속성이 사용 설정되면 Cloud Firestore는 백엔드에서 수신된 모든 문서를 오프라인 액세스용으로 캐시합니다. Cloud Firestore는 캐시 크기의 기본 임곗값을 설정합니다. 기본값을 초과하면 Cloud Firestore는 사용하지 않는 오래된 문서를 주기적으로 정리합니다. 다른 캐시 크기 임곗값을 구성하거나 정리 프로세스를 완전히 사용 중지할 수 있습니다.

Web version 9

import { initializeFirestore, CACHE_SIZE_UNLIMITED } from "firebase/firestore";

const firestoreDb = initializeFirestore(app, {
  cacheSizeBytes: CACHE_SIZE_UNLIMITED
});

Web version 8

firebase.firestore().settings({
    cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED
});
Swift
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
// The default cache size threshold is 100 MB. Configure "cacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "FirestoreCacheSizeUnlimited"
// to disable clean-up.
let settings = Firestore.firestore().settings
settings.cacheSizeBytes = FirestoreCacheSizeUnlimited
Firestore.firestore().settings = settings
Objective-C
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
// The default cache size threshold is 100 MB. Configure "cacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "kFIRFirestoreCacheSizeUnlimited"
// to disable clean-up.
FIRFirestoreSettings *settings = [FIRFirestore firestore].settings;
settings.cacheSizeBytes = kFIRFirestoreCacheSizeUnlimited;
[FIRFirestore firestore].settings = settings;
  

Kotlin+KTX


// The default cache size threshold is 100 MB. Configure "setCacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "CACHE_SIZE_UNLIMITED"
// to disable clean-up.
val settings = FirebaseFirestoreSettings.Builder()
        .setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
        .build()
db.firestoreSettings = settings

Java


// The default cache size threshold is 100 MB. Configure "setCacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "CACHE_SIZE_UNLIMITED"
// to disable clean-up.
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
        .setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
        .build();
db.setFirestoreSettings(settings);

Dart

db.settings = const Settings(
  persistenceEnabled: true,
  cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);

오프라인 데이터 수신 대기

오프라인 지속성을 사용 설정한 경우 기기가 오프라인 상태이면 로컬에 캐시된 데이터가 변경될 때 리스너가 수신 대기 이벤트를 수신합니다. 문서, 컬렉션, 쿼리를 수신 대기할 수 있습니다.

데이터의 출처가 서버인지 아니면 캐시인지 확인하려면 스냅샷 이벤트의 SnapshotMetadata에 있는 fromCache 속성을 사용합니다. fromCachetrue이면 캐시에서 가져온 데이터이므로 서버와 일치하지 않거나 불완전할 수 있습니다. fromCachefalse이면 서버의 최신 업데이트와 일치하는 완전한 최신 데이터입니다.

기본적으로 SnapshotMetadata 변경되면 이벤트가 발생하지 않습니다. fromCache 값에 의존하는 경우 수신 대기 핸들러를 연결할 때 includeMetadataChanges 수신 대기 옵션을 지정하세요.

Web version 9

import { collection, onSnapshot, where, query } from "firebase/firestore";

const q = query(collection(db, "cities"), where("state", "==", "CA"));
onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => {
    snapshot.docChanges().forEach((change) => {
        if (change.type === "added") {
            console.log("New city: ", change.doc.data());
        }

        const source = snapshot.metadata.fromCache ? "local cache" : "server";
        console.log("Data came from " + source);
    });
});

Web version 8

db.collection("cities").where("state", "==", "CA")
  .onSnapshot({ includeMetadataChanges: true }, (snapshot) => {
      snapshot.docChanges().forEach((change) => {
          if (change.type === "added") {
              console.log("New city: ", change.doc.data());
          }

          var source = snapshot.metadata.fromCache ? "local cache" : "server";
          console.log("Data came from " + source);
      });
  });
Swift
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
// Listen to metadata updates to receive a server snapshot even if
// the data is the same as the cached data.
db.collection("cities").whereField("state", isEqualTo: "CA")
    .addSnapshotListener(includeMetadataChanges: true) { querySnapshot, error in
        guard let snapshot = querySnapshot else {
            print("Error retreiving snapshot: \(error!)")
            return
        }

        for diff in snapshot.documentChanges {
            if diff.type == .added {
                print("New city: \(diff.document.data())")
            }
        }

        let source = snapshot.metadata.isFromCache ? "local cache" : "server"
        print("Metadata: Data fetched from \(source)")
}
Objective-C
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
// Listen to metadata updates to receive a server snapshot even if
// the data is the same as the cached data.
[[[db collectionWithPath:@"cities"] queryWhereField:@"state" isEqualTo:@"CA"]
    addSnapshotListenerWithIncludeMetadataChanges:YES
    listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
      if (snapshot == nil) {
        NSLog(@"Error retreiving snapshot: %@", error);
        return;
      }
      for (FIRDocumentChange *diff in snapshot.documentChanges) {
        if (diff.type == FIRDocumentChangeTypeAdded) {
          NSLog(@"New city: %@", diff.document.data);
        }
      }

      NSString *source = snapshot.metadata.isFromCache ? @"local cache" : @"server";
      NSLog(@"Metadata: Data fetched from %@", source);
    }];

Kotlin+KTX

db.collection("cities").whereEqualTo("state", "CA")
    .addSnapshotListener(MetadataChanges.INCLUDE) { querySnapshot, e ->
        if (e != null) {
            Log.w(TAG, "Listen error", e)
            return@addSnapshotListener
        }

        for (change in querySnapshot!!.documentChanges) {
            if (change.type == DocumentChange.Type.ADDED) {
                Log.d(TAG, "New city: ${change.document.data}")
            }

            val source = if (querySnapshot.metadata.isFromCache) {
                "local cache"
            } else {
                "server"
            }
            Log.d(TAG, "Data fetched from $source")
        }
    }

Java

db.collection("cities").whereEqualTo("state", "CA")
        .addSnapshotListener(MetadataChanges.INCLUDE, new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(@Nullable QuerySnapshot querySnapshot,
                                @Nullable FirebaseFirestoreException e) {
                if (e != null) {
                    Log.w(TAG, "Listen error", e);
                    return;
                }

                for (DocumentChange change : querySnapshot.getDocumentChanges()) {
                    if (change.getType() == Type.ADDED) {
                        Log.d(TAG, "New city:" + change.getDocument().getData());
                    }

                    String source = querySnapshot.getMetadata().isFromCache() ?
                            "local cache" : "server";
                    Log.d(TAG, "Data fetched from " + source);
                }

            }
        });

Dart

db
    .collection("cities")
    .where("state", isEqualTo: "CA")
    .snapshots(includeMetadataChanges: true)
    .listen((querySnapshot) {
  for (var change in querySnapshot.docChanges) {
    if (change.type == DocumentChangeType.added) {
      final source =
          (querySnapshot.metadata.isFromCache) ? "local cache" : "server";

      print("Data fetched from $source}");
    }
  }
});

오프라인 데이터 가져오기

기기가 오프라인 상태일 때 문서를 가져오면 Cloud Firestore는 캐시의 데이터를 반환합니다.

컬렉션을 쿼리할 때 캐시된 문서가 없으면 빈 결과가 반환됩니다. 특정 문서를 가져오면 대신 오류가 반환됩니다.

오프라인 데이터 쿼리

쿼리는 오프라인 지속성과 연동합니다. 앞에서 설명한 것처럼 직접 가져오기 또는 수신 대기를 통해 쿼리 결과를 검색할 수 있습니다. 기기가 오프라인 상태일 때 로컬에 유지되는 데이터에 대해 새 쿼리를 만들 수도 있지만 이러한 쿼리는 처음에는 캐시된 문서에 대해서만 실행됩니다.

오프라인 쿼리 색인 구성

기본적으로 Firestore SDK는 오프라인 쿼리를 실행할 때 로컬 캐시의 컬렉션에 있는 모든 문서를 스캔합니다. 이 기본 동작을 사용하면 사용자가 장시간 오프라인 상태일 때 오프라인 쿼리 성능이 저하될 수 있습니다.

로컬 쿼리 색인을 구성하여 오프라인 쿼리의 성능을 향상시킬 수 있습니다.

Swift

Apple 플랫폼 SDK는 서버에서 색인을 구성하는 데 사용되는 동일한 JSON 구조의 구성을 읽고 동일한 색인 정의 형식을 따르는 setIndexConfiguration 메서드를 제공합니다.

// You will normally read this from a file asset or cloud storage.
let indexConfigJson = """
  {
    indexes: [
        ...
    ],
    fieldOverrides: [
        ...
    ]
  }
"""

// Apply the configuration.
Firestore.firestore().setIndexConfiguration(indexConfigJson)
Objective-C

Apple 플랫폼 SDK는 서버에서 색인을 구성하는 데 사용되는 동일한 JSON 구조의 구성을 읽고 동일한 색인 정의 형식을 따르는 setIndexConfiguration- 메서드를 제공합니다.

// You will normally read this from a file asset or cloud storage.
NSString *indexConfigJson = @" {                   "
                             "   indexes: [        "
                             "     ...             "
                             "   ],                "
                             "   fieldOverrides: [ "
                             "     ...             "
                             "   ]                 "
                             " }                   ";

// Apply the configuration.
[[FIRFirestore firestore] setIndexConfigurationFromJSON:indexConfigJson
                                             completion:^(NSError * _Nullable error) {
    // ...
}];

Java

Android SDK는 서버에서 색인을 구성하는 데 사용되는 동일한 JSON 구조의 구성을 읽고 동일한 색인 정의 형식을 따르는 setIndexConfiguration 메서드를 제공합니다.

// You will normally read this from a file asset or cloud storage.
String indexConfigJson = " {                   "
                       + "   indexes: [        "
                       + "     ...             "
                       + "   ],                "
                       + "   fieldOverrides: [ "
                       + "     ...             "
                       + "   ]                 "
                       + " }                   ";
// Apply the configuration.
FirebaseFirestore.getInstance().setIndexConfiguration(indexConfigJson);

Kotlin+KTX

Android SDK는 서버에서 색인을 구성하는 데 사용되는 동일한 JSON 구조의 구성을 읽고 동일한 색인 정의 형식을 따르는 setIndexConfiguration 메서드를 제공합니다.

// You will normally read this from a file asset or cloud storage.
val indexConfigJson = """
{
  indexes: [
      ...
  ],
  fieldOverrides: [
      ...
  ]
}
"""

// Apply the configuration.
FirebaseFirestore.getInstance().setIndexConfiguration(indexConfigJson)

Dart

Flutter SDK는 서버에서 색인을 구성하는 데 사용되는 동일한 JSON 구조의 구성을 읽고 동일한 색인 정의 형식을 따르는 setIndexConfigurationFromJSON 메서드를 제공합니다.

// You will normally read this from a file asset or cloud storage.
var indexConfigJson = """
{
  indexes: [
      ...
  ],
  fieldOverrides: [
      ...
  ]
}
""";

// Apply the configuration.
await FirebaseFirestore.instance.setIndexConfigurationFromJSON(json: indexConfigJson);

또는 setIndexConfiguration 메서드를 사용하여 클래스 기반 API로 색인을 구성할 수 있습니다.

var indexes = [
  Index(
    collectionGroup: "posts",
    queryScope: QueryScope.collection,
    fields: [
      IndexField(fieldPath: "author", arrayConfig: ArrayConfig.contains),
      IndexField(fieldPath: "timestamp", order: Order.descending)
    ],
  ),
];
await FirebaseFirestore.instance.setIndexConfiguration(indexes: indexes);

사용할 오프라인 색인 구성은 오프라인 상태일 때 앱에서 많이 액세스하는 컬렉션과 문서 및 원하는 오프라인 성능에 따라 다릅니다. 백엔드 색인 구성을 클라이언트에서 사용하도록 내보낼 수 있지만 앱의 오프라인 액세스 패턴은 온라인 액세스 패턴과 크게 다를 수 있으므로 온라인 색인 구성은 오프라인에서 사용하기에 적합하지 않을 수 있습니다. 앱에서 고성능으로 오프라인 액세스하려는 컬렉션과 문서는 무엇인가요? 앱의 동작을 분석한 후에는 색인 생성 가이드의 색인 정의 원칙을 따릅니다.

클라이언트 앱에서 오프라인 색인 구성을 로드할 수 있도록 하려면 다음 안내를 따르세요.

  • 앱에서 컴파일하고 배포합니다.
  • CDN에서 다운로드합니다.
  • Firebase용 Cloud Storage와 같은 스토리지 시스템에서 가져옵니다.

네트워크 액세스 중지 및 사용 설정

아래의 방법을 사용하여 Cloud Firestore 클라이언트에 대한 네트워크 액세스를 중지할 수 있습니다. 네트워크 액세스가 중지되어 있으면 모든 스냅샷 리스너와 문서 요청이 캐시에서 결과를 검색합니다. 쓰기 작업은 네트워크 액세스가 다시 사용 설정될 때까지 큐에 추가됩니다.

Web version 9

import { disableNetwork } from "firebase/firestore";

await disableNetwork(db);
console.log("Network disabled!");
// Do offline actions
// ...

Web version 8

firebase.firestore().disableNetwork()
    .then(() => {
        // Do offline actions
        // ...
    });
Swift
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
Firestore.firestore().disableNetwork { (error) in
    // Do offline things
    // ...
}
Objective-C
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
[[FIRFirestore firestore] disableNetworkWithCompletion:^(NSError *_Nullable error) {
  // Do offline actions
  // ...
}];

Kotlin+KTX

db.disableNetwork().addOnCompleteListener {
    // Do offline things
    // ...
}

Java

db.disableNetwork()
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Do offline things
                // ...
            }
        });

Dart

db.disableNetwork().then((_) {
  // Do offline things
});

네트워크 액세스를 다시 사용 설정하려면 다음 방법을 사용하세요.

Web version 9

import { enableNetwork } from "firebase/firestore";

await enableNetwork(db);
// Do online actions
// ...

Web version 8

firebase.firestore().enableNetwork()
    .then(() => {
        // Do online actions
        // ...
    });
Swift
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
Firestore.firestore().enableNetwork { (error) in
    // Do online things
    // ...
}
Objective-C
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
[[FIRFirestore firestore] enableNetworkWithCompletion:^(NSError *_Nullable error) {
  // Do online actions
  // ...
}];

Kotlin+KTX

db.enableNetwork().addOnCompleteListener {
    // Do online things
    // ...
}

Java

db.enableNetwork()
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Do online things
                // ...
            }
        });

Dart

db.enableNetwork().then((_) {
  // Back online
});