node.js - funciones - firebase functions tutorial
Las funciones en la nube de Firebase son muy lentas (4)
Estamos trabajando en una aplicación que utiliza las nuevas funciones en la nube de Firebase. Lo que está sucediendo actualmente es que una transacción se coloca en el nodo de la cola. Y luego la función elimina ese nodo y lo coloca en el nodo correcto. Esto se ha implementado debido a la capacidad de trabajar sin conexión.
Nuestro problema actual es la velocidad de la función. La función en sí misma tarda unos 400 ms, así que está bien. Pero a veces las funciones toman mucho tiempo (alrededor de 8 segundos), mientras que la entrada ya se agregó a la cola.
Sospechamos que el servidor tarda en iniciarse, porque cuando hacemos la acción una vez más después del primero. Toma mucho menos tiempo.
Hay alguna manera de arreglar este problema? Aquí abajo agregué el código de nuestra función. Sospechamos que no tiene nada de malo, pero lo agregamos por si acaso.
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;
}
Estoy enfrentando problemas similares con las funciones de nube de firestore. El más grande es el rendimiento. Especialmente en el caso de nuevas empresas en etapa inicial, cuando no puede permitirse que sus primeros clientes vean aplicaciones "lentas". Una función simple de generación de documentación, por ejemplo, proporciona esto:
- La ejecución de la función tomó 9522 ms, finalizó con el código de estado: 200
Luego: tenía una página de términos y condiciones sencilla. Con las funciones en la nube, la ejecución debido al arranque en frío tardaría entre 10 y 15 segundos, incluso a veces. Luego lo moví a una aplicación node.js, alojada en un contenedor de appengine. El tiempo se ha reducido a 2-3 segundos.
He estado comparando muchas de las características de mongodb con firestore y, a veces, también me pregunto si durante esta fase temprana de mi producto también debería pasar a una base de datos diferente. El mayor consejo que tuve en firestore fue la funcionalidad de activación onCreate, onUpdate de objetos de documento.
https://db-engines.com/en/system/Google+Cloud+Firestore%3BMongoDB
Básicamente, si hay partes estáticas de su sitio que pueden descargarse en un entorno de motor, quizás no sea una mala idea.
También he hecho estas cosas, lo que mejora el rendimiento una vez que las funciones se calientan, pero el arranque en frío me está matando. Uno de los otros problemas que he encontrado es con cors, porque se necesitan dos viajes a las funciones de la nube para hacer el trabajo. Sin embargo, estoy seguro de que puedo arreglar eso.
Cuando tiene una aplicación en su fase inicial (demo) cuando no se usa con frecuencia, el rendimiento no será excelente. Esto es algo que debe considerarse, ya que los primeros usuarios con productos tempranos deben verse lo mejor posible frente a clientes / inversores potenciales. Nos encantó la tecnología, por lo que migramos de marcos de trabajo antiguos y probados, pero nuestra aplicación parece bastante lenta en este momento. A continuación, intentaré algunas estrategias de calentamiento para que se vea mejor
bombero aquí
Parece que está experimentando un llamado arranque en frío de la función.
Cuando su función no se ha ejecutado en algún momento, Cloud Functions la pone en un modo que utiliza menos recursos. Luego, cuando vuelva a presionar la función, restaurará el entorno desde este modo. El tiempo que se tarda en restaurar consiste en un costo fijo (por ejemplo, restaurar el contenedor) y un costo variable parcial (por ejemplo, si usa muchos módulos de nodo, puede llevar más tiempo).
Continuamente estamos monitoreando el desempeño de estas operaciones para asegurar la mejor combinación entre la experiencia del desarrollador y el uso de recursos. Así que espere que estos tiempos mejoren con el tiempo.
La buena noticia es que solo debe experimentar esto durante el desarrollo. Una vez que sus funciones se activan con frecuencia en la producción, es probable que casi nunca vuelvan a comenzar en frío.
Actualización
: parece que muchos de estos problemas se pueden resolver utilizando la variable oculta
process.env.FUNCTION_NAME
como se ve aquí:
https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462
Actualizar con código : por ejemplo, si tiene el siguiente archivo de índice:
...
exports.doSomeThing = require(''./doSomeThing'');
exports.doSomeThingElse = require(''./doSomeThingElse'');
exports.doOtherStuff = require(''./doOtherStuff'');
// and more.......
Luego, todos sus archivos se cargarán y todos los requisitos de esos archivos también se cargarán, lo que generará una gran sobrecarga y contaminará su alcance global para todas sus funciones.
En lugar de separar su incluye como:
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === ''doSomeThing'') {
exports.doSomeThing = require(''./doSomeThing'');
}
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === ''doSomeThingElse'') {
exports. doSomeThingElse = require(''./doSomeThingElse'');
}
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === ''doOtherStuff'') {
exports. doOtherStuff = require(''./doOtherStuff'');
}
Esto solo cargará los archivos necesarios cuando se llama específicamente a esa función; lo que le permite mantener su alcance global mucho más limpio, lo que debería resultar en botas de frío más rápidas.
Esto debería permitir una solución mucho más ordenada que la que he hecho a continuación (aunque la explicación a continuación sigue siendo válida).
Respuesta original
Parece que requerir archivos e inicialización general en el ámbito global es una gran causa de desaceleración durante el arranque en frío.
A medida que un proyecto obtiene más funciones, el alcance global se contamina cada vez más, lo que empeora el problema, especialmente si
Object.assign(exports, require(''./more-functions.js''));
sus funciones en archivos separados (como mediante el uso de
Object.assign(exports, require(''./more-functions.js''));
en su
index.js
.
Me las arreglé para ver grandes ganancias en el rendimiento de arranque en frío moviendo todos mis requisitos a un método init como se muestra a continuación y luego llamándolo como la primera línea dentro de cualquier definición de función para ese archivo. P.ej:
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;
}
He visto mejoras de aproximadamente 7-8 segundos a 2-3 segundos al aplicar esta técnica a un proyecto con ~ 30 funciones en 8 archivos. Esto también parece causar que las funciones deban iniciarse en frío con menos frecuencia (¿presumiblemente debido a un menor uso de memoria?)
Desafortunadamente, esto todavía hace que las funciones HTTP sean apenas utilizables para el uso de producción orientado al usuario.
Esperando que el equipo de Firebase tenga algunos planes en el futuro para permitir el alcance adecuado de las funciones de modo que solo los módulos relevantes necesiten cargarse para cada función.