source

파이어스토어에 많은 문서를 작성하는 가장 빠른 방법은 무엇입니까?

factcode 2023. 6. 23. 22:28
반응형

파이어스토어에 많은 문서를 작성하는 가장 빠른 방법은 무엇입니까?

저는 파이어스토어에 많은 문서를 작성해야 합니다.

Node.js에서 가장 빠른 방법은 무엇입니까?

TL;DR: Firestore에서 대량 날짜 생성을 수행하는 가장 빠른 방법은 병렬 개별 쓰기 작업을 수행하는 것입니다.

Firestore에 1,000개의 문서를 작성하는 데 필요한 작업:

  1. ~105.4s 개별 을 사용하는 에는 다음과 같이 합니다.
  2. ~ 2.8s(2) 일괄 처리된 쓰기 작업을 사용하는 경우
  3. ~ 1.5s 쓰기 에는 사용할 수 없습니다.

Firestore에서 많은 수의 쓰기 작업을 수행하는 세 가지 일반적인 방법이 있습니다.

  1. 각 개별 쓰기 작업을 순서대로 수행합니다.
  2. 일괄 처리된 쓰기 작업 사용.
  3. 개별 쓰기 작업을 병렬로 수행합니다.

랜덤화된 문서 데이터 배열을 사용하여 아래에서 차례로 각 데이터를 조사합니다.


개별 순차 쓰기 작업

이것은 가능한 가장 간단한 해결책입니다.

async function testSequentialIndividualWrites(datas) {
  while (datas.length) {
    await collection.add(datas.shift());
  }
}

우리는 모든 문서를 작성할 때까지 각 문서를 차례로 작성합니다.각 쓰기 작업이 완료될 때까지 기다렸다가 다음 쓰기 작업을 시작합니다.

이 방법을 사용하면 문서 1,000개를 작성하는 데 약 105초가 소요되므로 처리량은 초당10개의 문서 쓰기가 가능합니다.


일괄 처리된 쓰기 작업 사용

이것이 가장 복잡한 해결책입니다.

async function testBatchedWrites(datas) {
  let batch = admin.firestore().batch();
  let count = 0;
  while (datas.length) {
    batch.set(collection.doc(Math.random().toString(36).substring(2, 15)), datas.shift());
    if (++count >= 500 || !datas.length) {
      await batch.commit();
      batch = admin.firestore().batch();
      count = 0;
    }
  }
}

가 다과같생수있다습니성할이를 만드는 을 볼 수 .BatchedWrite전화로 반대합니다.batch()최대 500개의 문서 용량까지 채우고 Firestore에 기록합니다.각 문서에 고유할 가능성이 높은 생성된 이름을 지정합니다(이 테스트에 적합).

이 방법으로 1,000개의 문서를 작성하는 데 약 2.8초가 소요되므로 초당 약 357개의 문서를 작성할 수 있습니다.

이는 순차적인 개별 쓰기 작업보다 훨씬 빠릅니다.사실, 많은 개발자들은 이 접근 방식이 가장 빠르다고 생각하기 때문에 이 접근 방식을 사용하지만, 위의 결과가 이미 보여주었듯이 이것은 사실이 아닙니다.코드는 배치의 크기 제약으로 인해 가장 복잡합니다.


병렬 개별 쓰기 작업

Firestore 설명서에는 많은 데이터를 추가할 때의 성능에 대해 다음과 같이 나와 있습니다.

대량 데이터 입력의 경우 병렬로 개별 쓰기가 가능한 서버 클라이언트 라이브러리를 사용합니다.일괄 처리된 쓰기는 직렬화된 쓰기보다 성능이 우수하지만 병렬 쓰기보다는 성능이 우수하지 않습니다.

다음 코드를 사용하여 테스트할 수 있습니다.

async function testParallelIndividualWrites(datas) {
  await Promise.all(datas.map((data) => collection.add(data)));
}

이 코드는 다음과 같이 시작됩니다.add한 다음 합니다.Promise.all()모든 것이 끝날 때까지 기다리는 것.이 접근 방식을 사용하면 작업을 병렬로 실행할 수 있습니다.

이 방법으로 1,000개의 문서를 작성하는 데 약 1.5초가 소요되므로 초당667개의 문서를 작성할 수 있습니다.

처음 두 가지 접근 방식의 차이는 크지 않지만 여전히 일괄적인 쓰기 방식보다 1.8배 이상 빠릅니다.


몇 가지 참고 사항:

  • 이 테스트의 전체 코드는 Github에서 확인할 수 있습니다.
  • 테스트가 Node.js로 수행되었지만 관리 SDK가 지원하는 모든 플랫폼에서 유사한 결과를 얻을 수 있습니다.
  • 그러나 클라이언트 SDK를 사용하여 대량 삽입을 수행하지 마십시오. 결과가 매우 다르고 예측 가능성이 훨씬 낮을 수 있습니다.
  • 일반적으로 실제 성능은 컴퓨터, 인터넷 연결의 대역폭 및 지연 시간 및 기타 많은 요인에 따라 달라집니다.그것들을 바탕으로 당신은 또한 차이를 볼 수 있지만, 저는 주문이 동일할 것으로 예상합니다.
  • 자체 검정에 특이치가 있거나 완전히 다른 결과가 발견되면 아래에 주석을 남깁니다.
  • 일괄 처리된 쓰기는 원자적입니다.따라서 문서 간에 종속성이 있고 모든 문서를 작성해야 하거나 작성할 필요가 없는 경우 일괄 작성을 사용해야 합니다.

OP에 대한 코멘트에서 언급했듯이, 저는 클라우드 함수 내부의 Firestore에 문서를 작성할 때 반대의 경험을 했습니다.

TL;DR: Firestore에 1200개의 문서를 쓸 때 병렬 개별 쓰기가 병렬 배치 쓰기보다 5배 이상 느립니다.

제가 생각할 수 있는 유일한 설명은 구글 클라우드 기능과 Firestore 사이에서 발생하는 일종의 병목 현상이나 요청 속도 제한입니다.그건 좀 미스터리야.

다음은 제가 벤치마킹한 두 가지 방법의 코드입니다.

const functions = require('firebase-functions');
const admin = require('firebase-admin');


admin.initializeApp();
const db = admin.firestore();


// Parallel Batch Writes
exports.cloneAppBatch = functions.https.onCall((data, context) => {

    return new Promise((resolve, reject) => {

        let fromAppKey = data.appKey;
        let toAppKey = db.collection('/app').doc().id;


        // Clone/copy data from one app subcollection to another
        let startTimeMs = Date.now();
        let docs = 0;

        // Write the app document (and ensure cold start doesn't affect timings below)
        db.collection('/app').doc(toAppKey).set({ desc: 'New App' }).then(() => {

            // Log Benchmark
            functions.logger.info(`[BATCH] 'Write App Config Doc' took ${Date.now() - startTimeMs}ms`);


            // Get all documents in app subcollection
            startTimeMs = Date.now();

            return db.collection(`/app/${fromAppKey}/data`).get();

        }).then(appDataQS => {

            // Log Benchmark
            functions.logger.info(`[BATCH] 'Read App Data' took ${Date.now() - startTimeMs}ms`);


            // Batch up documents and write to new app subcollection
            startTimeMs = Date.now();

            let commits = [];
            let bDocCtr = 0;
            let batch = db.batch();

            appDataQS.forEach(docSnap => {

                let doc = docSnap.data();
                let docKey = docSnap.id;
                docs++;

                let docRef = db.collection(`/app/${toAppKey}/data`).doc(docKey);

                batch.set(docRef, doc);
                bDocCtr++

                if (bDocCtr >= 500) {
                    commits.push(batch.commit());
                    batch = db.batch();
                    bDocCtr = 0;
                }

            });

            if (bDocCtr > 0) commits.push(batch.commit());

            Promise.all(commits).then(results => {
                // Log Benchmark
                functions.logger.info(`[BATCH] 'Write App Data - ${docs} docs / ${commits.length} batches' took ${Date.now() - startTimeMs}ms`);
                resolve(results);
            });
         
        }).catch(err => {
            reject(err);
        });

    });

});


// Parallel Individual Writes
exports.cloneAppNoBatch = functions.https.onCall((data, context) => {

    return new Promise((resolve, reject) => {

        let fromAppKey = data.appKey;
        let toAppKey = db.collection('/app').doc().id;


        // Clone/copy data from one app subcollection to another
        let startTimeMs = Date.now();
        let docs = 0;

        // Write the app document (and ensure cold start doesn't affect timings below)
        db.collection('/app').doc(toAppKey).set({ desc: 'New App' }).then(() => {

            // Log Benchmark
            functions.logger.info(`[INDIVIDUAL] 'Write App Config Doc' took ${Date.now() - startTimeMs}ms`);


            // Get all documents in app subcollection
            startTimeMs = Date.now();

            return db.collection(`/app/${fromAppKey}/data`).get();

        }).then(appDataQS => {

            // Log Benchmark
            functions.logger.info(`[INDIVIDUAL] 'Read App Data' took ${Date.now() - startTimeMs}ms`);


            // Gather up documents and write to new app subcollection
            startTimeMs = Date.now();

            let commits = [];

            appDataQS.forEach(docSnap => {

                let doc = docSnap.data();
                let docKey = docSnap.id;
                docs++;
                    
                // Parallel individual writes
                commits.push(db.collection(`/app/${toAppKey}/data`).doc(docKey).set(doc));
        
            });

            Promise.all(commits).then(results => {
                // Log Benchmark
                functions.logger.info(`[INDIVIDUAL] 'Write App Data - ${docs} docs' took ${Date.now() - startTimeMs}ms`);
                resolve(results);
            });
         
        }).catch(err => {
            reject(err);
        });

    });

});

구체적인 결과는 다음과 같습니다(각각 평균 3회 런).

배치 쓰기:

1200개 문서 읽기 - 2.4초 / 1200개 문서 쓰기 - 1.8초

개별 쓰기:

1200개 문서 읽기 - 2.4초 / 1200개 문서 쓰기 - 10.5초

참고: 이 결과는 일전에 받은 결과보다 훨씬 낫습니다. 아마도 Google이 좋지 않은 하루를 보내고 있었을 것입니다. 그러나 배치와 개별 쓰기 간의 상대적인 성능은 그대로 유지됩니다.비슷한 경험을 한 사람이 있는지 확인해 보는 것이 좋을 것 같습니다.

저는 https://github.com/stpch/firestore-multibatch 에서 언급한 병렬 배치 작업을 구현하는 작은 라이브러리를 발견했습니다.간단한 인터페이스를 제공하므로 500 op 제한에 대한 걱정 없이 배치에 계속 추가할 수 있습니다.

언급URL : https://stackoverflow.com/questions/58897274/what-is-the-fastest-way-to-write-a-lot-of-documents-to-firestore

반응형