Firebase 클라우드 기능이 매우 느립니다.


131

새로운 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;
}

위의 'once ()'호출의 약속을 반환하지 않는 것이 안전합니까?
jazzgil

답변:


111

여기 firebaser

소위 콜드 스타트 ​​기능을 경험 한 것 같습니다.

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

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

좋은 소식은 개발 중에 만 이것을 경험해야한다는 것입니다. 기능이 프로덕션에서 자주 트리거되면 다시 콜드 스타트를 할 가능성이 거의 없습니다.


3
중재자 참고 :이 게시물에 대한 모든 주제에서 벗어난 댓글이 삭제되었습니다. 설명을 사용하여 설명을 요청하거나 개선 사항 만 제안하십시오. 관련이 있지만 다른 질문이있는 경우 새 질문 을하고이 질문 에 대한 링크를 포함하여 컨텍스트를 제공하세요.
Bhargav Rao

55

2020 년 5 월 업데이트 maganap의 의견을 보내 주셔서 감사합니다-Node 10+ FUNCTION_NAME에서 K_SERVICE( FUNCTION_TARGET은 함수 자체가 이름이 아니라ENTRY_POINT 입니다.). 아래 코드 샘플은 아래에 업데이트되었습니다.

https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes 에서 자세한 정보

업데이트 -https process.env.FUNCTION_NAME: //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');
}

이것은 해당 함수가 특별히 호출 될 때만 필요한 파일을로드합니다. 글로벌 스코프를 훨씬 깨끗하게 유지하여 콜드 부팅이 빨라집니다.


이것은 내가 아래에서 한 것보다 훨씬 더 깔끔한 솔루션을 허용합니다 (아래 설명은 여전히 ​​유효합니다).


원래 답변

전역 범위에서 발생하는 파일 및 일반 초기화가 콜드 부팅 중에 느려지는 큰 원인 인 것 같습니다.

프로젝트가 더 많은 기능을수록 글로벌 범위는 문제가 더 악화 점점 더 오염되어 - 특히 범위 경우 기능을 별도의 파일로 (예를 사용하여 같은 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 팀이 향후 적절한 기능 범위 지정을 허용하여 각 기능에 대해 관련 모듈 만로드하면되는 몇 가지 계획이 있기를 바랍니다.


안녕하세요 Tyris, 시간 운영과 동일한 문제에 직면하고 있으며 솔루션을 구현하려고합니다. 이해하려고, 누가 언제 초기화 함수를 호출합니까?
Manspof

안녕하세요 @AdirZoari, init () 등을 사용하는 것에 대한 설명은 아마도 모범 사례가 아닐 것입니다. 그 가치는 핵심 문제에 대한 나의 발견을 보여주는 것입니다. 숨겨진 변수를보고 process.env.FUNCTION_NAME해당 함수에 필요한 파일을 조건부로 포함하는 데 사용하는 것이 훨씬 낫습니다 . github.com/firebase/functions-samples/issues/… 의 주석은 이 작업에 대한 정말 좋은 설명을 제공합니다! 전역 범위가 메서드로 오염되지 않고 관련없는 함수에서 포함되도록합니다.
Tyris

1
안녕하세요 @davidverweij, 두 번 또는 병렬로 실행되는 함수의 가능성 측면에서 도움이 될 것이라고 생각하지 않습니다. 기능은 필요에 따라 자동 확장되므로 여러 기능 (동일한 기능 또는 다른 기능)을 언제든지 병렬로 실행할 수 있습니다. 즉, 데이터 안전을 고려하고 트랜잭션 사용을 고려해야합니다. 또한 두 번 실행될 수있는 함수에 대한이 기사를 확인하십시오. cloud.google.com/blog/products/serverless/…
Tyris

1
알림 FUNCTIONS_NAMEcloud.google.com/functions/docs/…에 설명 된대로 노드 6 및 8에서만 유효합니다 . 노드 10이 사용해야 함FUNCTION_TARGET
maganap

1
@maganap을 업데이트 해 주셔서 감사 K_SERVICE합니다. doco ( cloud.google.com/functions/docs/migrating/…) 에 따라 사용해야하는 것 같습니다 .-답변을 업데이트했습니다.
Tyris

7

Firestore 클라우드 기능과 유사한 문제에 직면하고 있습니다. 가장 큰 것은 성능입니다. 특히 초기 고객이 "부진한"앱을 볼 여유가없는 초기 단계의 스타트 업의 경우. 예를 들어 간단한 문서 생성 기능은 다음을 제공합니다.

-함수 실행에 9522ms 소요, 상태 코드 : 200으로 완료

그때 : 나는 전면적 인 이용 약관 페이지를 가지고있었습니다. 클라우드 기능을 사용하면 콜드 스타트로 인한 실행에 때때로 10-15 초가 걸립니다. 그런 다음 appengine 컨테이너에서 호스팅되는 node.js 앱으로 옮겼습니다. 시간이 2-3 초로 단축되었습니다.

나는 mongodb의 많은 기능을 firestore와 비교해 왔으며 때로는 제품의 초기 단계에서 다른 데이터베이스로 이동해야할지 궁금합니다. 내가 firestore에서 가진 가장 큰 진보는 문서 객체의 onCreate, onUpdate 트리거 기능이었습니다.

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

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


1
Firebase 함수가 동적 사용자 대상 콘텐츠를 표시하는 한 목적에 적합하지 않다고 생각합니다. 비밀번호 재설정과 같은 작업에는 HTTP 기능을 조금만 사용하지만 일반적으로 동적 콘텐츠가있는 경우 다른 곳에서 익스프레스 앱으로 제공하거나 다른 언어를 사용합니다.
Tyris

2

기능이 예열되면 성능이 향상되지만 콜드 스타트가 나를 죽이고 있습니다. 내가 만난 다른 문제 중 하나는 cors와 관련된 것입니다. 작업을 완료하려면 클라우드 기능으로 두 번 이동해야하기 때문입니다. 그래도 고칠 수 있다고 확신합니다.

앱을 자주 사용하지 않는 초기 (데모) 단계에 있으면 성능이 좋지 않을 것입니다. 초기 제품을 가진 얼리 어답터는 잠재 고객 / 투자자 앞에서 가장 잘 보일 필요가 있기 때문에 이것은 고려해야 할 사항입니다. 우리는 기술이 마음에 들었 기 때문에 기존의 검증 된 프레임 워크에서 마이그레이션했지만이 시점에서는 앱이 상당히 느려 보입니다. 다음으로 더보기 좋게 만들기 위해 몇 가지 준비 전략을 시도해 보겠습니다.


우리는 모든 단일 기능을 깨우기 위해 cron-job을 테스트하고 있습니다. 이 접근 방식이 도움이 될 수도 있습니다.
Jesús Fuentes

안녕하세요 @ JesúsFuentes 기능이 작동하는지 궁금합니다. 미친 솔루션과 같은 소리 : D
알렉산드르 Zavalii

1
안녕 @Alexandr, 슬프게도 아직 할 시간이 없었지만 최우선 순위 목록에 있습니다. 하지만 이론적으로는 작동합니다. 문제는 Firebase 앱에서 시작해야하는 onCall 함수와 함께 발생합니다. X 분마다 클라이언트에서 전화를 걸까요? 우리는 볼 것이다.
Jesús Fuentes

1
@Alexandr Stackoverflow 밖에서 대화를 나눌까요? 우리는 새로운 접근 방식으로 서로를 도울 수 있습니다.
Jesús Fuentes

1
@Alexandr 우리는 아직이 'wakeup'해결 방법을 테스트하지 않았지만 이미 europe-west1에 함수를 배포했습니다. 여전히 용납 할 수없는 시간.
Jesús Fuentes

0

업데이트 / 편집 : 2020 년 5 월 새로운 구문 및 업데이트 예정

방금이라는 패키지를 게시 better-firebase-functions했습니다. 자동으로 함수 디렉터리를 검색하고 찾은 모든 함수를 내보내기 개체에 올바르게 중첩하는 동시에 콜드 부팅 성능을 향상시키기 위해 함수를 서로 분리합니다.

모듈 범위 내에서 각 함수에 필요한 종속성 만 지연로드하고 캐시하는 경우 빠르게 성장하는 프로젝트에서 함수를 최적의 상태로 유지하는 가장 간단하고 쉬운 방법입니다.

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

흥미로운 .. 'better-firebase-functions'의 저장소는 어디에서 볼 수 있습니까?
JerryGoyal

1
github.com/gramstr/better-firebase-functions- 확인 하시고 어떻게 생각하는지 알려주세요! 또한
기여해 주시기 바랍니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.