Pracujemy nad aplikacją, która korzysta z nowych funkcji chmury Firebase. Obecnie dzieje się tak, że transakcja jest umieszczana w węźle kolejki. Następnie funkcja usuwa ten węzeł i umieszcza go we właściwym węźle. Zostało to zaimplementowane ze względu na możliwość pracy w trybie offline.
Naszym obecnym problemem jest szybkość funkcji. Sama funkcja zajmuje około 400 ms, więc to w porządku. Ale czasami funkcje zajmują bardzo dużo czasu (około 8 sekund), podczas gdy wpis został już dodany do kolejki.
Podejrzewamy, że serwer potrzebuje czasu na uruchomienie, bo gdy wykonujemy akcję jeszcze raz po pierwszej. Zajmuje to znacznie mniej czasu.
Czy jest jakiś sposób na rozwiązanie tego problemu? Tutaj dodałem kod naszej funkcji. Podejrzewamy, że nie ma w tym nic złego, ale dodaliśmy to na wszelki wypadek.
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;
}
źródło
Odpowiedzi:
firebaser tutaj
Wygląda na to, że masz tak zwany zimny start funkcji.
Gdy Twoja funkcja nie była wykonywana od jakiegoś czasu, Cloud Functions przełącza ją w tryb, który zużywa mniej zasobów. Następnie po ponownym uruchomieniu funkcji przywraca środowisko z tego trybu. Czas potrzebny do przywrócenia składa się ze stałego kosztu (np. Przywrócenie kontenera) i częściowego kosztu zmiennego (np. Jeśli używasz wielu modułów węzłów, może to zająć więcej czasu).
Nieustannie monitorujemy wydajność tych operacji, aby zapewnić najlepsze połączenie doświadczenia programisty i wykorzystania zasobów. Więc spodziewaj się, że te czasy poprawią się z czasem.
Dobra wiadomość jest taka, że powinieneś tego doświadczyć tylko podczas programowania. Gdy funkcje są często uruchamiane w środowisku produkcyjnym, istnieje prawdopodobieństwo, że prawie nigdy nie osiągną zimnego startu.
źródło
Aktualizacja maj 2020 Dzięki za komentarz maganap - w Węźle 10+
FUNCTION_NAME
jest zastępowaneK_SERVICE
(FUNCTION_TARGET
to sama funkcja, a nie jej nazwa, zastępującENTRY_POINT
). Poniższe przykłady kodu zostały zaktualizowane poniżej.Więcej informacji na https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes
Aktualizacja - wygląda na to, że wiele z tych problemów można rozwiązać za pomocą ukrytej zmiennej,
process.env.FUNCTION_NAME
jak widać tutaj: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462Aktualizuj za pomocą kodu - na przykład, jeśli masz następujący plik indeksu:
Następnie wszystkie pliki zostaną załadowane, a wszystkie wymagania tych plików również zostaną załadowane, co spowoduje duże obciążenie i zanieczyszczenie globalnego zakresu wszystkich funkcji.
Zamiast tego oddzielaj elementy dołączone jako:
Spowoduje to załadowanie wymaganych plików tylko wtedy, gdy ta funkcja zostanie specjalnie wywołana; pozwalając na utrzymanie globalnego zasięgu znacznie czystszym, co powinno skutkować szybszymi zimnymi butami.
Powinno to pozwolić na znacznie lepsze rozwiązanie niż to, co zrobiłem poniżej (chociaż wyjaśnienie poniżej nadal jest aktualne).
Oryginalna odpowiedź
Wygląda na to, że wymaganie plików i ogólna inicjalizacja zachodząca w zakresie globalnym jest ogromną przyczyną spowolnienia podczas zimnego rozruchu.
W miarę jak projekt uzyskuje więcej funkcji, zasięg globalny jest coraz bardziej zanieczyszczany, co pogarsza problem - szczególnie jeśli zakres funkcji obejmuje oddzielne pliki (na przykład przy użyciu
Object.assign(exports, require('./more-functions.js'));
w plikuindex.js
.Udało mi się zaobserwować ogromny wzrost wydajności zimnego rozruchu, przenosząc wszystkie moje wymagania do metody init, jak poniżej, a następnie wywołując ją jako pierwszą linię w dowolnej definicji funkcji dla tego pliku. Na przykład:
Widziałem poprawę od około 7-8s do 2-3s podczas stosowania tej techniki do projektu z ~ 30 funkcjami w 8 plikach. Wydaje się również, że funkcje muszą być rzadziej uruchamiane na zimno (prawdopodobnie z powodu mniejszego zużycia pamięci?)
Niestety, nadal sprawia to, że funkcje HTTP są ledwo użyteczne do użytku produkcyjnego dla użytkownika.
Mając nadzieję, że zespół Firebase ma w przyszłości pewne plany, które pozwolą na odpowiednie określenie zakresu funkcji, tak aby tylko odpowiednie moduły musiały być ładowane dla każdej funkcji.
źródło
process.env.FUNCTION_NAME
i użył jej do warunkowego dołączenia plików wymaganych dla tej funkcji. Komentarz na github.com/firebase/functions-samples/issues/… daje naprawdę dobry opis tego działania! Zapewnia, że zakres globalny nie jest zanieczyszczony metodami i zawiera nieistotne funkcje.FUNCTIONS_NAME
dotyczy tylko węzłów 6 i 8, jak wyjaśniono tutaj: cloud.google.com/functions/docs/… . Węzeł 10 powinien użyćFUNCTION_TARGET
K_SERVICE
zgodnie z doco na cloud.google.com/functions/docs/migrating/ ... - Zaktualizowałem moją odpowiedź.Mam podobne problemy z funkcjami chmury Firestore. Największą jest wydajność. Szczególnie w przypadku startupów na wczesnym etapie, kiedy nie możesz pozwolić swoim klientom na oglądanie „powolnych” aplikacji. Prosta funkcja generowania dokumentacji np. Daje to:
- Wykonanie funkcji trwało 9522 ms, zakończone kodem statusu: 200
Potem: miałem prostą stronę z warunkami. W przypadku funkcji chmurowych wykonanie z powodu zimnego startu nawet czasami zajmowałoby 10-15 sekund. Następnie przeniosłem go do aplikacji node.js hostowanej w kontenerze appengine. Czas spadł do 2-3 sekund.
Porównywałem wiele funkcji mongodb z firestore i czasami też zastanawiam się, czy na tym wczesnym etapie mojego produktu powinienem również przenieść się do innej bazy danych. Największą reklamą, jaką miałem w Firestore, była funkcja wyzwalacza onCreate, onUpdate obiektów dokumentów.
https://db-engines.com/en/system/Google+Cloud+Firestore%3BMongoDB
Zasadniczo, jeśli istnieją statyczne części witryny, które można przenieść do środowiska Appengine, być może nie jest to zły pomysł.
źródło
Zrobiłem też te rzeczy, co poprawia wydajność po rozgrzaniu funkcji, ale zimny start mnie zabija. Jednym z innych problemów, które napotkałem, są cors, ponieważ wykonanie zadania wymaga dwóch podróży do funkcji chmury. Jestem jednak pewien, że mogę to naprawić.
Jeśli masz aplikację we wczesnej fazie (demo), kiedy nie jest często używana, wydajność nie będzie świetna. Należy to wziąć pod uwagę, ponieważ pierwsi użytkownicy z wczesnymi produktami muszą wyglądać jak najlepiej przed potencjalnymi klientami / inwestorami. Uwielbialiśmy tę technologię, więc przenieśliśmy się ze starszych, wypróbowanych i sprawdzonych frameworków, ale nasza aplikacja wydaje się w tym momencie dość powolna. Następnie spróbuję kilku strategii rozgrzewki, aby wyglądało lepiej
źródło
AKTUALIZACJA / EDYCJA: nowa składnia i aktualizacje w MAJ 2020
Właśnie opublikowałem pakiet o nazwie
better-firebase-functions
, który automatycznie przeszukuje katalog funkcji i prawidłowo zagnieżdża wszystkie znalezione funkcje w obiekcie eksportu, jednocześnie izolując funkcje od siebie, aby poprawić wydajność zimnego rozruchu.Jeśli ładujesz z opóźnieniem i buforujesz tylko zależności, których potrzebujesz dla każdej funkcji w zakresie modułu, okaże się, że jest to najprostszy i najłatwiejszy sposób na utrzymanie optymalnej wydajności funkcji w szybko rozwijającym się projekcie.
źródło