[firebase] Cloud Firestore 수집 수

컬렉션에서 새로운 Firebase 데이터베이스 인 Cloud Firestore를 사용하는 항목 수를 계산할 수 있습니까?

그렇다면 어떻게해야합니까?



답변


업데이트 (2019 년 4 월)-FieldValue.increment (큰 수집 솔루션 참조)


많은 질문과 마찬가지로, 대답은 – 그것은 의존한다 .

프런트 엔드에서 대량의 데이터를 처리 할 때는 매우주의해야합니다. Firestore는 프론트 엔드를 느리게 만드는 것 외에도 백만 번의 읽기 당 0.60 달러를 청구합니다 .


소장 (100 개 미만의 서류)

주의해서 사용-프론트 엔드 사용자 경험에 영향을 줄 수 있음

이 반환 된 배열에 대해 너무 많은 논리를 수행하지 않는 한 프런트 엔드에서이를 처리하는 것이 좋습니다.

db.collection('...').get().then(snap => {
   size = snap.size // will return the collection size
});

중간 수집 (100 ~ 1000 개 문서)

주의해서 사용-Firestore 읽기 호출에는 많은 비용이 소요될 수 있습니다

프런트 엔드에서이를 처리하는 것은 사용자 시스템 속도를 늦출 가능성이 너무 높기 때문에 실현 불가능합니다. 이 논리 서버 측을 처리하고 크기 만 반환해야합니다.

이 방법의 단점은 여전히 ​​Firestore 읽기 (컬렉션 크기와 동일)를 호출하는 것입니다. 장기적으로는 예상보다 많은 비용이 발생할 수 있습니다.

클라우드 기능 :

...
db.collection('...').get().then(snap => {
    res.status(200).send({length: snap.size});
});

프런트 엔드 :

yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
     size = snap.length // will return the collection size
})

대규모 수집 (1000 개 이상의 문서)

가장 확장 가능한 솔루션


FieldValue.increment ()

2019 년 4 월부터 Firestore는 이제 이전의 데이터를 읽지 않고도 카운터를 완전히 원자 적으로 증가시킬 수 있습니다 . 이를 통해 여러 소스에서 동시에 업데이트 할 때 (이전에 트랜잭션을 사용하여 해결 한 경우) 올바른 카운터 값을 유지하면서 수행하는 데이터베이스 읽기 수를 줄일 수 있습니다.


문서의 삭제 또는 생성을 수신함으로써 데이터베이스에있는 카운트 필드에 추가하거나 제거 할 수 있습니다.

firestore 문서- 분산 카운터를 참조
하거나 Jeff Delaney의 데이터 집계 를 살펴보십시오 . 그의 가이드는 AngularFire를 사용하는 모든 사람에게 정말 환상적이지만 그의 교훈은 다른 프레임 워크에도 적용됩니다.

클라우드 기능 :

export const documentWriteListener =
    functions.firestore.document('collection/{documentUid}')
    .onWrite((change, context) => {

    if (!change.before.exists) {
        // New document Created : add one to count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(1)});

    } else if (change.before.exists && change.after.exists) {
        // Updating existing document : Do nothing

    } else if (!change.after.exists) {
        // Deleting document : subtract one from count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(-1)});

    }

return;
});

이제 프론트 엔드에서이 numberOfDocs 필드를 쿼리하여 컬렉션의 크기를 얻을 수 있습니다.


답변

가장 간단한 방법은 “querySnapshot”의 크기를 읽는 것입니다.

db.collection("cities").get().then(function(querySnapshot) {
    console.log(querySnapshot.size);
});

“querySnapshot”내 문서 배열의 길이를 읽을 수도 있습니다.

querySnapshot.docs.length;

또는 빈 값을 읽어 “querySnapshot”이 비어 있으면 부울 값이 반환됩니다.

querySnapshot.empty;


답변

내가 아는 한 여기에는 내장 솔루션이 없으며 노드 sdk에서만 가능합니다. 당신이 있다면

db.collection('someCollection')

당신이 사용할 수있는

.select([fields])

선택할 필드를 정의합니다. 빈 select ()를 수행하면 문서 참조 배열을 얻을 수 있습니다.

예:

db.collection('someCollection').select().get().then(
(snapshot) => console.log(snapshot.docs.length)
);

이 솔루션은 모든 문서를 다운로드하는 최악의 경우를위한 최적화 일 뿐이며 대규모 컬렉션에는 적용되지 않습니다!

또한 이것 좀 봐 :
클라우드 경우 FireStore와 컬렉션의 문서 수의 수를 얻는 방법


답변

큰 컬렉션 의 문서 수를 세어주의하십시오 . 모든 컬렉션에 대해 미리 계산 된 카운터를 원하면 firestore 데이터베이스와 약간 복잡합니다.

이 경우 다음과 같은 코드가 작동하지 않습니다.

export const customerCounterListener =
    functions.firestore.document('customers/{customerId}')
    .onWrite((change, context) => {

    // on create
    if (!change.before.exists && change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count + 1
                     }))
    // on delete
    } else if (change.before.exists && !change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count - 1
                     }))
    }

    return null;
});

https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees : Firestore 문서에서 알 수 있듯이 모든 클라우드 Firestore 트리거는 dem 등원이어야하기 때문입니다.

해결책

따라서 코드가 여러 번 실행되지 않도록하려면 이벤트 및 트랜잭션으로 관리해야합니다. 이것은 대규모 수집 카운터를 처리하는 특별한 방법입니다.

const executeOnce = (change, context, task) => {
    const eventRef = firestore.collection('events').doc(context.eventId);

    return firestore.runTransaction(t =>
        t
         .get(eventRef)
         .then(docSnap => (docSnap.exists ? null : task(t)))
         .then(() => t.set(eventRef, { processed: true }))
    );
};

const documentCounter = collectionName => (change, context) =>
    executeOnce(change, context, t => {
        // on create
        if (!change.before.exists && change.after.exists) {
            return t
                    .get(firestore.collection('metadatas')
                    .doc(collectionName))
                    .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: ((docSnap.data() && docSnap.data().count) || 0) + 1
                        }));
        // on delete
        } else if (change.before.exists && !change.after.exists) {
            return t
                     .get(firestore.collection('metadatas')
                     .doc(collectionName))
                     .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: docSnap.data().count - 1
                        }));
        }

        return null;
    });

사용 사례는 다음과 같습니다.

/**
 * Count documents in articles collection.
 */
exports.articlesCounter = functions.firestore
    .document('articles/{id}')
    .onWrite(documentCounter('articles'));

/**
 * Count documents in customers collection.
 */
exports.customersCounter = functions.firestore
    .document('customers/{id}')
    .onWrite(documentCounter('customers'));

보다시피, 다중 실행을 방지하는 핵심 은 컨텍스트 객체에서 eventId 라는 속성 입니다. 동일한 이벤트에 대해 함수가 여러 번 처리 된 경우 이벤트 ID는 모든 경우에 동일합니다. 불행히도 데이터베이스에는 “이벤트”컬렉션이 있어야합니다.


답변

2020 년에는 여전히 Firebase SDK에서 사용할 수 없지만 Firebase 확장 (베타) 에서는 사용할 수 있지만 설정 및 사용이 매우 복잡합니다.

합리적인 접근

도우미 … (만들기 / 삭제가 중복 된 것처럼 보이지만 onUpdate보다 저렴함)

export const onCreateCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(1);
  await statsDoc.set(countDoc, { merge: true });
};

export const onDeleteCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(-1);
  await statsDoc.set(countDoc, { merge: true });
};

export interface CounterPath {
  watch: string;
  name: string;
}

Firestore 후크 내보내기


export const Counters: CounterPath[] = [
  {
    name: "count_buildings",
    watch: "buildings/{id2}"
  },
  {
    name: "count_buildings_subcollections",
    watch: "buildings/{id2}/{id3}/{id4}"
  }
];


Counters.forEach(item => {
  exports[item.name + '_create'] = functions.firestore
    .document(item.watch)
    .onCreate(onCreateCounter());

  exports[item.name + '_delete'] = functions.firestore
    .document(item.watch)
    .onDelete(onDeleteCounter());
});

행동

건물 루트 수집 및 모든 하위 수집 이 추적됩니다.

여기에 이미지 설명을 입력하십시오

여기 /counters/루트 경로 아래

여기에 이미지 설명을 입력하십시오

이제 수집 횟수가 자동으로 업데이트됩니다! 카운트가 필요한 경우 컬렉션 경로를 사용하고 접두사를 붙입니다 counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));


답변

@Matthew에 동의 합니다. 이러한 쿼리를 수행하면 비용이 많이 듭니다 .

[프로젝트를 시작하기 전에 개발자를위한 조언]

우리는이 상황을 처음에 예상 했으므로 실제로 모든 카운터를 type 필드에 저장하기 위해 문서와 함께 카운터를 만들 수 있습니다 number.

예를 들면 다음과 같습니다.

콜렉션의 각 CRUD 조작에 대해 카운터 문서를 업데이트하십시오.

  1. 새로운 컬렉션 / 서브 컬렉션 을 생성 할 때 : (카운터에서 +1) [1 쓰기 작업]
  2. 컬렉션 / 서브 컬렉션 을 삭제 하는 경우 : (카운터에서 -1) [1 쓰기 작업]
  3. 기존 수집 / 하위 수집 을 업데이트 할 때는 카운터 문서에서 아무것도하지 마십시오. (0)
  4. 때를 기존 수집 / 하위 수집 읽을 카운터 문서에서 아무것도하지 마십시오 : (0)

다음에 컬렉션 수를 얻으려면 문서 필드를 쿼리하거나 가리켜 야합니다. [1 회 읽기 동작]

또한 컬렉션 이름을 배열에 저장할 수 있지만 까다로울 수 있습니다. firebase의 배열 조건은 다음과 같습니다.

// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}

// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']

// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}

따라서 컬렉션을 삭제하지 않으려는 경우 실제로 모든 컬렉션을 쿼리 할 때마다 배열을 사용하여 컬렉션 목록 이름을 저장할 수 있습니다.

그것이 도움이되기를 바랍니다!


답변

아니요, 현재 집계 쿼리를 기본적으로 지원하지 않습니다. 그러나 할 수있는 일이 몇 가지 있습니다.

첫 번째는 여기문서화되어 있습니다 . 트랜잭션 또는 클라우드 기능을 사용하여 집계 정보를 유지 보수 할 수 있습니다.

이 예는 함수를 사용하여 하위 등급의 등급 수와 평균 등급을 추적하는 방법을 보여줍니다.

exports.aggregateRatings = firestore
  .document('restaurants/{restId}/ratings/{ratingId}')
  .onWrite(event => {
    // Get value of the newly added rating
    var ratingVal = event.data.get('rating');

    // Get a reference to the restaurant
    var restRef = db.collection('restaurants').document(event.params.restId);

    // Update aggregations in a transaction
    return db.transaction(transaction => {
      return transaction.get(restRef).then(restDoc => {
        // Compute new number of ratings
        var newNumRatings = restDoc.data('numRatings') + 1;

        // Compute new average rating
        var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
        var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;

        // Update restaurant info
        return transaction.update(restRef, {
          avgRating: newAvgRating,
          numRatings: newNumRatings
        });
      });
    });
});

jbb가 언급 한 솔루션은 문서를 자주 계산하지 않으려는 경우에도 유용합니다. select()각 문서를 모두 다운로드하지 않도록 명령문 을 사용하십시오 (카운트 만 필요할 때 많은 대역폭이 필요함). select()솔루션은 모바일 앱에서 작동하지 않도록 지금은 서버 SDK에서만 사용할 수 있습니다.