Chức năng đám mây Firebase rất chậm


131

Chúng tôi đang làm việc trên một ứng dụng sử dụng các chức năng đám mây mới. Những gì hiện đang xảy ra là một giao dịch được đặt trong nút hàng đợi. Và sau đó hàm sẽ loại bỏ nút đó và đặt nó vào đúng nút. Điều này đã được thực hiện vì khả năng làm việc ngoại tuyến.

Vấn đề hiện tại của chúng tôi là tốc độ của chức năng. Các chức năng tự nó mất khoảng 400ms, vì vậy không sao. Nhưng đôi khi các chức năng mất một thời gian rất dài (khoảng 8 giây), trong khi mục nhập đã được thêm vào hàng đợi.

Chúng tôi nghi ngờ rằng máy chủ cần có thời gian để khởi động, bởi vì khi chúng tôi thực hiện hành động một lần nữa sau lần đầu tiên. Nó mất ít thời gian hơn.

Có cách nào để khắc phục vấn đề này? Ở đây tôi đã thêm mã của chức năng của chúng tôi. Chúng tôi nghi ngờ không có gì sai với nó, nhưng chúng tôi đã thêm nó chỉ trong trường hợp.

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;
}

Có an toàn không khi trả lại Lời hứa của các cuộc gọi 'một lần ()' ở trên không?
nhạc jazz

Câu trả lời:


111

xe cứu hỏa ở đây

Có vẻ như bạn đang trải qua cái gọi là khởi động nguội của chức năng.

Khi chức năng của bạn chưa được thực thi trong một thời gian, Cloud Function sẽ đặt nó ở chế độ sử dụng ít tài nguyên hơn. Sau đó, khi bạn nhấn lại chức năng, nó sẽ khôi phục môi trường từ chế độ này. Thời gian cần để khôi phục bao gồm chi phí cố định (ví dụ: khôi phục vùng chứa) và chi phí biến đổi một phần (ví dụ: nếu bạn sử dụng nhiều mô-đun nút, có thể mất nhiều thời gian hơn).

Chúng tôi liên tục theo dõi hiệu suất của các hoạt động này để đảm bảo kết hợp tốt nhất giữa trải nghiệm của nhà phát triển và sử dụng tài nguyên. Vì vậy, mong đợi những thời gian này để cải thiện theo thời gian.

Tin tốt là bạn chỉ nên trải nghiệm điều này trong quá trình phát triển. Khi các chức năng của bạn thường xuyên được kích hoạt trong sản xuất, rất có thể chúng sẽ không bao giờ gặp phải sự khởi đầu lạnh nữa.


3
Người điều hành Lưu ý : Tất cả các bình luận chủ đề ngoài bài viết này đã bị xóa. Vui lòng sử dụng các ý kiến ​​để yêu cầu làm rõ hoặc chỉ đề xuất cải tiến. Nếu bạn có một câu hỏi liên quan nhưng khác nhau, hãy đặt một câu hỏi mới và bao gồm một liên kết đến câu hỏi này để giúp cung cấp ngữ cảnh.
Bhargav Rao

55

Cập nhật tháng 5 năm 2020 Cảm ơn bình luận của maganap - trong Node 10+ FUNCTION_NAMEđược thay thế bằng K_SERVICE( FUNCTION_TARGETlà chính chức năng, không phải tên của nó, thay thếENTRY_POINT ). Mẫu mã dưới đây đã được udpated dưới đây.

Thông tin khác tại https://cloud.google.com/fifts/docs/migrating/nodejs-runtimes#nodejs-10-changes

Cập nhật - có vẻ như rất nhiều vấn đề có thể được giải quyết bằng cách sử dụng biến ẩn process.env.FUNCTION_NAMEnhư đã thấy ở đây: https://github.com/firebase/fifts-samples/issues/170#issuecomment-323375462

Cập nhật bằng mã - Ví dụ: nếu bạn có tệp chỉ mục sau:

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

Sau đó, tất cả các tệp của bạn sẽ được tải và tất cả các yêu cầu của các tệp đó cũng sẽ được tải, dẫn đến rất nhiều chi phí và gây ô nhiễm phạm vi toàn cầu của bạn cho tất cả các chức năng của bạn.

Thay vào đó, tách riêng bạn ra như:

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');
}

Điều này sẽ chỉ tải (các) tệp cần thiết khi chức năng đó được gọi cụ thể; cho phép bạn giữ phạm vi toàn cầu của mình sạch hơn rất nhiều, điều này sẽ giúp khởi động lạnh nhanh hơn.


Điều này sẽ cho phép một giải pháp gọn gàng hơn nhiều so với những gì tôi đã làm dưới đây (mặc dù lời giải thích bên dưới vẫn còn).


Câu trả lời gốc

Có vẻ như yêu cầu các tệp và khởi tạo chung xảy ra trong phạm vi toàn cầu là một nguyên nhân lớn gây chậm trong quá trình khởi động nguội.

Khi một dự án có nhiều chức năng hơn, phạm vi toàn cầu bị ô nhiễm ngày càng nhiều làm cho vấn đề trở nên tồi tệ hơn - đặc biệt là nếu bạn phạm vi các chức năng của mình thành các tệp riêng biệt (chẳng hạn như bằng cách sử dụng Object.assign(exports, require('./more-functions.js'));trong của bạn index.js.

Tôi đã quản lý để thấy mức tăng lớn trong hiệu suất khởi động nguội bằng cách chuyển tất cả các yêu cầu của tôi sang một phương thức init như bên dưới và sau đó gọi nó là dòng đầu tiên bên trong bất kỳ định nghĩa hàm nào cho tệp đó. Ví dụ:

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;
}

Tôi đã thấy các cải tiến từ khoảng 7-8 giây xuống còn 2-3 giây khi áp dụng kỹ thuật này cho một dự án với ~ 30 chức năng trên 8 tệp. Điều này dường như cũng khiến các chức năng cần được khởi động lạnh ít thường xuyên hơn (có lẽ là do sử dụng bộ nhớ thấp hơn?)

Thật không may, điều này vẫn làm cho các chức năng HTTP hầu như không sử dụng được cho việc sử dụng sản xuất đối với người dùng.

Hy vọng nhóm Firebase có một số kế hoạch trong tương lai để cho phép phạm vi chức năng phù hợp để chỉ các mô-đun có liên quan cần được tải cho mỗi chức năng.


Này Tyris, tôi đang đối mặt với vấn đề tương tự với hoạt động thời gian, tôi đang cố gắng thực hiện giải pháp của bạn. Chỉ cần cố gắng để hiểu, ai gọi chức năng init và khi nào?
Manspof

Xin chào @AdirZoari, giải thích của tôi về việc sử dụng init () và vv có lẽ không phải là cách thực hành tốt nhất; giá trị của nó chỉ là để chứng minh những phát hiện của tôi về vấn đề cốt lõi. Bạn sẽ tốt hơn nhiều khi nhìn vào biến ẩn process.env.FUNCTION_NAMEvà sử dụng nó để bao gồm một cách có điều kiện các tệp cần thiết cho chức năng đó. Nhận xét tại github.com/firebase/fifts-samples/issues/ cho một mô tả thực sự tốt về công việc này! Nó đảm bảo rằng phạm vi toàn cầu không bị ô nhiễm với các phương thức và bao gồm từ các chức năng không liên quan.
Tyris

1
Xin chào @davidverweij, tôi không nghĩ rằng điều này sẽ giúp về khả năng các chức năng của bạn chạy hai lần hoặc song song. Chức năng tự động chia tỷ lệ khi cần thiết để nhiều chức năng (cùng chức năng hoặc các chức năng khác nhau) có thể chạy song song bất cứ lúc nào. Điều này có nghĩa là bạn phải xem xét an toàn dữ liệu và xem xét sử dụng các giao dịch. Ngoài ra, hãy xem bài viết này về các chức năng của bạn có thể chạy hai lần: cloud.google.com/blog/products/serverless/ mẹo
Tyris

1
Thông báo FUNCTIONS_NAMEchỉ hợp lệ với nút 6 và 8, như được giải thích ở đây: cloud.google.com/fifts/docs/ ,. Nút 10 nên sử dụngFUNCTION_TARGET
maganap

1
Cảm ơn bạn đã cập nhật @maganap, có vẻ như nó nên được sử dụng K_SERVICEtheo doco tại cloud.google.com/fifts/docs/migrating/, - Tôi đã cập nhật câu trả lời của mình.
Tyris

7

Tôi đang đối mặt với các vấn đề tương tự với các chức năng đám mây Firestore. Lớn nhất là hiệu suất. Đặc biệt trong trường hợp khởi nghiệp ở giai đoạn đầu, khi bạn không đủ khả năng để khách hàng sớm của mình thấy các ứng dụng "chậm chạp". Hàm tạo tài liệu đơn giản, ví dụ, cung cấp cho điều này:

- Thực thi chức năng mất 9522 ms, kết thúc với mã trạng thái: 200

Sau đó: Tôi đã có một trang điều khoản và điều kiện căng thẳng. Với các chức năng của đám mây, việc thực thi do khởi động nguội sẽ mất từ ​​10 đến 15 giây. Sau đó tôi đã chuyển nó sang một ứng dụng node.js, được lưu trữ trên thùng chứa appengine. Thời gian đã giảm xuống còn 2-3 giây.

Tôi đã so sánh nhiều tính năng của mongodb với Firestore và đôi khi tôi cũng tự hỏi liệu trong giai đoạn đầu của sản phẩm này, tôi cũng nên chuyển sang một cơ sở dữ liệu khác. Ưu điểm lớn nhất tôi có trong Firestore là chức năng kích hoạt onCreate, onUpdate của các đối tượng tài liệu.

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

Về cơ bản nếu có các phần tĩnh của trang web của bạn có thể được giảm tải cho môi trường appengine, có lẽ không phải là một ý tưởng tồi.


1
Tôi không nghĩ Chức năng Firebase phù hợp cho mục đích hiển thị nội dung người dùng động. Chúng tôi sử dụng một vài chức năng HTTP một cách tiết kiệm cho những thứ như đặt lại mật khẩu, nhưng nói chung nếu bạn có nội dung động, hãy phân phát nó ở nơi khác như một ứng dụng cấp tốc (hoặc sử dụng ngôn ngữ khác).
Tyris

2

Tôi cũng đã làm những điều này, giúp cải thiện hiệu suất khi các chức năng được làm nóng, nhưng khởi đầu lạnh đang giết chết tôi. Một trong những vấn đề khác mà tôi gặp phải là với cors, vì phải mất hai chuyến đến các chức năng đám mây để hoàn thành công việc. Tôi chắc chắn rằng tôi có thể khắc phục điều đó, mặc dù.

Khi bạn có một ứng dụng ở giai đoạn đầu (bản demo) khi không được sử dụng thường xuyên, hiệu suất sẽ không được tốt. Đây là điều cần được xem xét, vì những người chấp nhận sớm với sản phẩm sớm cần phải nhìn tốt nhất của họ trước các khách hàng / nhà đầu tư tiềm năng. Chúng tôi yêu thích công nghệ này vì vậy chúng tôi đã di chuyển từ các khuôn khổ đã thử và cũ hơn, nhưng ứng dụng của chúng tôi có vẻ khá chậm chạp vào thời điểm này. Tôi sẽ tiếp theo thử một số chiến lược khởi động để làm cho nó trông đẹp hơn


Chúng tôi đang thử nghiệm một công việc định kỳ để đánh thức mọi chức năng đơn lẻ. Có lẽ cách tiếp cận này cũng giúp bạn.
Jesús Fuentes

hey @ JesúsFuentes Tôi chỉ tự hỏi nếu đánh thức chức năng làm việc cho bạn. Nghe có vẻ là một giải pháp điên rồ: D
Alexandr Zavalii 26/07/18

1
Xin chào @Alexandr, thật đáng buồn khi chúng tôi chưa có thời gian để làm điều đó, nhưng nó nằm trong danh sách ưu tiên hàng đầu của chúng tôi. Nó nên hoạt động trên lý thuyết, mặc dù. Vấn đề đi kèm với các chức năng onCall, yêu cầu phải được khởi chạy từ Ứng dụng Firebase. Có thể gọi họ từ khách hàng mỗi X phút? Chúng ta sẽ thấy.
Jesús Fuentes

1
@Alexandr chúng ta sẽ có một cuộc trò chuyện bên ngoài Stackoverflow? Chúng tôi có thể giúp đỡ nhau với những cách tiếp cận mới.
Jesús Fuentes

1
@Alexandr chúng tôi chưa thử nghiệm cách giải quyết 'thức dậy' này nhưng chúng tôi đã triển khai các chức năng của mình cho europe-west1. Tuy nhiên, thời gian không thể chấp nhận.
Jesús Fuentes

0

CẬP NHẬT / EDIT: cú pháp và cập nhật mới vào tháng 5 năm 2020

Tôi vừa xuất bản một gói được gọi là better-firebase-functions , nó tự động tìm kiếm thư mục hàm của bạn và lồng chính xác tất cả các hàm tìm thấy trong đối tượng xuất của bạn, đồng thời cách ly các hàm với nhau để cải thiện hiệu năng khởi động nguội.

Nếu bạn lười tải và chỉ lưu các bộ đệm phụ thuộc vào từng chức năng trong phạm vi mô-đun, bạn sẽ thấy đó là cách đơn giản và dễ dàng nhất để giữ cho các chức năng của bạn hiệu quả tối ưu trong một dự án đang phát triển nhanh.

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

thú vị .. tôi có thể thấy repo của 'chức năng căn cứ tốt hơn' ở đâu?
JerryGidel

1
github.com/gramstr/better-firebase-fifts - vui lòng kiểm tra và cho tôi biết những gì bạn nghĩ! Hãy đóng góp thật tốt :)
George43g
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.