programing

Firebase 클라우드 기능이 매우 느림

powerit 2023. 6. 12. 21:57
반응형

Firebase 클라우드 기능이 매우 느림

우리는 새로운 파이어베이스 클라우드 기능을 사용하는 애플리케이션을 개발하고 있습니다.현재 발생하고 있는 것은 트랜잭션이 대기열 노드에 배치되는 것입니다.그런 다음 함수는 해당 노드를 제거하고 올바른 노드에 배치합니다.이것은 오프라인으로 작업할 수 있기 때문에 구현되었습니다.

우리의 현재 문제는 기능의 속도입니다.기능 자체가 400ms 정도 걸리니 괜찮습니다.그러나 항목이 이미 대기열에 추가되어 있는 동안 기능이 매우 오랜 시간(약 8초)이 걸릴 수 있습니다.

첫 번째 이후에 작업을 한 번 더 수행할 때 서버를 부팅하는 데 시간이 걸릴 것으로 예상됩니다.시간이 훨씬 적게 걸립니다.

이 문제를 해결할 방법이 있습니까?아래에 우리 기능의 코드를 추가했습니다.저희는 이상이 없는 것으로 의심하지만, 만약을 위해 추가했습니다.

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

exports.insertTransaction = functions.database
    .ref('/userPlacePromotionTransactionsQueue/{userKey}/{placeKey}/{promotionKey}/{transactionKey}')
    .onWrite(event => {
        if (event.data.val() == null) return null;

        // get keys
        const userKey = event.params.userKey;
        const placeKey = event.params.placeKey;
        const promotionKey = event.params.promotionKey;
        const transactionKey = event.params.transactionKey;

        // init update object
        const data = {};

        // get the transaction
        const transaction = event.data.val();

        // transfer transaction
        saveTransaction(data, transaction, userKey, placeKey, promotionKey, transactionKey);
        // remove from queue
        data[`/userPlacePromotionTransactionsQueue/${userKey}/${placeKey}/${promotionKey}/${transactionKey}`] = null;

        // fetch promotion
        database.ref(`promotions/${promotionKey}`).once('value', (snapshot) => {
            // Check if the promotion exists.
            if (!snapshot.exists()) {
                return null;
            }

            const promotion = snapshot.val();

            // fetch the current stamp count
            database.ref(`userPromotionStampCount/${userKey}/${promotionKey}`).once('value', (snapshot) => {
                let currentStampCount = 0;
                if (snapshot.exists()) currentStampCount = parseInt(snapshot.val());

                data[`userPromotionStampCount/${userKey}/${promotionKey}`] = currentStampCount + transaction.amount;

                // determines if there are new full cards
                const currentFullcards = Math.floor(currentStampCount > 0 ? currentStampCount / promotion.stamps : 0);
                const newStamps = currentStampCount + transaction.amount;
                const newFullcards = Math.floor(newStamps / promotion.stamps);

                if (newFullcards > currentFullcards) {
                    for (let i = 0; i < (newFullcards - currentFullcards); i++) {
                        const cardTransaction = {
                            action: "pending",
                            promotion_id: promotionKey,
                            user_id: userKey,
                            amount: 0,
                            type: "stamp",
                            date: transaction.date,
                            is_reversed: false
                        };

                        saveTransaction(data, cardTransaction, userKey, placeKey, promotionKey);

                        const completedPromotion = {
                            promotion_id: promotionKey,
                            user_id: userKey,
                            has_used: false,
                            date: admin.database.ServerValue.TIMESTAMP
                        };

                        const promotionPushKey = database
                            .ref()
                            .child(`userPlaceCompletedPromotions/${userKey}/${placeKey}`)
                            .push()
                            .key;

                        data[`userPlaceCompletedPromotions/${userKey}/${placeKey}/${promotionPushKey}`] = completedPromotion;
                        data[`userCompletedPromotions/${userKey}/${promotionPushKey}`] = completedPromotion;
                    }
                }

                return database.ref().update(data);
            }, (error) => {
                // Log to the console if an error happened.
                console.log('The read failed: ' + error.code);
                return null;
            });

        }, (error) => {
            // Log to the console if an error happened.
            console.log('The read failed: ' + error.code);
            return null;
        });
    });

function saveTransaction(data, transaction, userKey, placeKey, promotionKey, transactionKey) {
    if (!transactionKey) {
        transactionKey = database.ref('transactions').push().key;
    }

    data[`transactions/${transactionKey}`] = transaction;
    data[`placeTransactions/${placeKey}/${transactionKey}`] = transaction;
    data[`userPlacePromotionTransactions/${userKey}/${placeKey}/${promotionKey}/${transactionKey}`] = transaction;
}

여기서 불을 뿜습니다.

소위 기능의 콜드 스타트를 경험하고 있는 것처럼 들립니다.

일정 기간 동안 기능이 실행되지 않으면 클라우드 기능은 사용하지 않는 계산 시간에 대한 비용을 지불하지 않도록 더 적은 리소스를 사용하는 모드로 전환됩니다.그런 다음 기능을 다시 누르면 이 모드에서 환경이 복원됩니다.복원에 걸리는 시간은 고정 비용(예: 컨테이너 복원)과 부품 가변 비용(예: 노드 모듈을 많이 사용하는 경우 더 오래 걸릴있음)으로 구성됩니다.

NAT은 개발자 환경과 리소스 사용 간에 최적의 조합을 보장하기 위해 이러한 작업의 성능을 지속적으로 모니터링하고 있습니다.따라서 이러한 시간이 시간이 지남에 따라 개선될 것으로 기대합니다.

좋은 소식은 개발 중에만 이런 경험을 해야 한다는 것입니다.운영 환경에서 기능이 자주 작동되면 특히 트래픽이 일정한 경우 다시는 콜드 스타트를 하지 못할 가능성이 높습니다.그러나 일부 기능에서 트래픽이 급증하는 경향이 있는 경우에도 모든 급증에 대해 콜드 스타트가 계속 표시됩니다.이 경우 지연 시간에 중요한 기능의 설정된 인스턴스 수를 항상 따뜻하게 유지하는 설정을 고려할 수 있습니다.

2021년 3월 업데이트 아래 프로세스 자동화에 대한 깔끔한 솔루션을 제공하는 @George43g의 아래 답변을 확인할 가치가 있을 수 있습니다.참고 - 직접 시도해 본 적이 없으므로 보증할 수는 없지만 여기에 설명된 프로세스가 자동화되는 것 같습니다.자세한 내용은 https://github.com/gramstr/better-firebase-functions 에서 확인할 수 있습니다. 그렇지 않으면 직접 구현하고 기능 내부에서 무슨 일이 일어나고 있는지 이해하는 방법을 계속 읽어 보십시오.

2020년 5월 업데이트 maganap - 노드 10+의 코멘트에 감사드립니다.FUNCTION_NAME는 로대됨으로 됩니다.K_SERVICE(FUNCTION_TARGET이름이 아니라 기능 자체가 대체하는 것입니다.ENTRY_POINT이 아래에서 아래 코드 샘플이 아래에서 업데이트되었습니다.

많은 정보는 https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes 에서 확인할 수 있습니다.

업데이트 - 숨겨진 변수를 사용하여 이러한 많은 문제를 해결할 수 있는 것으로 보입니다.process.env.FUNCTION_NAME여기에서 볼 수 있는 것처럼: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462

코드로 업데이트 - 예를 들어 다음 인덱스 파일이 있는 경우:

...
exports.doSomeThing = require('./doSomeThing');
exports.doSomeThingElse = require('./doSomeThingElse');
exports.doOtherStuff = require('./doOtherStuff');
// and more.......

그러면 모든 파일이 로드되고 해당 파일의 요구 사항도 모두 로드되어 오버헤드가 많이 발생하고 모든 기능에 대한 글로벌 범위가 오염됩니다.

대신 에 포함된 항목을 다음과 같이 구분합니다.

const function_name = process.env.FUNCTION_NAME || process.env.K_SERVICE;
if (!function_name || function_name === 'doSomeThing') {
  exports.doSomeThing = require('./doSomeThing');
}
if (!function_name || function_name === 'doSomeThingElse') {
  exports.doSomeThingElse = require('./doSomeThingElse');
}
if (!function_name || function_name === 'doOtherStuff') {
  exports.doOtherStuff = require('./doOtherStuff');
}

이렇게 하면 해당 기능이 특별히 호출될 때만 필요한 파일이 로드되므로 전역 범위를 훨씬 깨끗하게 유지할 수 있으므로 콜드 부팅 속도가 빨라집니다.


이를 통해 제가 아래에서 수행한 것보다 훨씬 더 깔끔한 솔루션이 가능할 것입니다(아래 설명은 여전히 유효함).


원답

파일 및 일반 초기화를 글로벌 범위에서 요구하는 것은 콜드 부팅 시 속도 저하의 큰 원인인 것으로 보입니다.

가 더 되면 더 심각해집니다. 파일로 를 지정할 경우(예: 제기따에범공점글함라문다심프어니더오제집가해각욱되더염로위로점벌가젝가더트많능은을▁(▁using▁as▁as▁bysuch▁files다심▁you▁into▁gets▁is▁your▁separate니) 특히 기능을 별도의 파일로 범위를 지정하는 경우(예: 사용)Object.assign(exports, require('./more-functions.js'));의 신의에index.js.

아래와 같이 모든 요구 사항을 init 메서드로 이동한 다음 해당 파일에 대한 함수 정의 내의 첫 번째 줄로 호출함으로써 콜드 부트 성능이 크게 향상되었습니다.예:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
// Late initialisers for performance
let initialised = false;
let handlebars;
let fs;
let path;
let encrypt;

function init() {
  if (initialised) { return; }

  handlebars = require('handlebars');
  fs = require('fs');
  path = require('path');
  ({ encrypt } = require('../common'));
  // Maybe do some handlebars compilation here too

  initialised = true;
}

8개의 파일에 걸쳐 30개의 기능이 있는 프로젝트에 이 기술을 적용했을 때 약 7-8에서 2-3으로 개선된 것을 보았습니다.이로 인해 기능의 콜드 부팅 빈도가 감소하는 것으로 보입니다(아마도 메모리 사용량이 적기 때문일 것입니다).

불행하게도 이것은 여전히 HTTP 기능을 사용자 대면 프로덕션 용도로 거의 사용할 수 없게 만듭니다.

향후 Firebase 팀이 각 기능에 대해 관련 모듈만 로드하면 되도록 기능의 적절한 범위를 지정할 수 있는 몇 가지 계획을 수립하기를 바랍니다.

저는 파이어스토어 클라우드 기능과 관련하여 비슷한 문제에 직면해 있습니다.가장 큰 것은 성능입니다.특히 초기 단계 스타트업의 경우, 초기 고객이 "느린" 앱을 볼 여유가 없을 때.다음과 같은 간단한 문서 생성 기능을 사용하면 다음과 같은 이점을 얻을 수 있습니다.

함수 실행에 9522ms가 소요되었으며 상태 코드: 200으로 완료되었습니다.

그 다음: 간단한 약관 페이지가 있었습니다.클라우드 기능을 사용하면 콜드 스타트로 인한 실행 시간이 10-15초 정도 걸릴 수 있습니다.그런 다음 앱 엔진 컨테이너에서 호스팅되는 node.js 앱으로 이동했습니다.시간이 2~3초로 줄었습니다.

저는 mongodb의 많은 특징들을 firestore와 비교해 보았는데, 때때로 저도 제 제품의 이 초기 단계에서 다른 데이터베이스로 옮겨야 하는지 궁금합니다.Firestore에서 가장 큰 장점은 Create의 문서 객체 업데이트에 대한 트리거 기능이었습니다.

https://db-engines.com/en/system/Google+Cloud+Firestore%3BMongoDB

기본적으로 앱 엔진 환경으로 오프로드할 수 있는 사이트의 정적인 부분이 있다면 나쁘지 않을 것입니다.

업데이트: 2022 - lib가 다시 유지됩니다.이제 Firebase는 인스턴스를 따뜻하게 유지할 수 있지만 성능과 코드 구조의 이점이 있습니다.

업데이트/편집: 2020년 5월에 출시될 새로운 구문 및 업데이트

나는 방금 다음과 같은 패키지를 게시했습니다.better-firebase-functions자동으로 함수 디렉터리를 검색하고 검색된 모든 함수를 내보내는 개체에 올바르게 중첩하는 동시에 함수를 서로 격리하여 콜드 부트 성능을 향상시킵니다.

모듈 범위 내에서 각 기능에 필요한 종속성만 느리게 로드하고 캐쉬하면 빠르게 성장하는 프로젝트에 비해 기능을 최적으로 효율적으로 유지하는 가장 간단하고 쉬운 방법을 찾을 수 있습니다.

import { exportFunctions } from 'better-firebase-functions'
exportFunctions({__filename, exports})

기능이 따뜻해지면 성능이 향상되는 이런 것들도 해봤지만 냉랭한 출발이 저를 괴롭힙니다.제가 직면한 또 다른 문제 중 하나는 cors와 관련된 것입니다. 왜냐하면 클라우드 기능을 수행하는 데 두 번의 작업이 필요하기 때문입니다.하지만 제가 고칠 수 있다고 확신합니다.

앱이 자주 사용되지 않을 때 초기(데모) 단계에 있을 때 성능이 좋지 않을 것입니다.초기 제품을 사용하는 얼리 어답터는 잠재 고객/투자자 앞에서 최고의 모습을 보여야 하기 때문에 이는 고려해야 할 사항입니다.우리는 이 기술을 좋아했기 때문에 기존의 검증된 프레임워크에서 마이그레이션했지만, 현재로서는 앱이 상당히 부진한 것 같습니다.다음에는 워밍업 전략을 좀 더 잘 사용해 보겠습니다.

Firebase Functions의 첫 프로젝트에서 간단한 기능이 몇 분 안에 실행되는 매우 저조한 성능을 경험했습니다(기능 실행에 대한 60년대 제한을 알고 있었고, 제 기능에 문제가 있다는 것을 알았습니다).제 경우 문제는 기능을 제대로 종료하지 않았다는 것입니다.

동일한 문제가 발생한 경우 다음을 수행하여 기능을 종료해야 합니다.

  1. HTTP 트리거에 대한 응답 전송
  2. 백그라운드 트리거에 대한 약속 반환

여기 문제를 해결하는 데 도움이 된 파이어베이스의 유튜브 링크가 있습니다.

클라우드 기능은 파이어스토어 라이브러리에서 사용되는 gRpc 라이브러리로 인해 파이어스토어 라이브러리와 함께 사용할 때 콜드 시작 시간이 일정하지 않습니다.

우리는 최근 공식 nodejs-firestore 클라이언트와 병렬로 업데이트하기 위한 완전 호환 Rest 클라이언트(@bountyrush/firestore)를 만들었습니다.

다행히 지금은 콜드 스타트가 훨씬 나아졌고, 이전에 사용했던 redis 메모리 저장소를 캐시로 사용하는 것도 중단했습니다.

통합 단계:

1. npm install @bountyrush/firestore
2. Replace require('@google-cloud/firestore') with require('@bountyrush/firestore')
3. Have FIRESTORE_USE_REST_API = 'true' in your environment variables. (process.env.FIRESTORE_USE_REST_API should be set to 'true' for using in rest mode. If its not set, it just standard firestore with grpc connections)

언급URL : https://stackoverflow.com/questions/42726870/firebase-cloud-functions-is-very-slow

반응형