Google Cloud Function don't publish on PubSub, Timeout exceeded - node.js

first thanks for reading this and try to answer it :)
On GCP (Google Cloud Plateform), i've got a database stored on cloud SQL,
2 Cloud functions, the first one is used to make a request on the database (this request can have more that 1000 result) then publish the result on PubSub, and the second one is used to do web scraping thanks to puppeteer.
first cloud functions code:
//[Requirement]
const mysql = require('mysql')
const {SecretManagerServiceClient} = require('#google-cloud/secret-manager')
const ProjectID = process.env.secretID
const SqlPass = `projects/xxx`
const client = new SecretManagerServiceClient()
const {PubSub} = require('#google-cloud/pubsub');
const pubSubClient = new PubSub();
const topicName = "xxxxxx";
//[/Requirement]
exports.LaunchAudit = async () => {
const dbSocketPath = "/cloudsql"
const DB_USER = "xxx"
const DB_PASS = await getSecret()
const DB_NAME = "xxx"
const CLOUD_SQL_CONNECTION_NAME = "xxx"
//[SQL CONNEXION]
let pool = mysql.createPool({
connectionLimit: 1,
socketPath: `${dbSocketPath}/${CLOUD_SQL_CONNECTION_NAME}`,
user: DB_USER,
password: DB_PASS,
connectTimeout: 500,
database: DB_NAME
})
//[/SQL CONNEXION]
//set the request
let sql = `select * from * where *;`
//make the setted request
await pool.query(sql, async (e,results) => {
//if there is an error send it
if(e){
throw e
}
//for each result of the query, log it and publish on PubSub ("Audit-property" topic)
results.forEach(async element => {
console.log(JSON.stringify(element))
await msgPubSub(JSON.stringify(element))
})
})
}
async function msgPubSub(data){
//console.log(data)
const messageBuffer = Buffer.from(data)
try {
const topicPublisher = await pubSubClient.topic(topicName).publish(messageBuffer)
console.log("Message id: " + topicPublisher)
} catch (error) {
console.error(`Error while publishing message: ${error.message}`)
}
}
firstly, when it works, it takes long to publish on PubSub topic the first message, something like 6 minutes, why there is this delay ? And When i do a big request (something like 500+ result) i've got a Timeout error : Total timeout of API google.pubsub.v1.Publisher exceeded 600000 milliseconds before any response was received.
I've tried to publish a batched message, add some memory to the cloud functions, use google-gax, but got the same result.
i'm using nodejs10.
2nd cloud functions message part code:
exports.MainAudit = async message => {
const property = Buffer.from(message.data, 'base64').toString()
const pProperty = JSON.parse(property)
console.log(property)
}
package.json dependencies:
"dependencies": {
"#google-cloud/pubsub": "^2.6.0",
"#google-cloud/secret-manager": "^3.2.0",
"google-gax": "^2.9.2",
"mysql": "^2.18.1",
"node-fetch": "^2.6.1"
}
Log + Timestamp:

As the code is now, you are creating a new instance of a publisher for each message you publish. This is because pubSubClient.topic(topicName) creates an instance for publishing to the topic. Therefore, you are paying the overhead of establishing a connection for each message you send. Instead, you'd want to create that object a single time and reuse it:
const pubSubClient = new PubSub();
const topicName = "xxxxxx";
const topicPublisher = pubSubClient.topic(topicName)
However, this still leaves an inefficiency in your application where you are waiting for each message to publish before starting the next publish due to the use of await on the publish call and the call to msgPubSub. The Pub/Sub client library can batch messages togetherr for more efficient sending, but you'd need to allow multiple calls to publish to be outstanding to take advantage of it. You'd want to await on a Promise.all of the list of promises returned from publishing.

Related

Unable to use 'array-contains' where clause in cloud function

I am working on a job bidding app.
Each user has a field "User job notifications preferences".
The array field stores the data to which type of job they would like to receive notifications for.
for example:
Person A has the setting to receive a notification when a job of type 'plumming' is created.
Person B has the setting to receive a notification when a job of type 'electrical' is created.
Person C creates a plumming job,
Peron A should receive a notification to let them know a new job of type 'plumming' has been created.
here is the code snip
// when a job is updated from new to open
// send notifications to the users that signed up for that jobtype notification
exports.onJobUpdateFromNewToOpen= functions.firestore
.document('job/{docId}')
.onUpdate(async (change, eventContext) => {
const beforeSnapData = change.before.data();
const afterSnapData = change.after.data();
const jobType = afterSnapData['Job type'];
const afterJobState = afterSnapData["Job state"];
const beforeJobState = beforeSnapData["Job state"];
console.log('job updated');
// only consider jobs switching from new to open
if (beforeJobState=="New" && afterJobState == "Open") {
console.log('job updated from new to open');
console.log('jobType: '+jobType);
console.log('job id: '+change.after.id )
// get users that contain the matching job type
const usersWithJobTypePreferenceList = await admin.firestore().collection("user").where("User job notifications preferences", "array-contains-any", jobType).get();
// get their userIds
const userIdsList = [];
usersWithJobTypePreferenceList.forEach((doc) => {
const userId = doc.data()["User id"];
userIdsList.push(userId);
})
// get their user tokens
const userTokenList = [];
for (var user in userIdsList) {
const userId = userIdsList[user];
const userToken = await (await admin.firestore().collection("user token").doc(userId).get()).data()["token"];
userTokenList.push(userToken);
};
// send message
const messageTitle = "new " + jobType + " has been created";
for (var token in userTokenList) {
var userToken = userTokenList[token];
const payload = {
notification: {
title: messageTitle,
body: messageTitle,
sound: "default",
},
data: {
click_action: "FLUTTER_NOTIFICATION_CLICK",
message: "Sample Push Message",
},
};
return await admin.messaging().sendToDevice(receiverToken, payload);
}
}
});
I think the issue is at the following line because I am getting the error 'Error: 3 INVALID_ARGUMENT: 'ARRAY_CONTAINS_ANY' requires an ArrayValue' (see image)
const usersWithJobTypePreferenceList = await admin.firestore().collection("user").where("User job notifications preferences", "array-contains-any", jobType).get();
below is the full error:
Error: 3 INVALID_ARGUMENT: 'ARRAY_CONTAINS_ANY' requires an ArrayValue.
at Object.callErrorFromStatus (/workspace/node_modules/#grpc/grpc-js/build/src/call.js:31:19)
at Object.onReceiveStatus (/workspace/node_modules/#grpc/grpc-js/build/src/client.js:352:49)
at Object.onReceiveStatus (/workspace/node_modules/#grpc/grpc-js/build/src/client-interceptors.js:328:181)
at /workspace/node_modules/#grpc/grpc-js/build/src/call-stream.js:188:78
at processTicksAndRejections (node:internal/process/task_queues:78:11)
I interpret the error as the following: there is no value being passed to 'jobType'.but that cant be right because I am printing the value ( see screenshot )
I found the following related questions but I dont think I am having the same issue:
Getting firestore data from a Google Cloud Function with array-contains-any
Firestore: Multiple 'array-contains'
So I am not sure what the issue is here, any ideas?
here is how the data looks in firebase:
I looked at similar questions and I printed the values being passed to the function that was creating the error
I updated the line that was giving me an issue now everything works :) ::
'''
const usersWithJobTypePreferenceList = await admin.firestore().collection("user").where("User job notifications preferences", "array-contains", jobType).get();
'''

GCP Document AI Example Not Working - Receiving INVALID_ARGUMENT: Request contains an invalid argument

The error is with the batchProcessDocuments line and has the error:
{
code: 3,
details: 'Request contains an invalid argument.',
metadata: Metadata {
internalRepr: Map { 'grpc-server-stats-bin' => [Array] },
options: {}
},
note: 'Exception occurred in retry method that was not classified as transient'
}
I've tried to copy the example as much as possible but without success. Is there a way of finding out more information regarding the input parameters that are required? There are very few examples of using Document AI on the web with this being a new product.
Here is my code sample:
const projectId = "95715XXXXX";
const location = "eu"; // Format is 'us' or 'eu'
const processorId = "a1e1f6a3XXXXXXXX";
const gcsInputUri = "gs://nmm-storage/test.pdf";
const gcsOutputUri = "gs://nmm-storage";
const gcsOutputUriPrefix = "out_";
// Imports the Google Cloud client library
const {
DocumentProcessorServiceClient,
} = require("#google-cloud/documentai").v1beta3;
const { Storage } = require("#google-cloud/storage");
// Instantiates Document AI, Storage clients
const client = new DocumentProcessorServiceClient();
const storage = new Storage();
const { default: PQueue } = require("p-queue");
async function batchProcessDocument() {
const name = `projects/${projectId}/locations/${location}/processors/${processorId}`;
// Configure the batch process request.
const request = {
name,
inputConfigs: [
{
gcsSource: gcsInputUri,
mimeType: "application/pdf",
},
],
outputConfig: {
gcsDestination: `${gcsOutputUri}/${gcsOutputUriPrefix}/`,
},
};
// Batch process document using a long-running operation.
// You can wait for now, or get results later.
// Note: first request to the service takes longer than subsequent
// requests.
const [operation] = await client.batchProcessDocuments(request); //.catch(err => console.log('err', err));
// Wait for operation to complete.
await operation.promise();
console.log("Document processing complete.");
}
batchProcessDocument();
I think this is the solution: https://stackoverflow.com/a/66765483/15461811
(you have to set the apiEndpoint parameter)

Nodejs Elastic benastalk refused to connect to upsteam/ upsteam prematurely closed

I am getting the following errors when running my application in elastic beanstalk: [error] 3636#0: *295 upstream prematurely closed connection while reading response header from upstream and [error] 3636#0: *295 connect() failed (111: Connection refused) while connecting to upstream Its strange because if I hit those routes independently it works fine. It only appears to error when firing those routes from my vuex action.
The following is the log from the AWS elastic beanstalk.
The following is the network tab when it hits my FFmpeg route:
The following is the generate video action as fired from vuex.
async [GENERATE_VIDEO]({state, rootState, dispatch, commit}){
const username = rootState.user.currentUser.username;
const s3Id = rootState.templates.currentVideo.stock_s3_id;
const type = rootState.dataClay.fileFormat || state.type;
const vid = new Whammy.fromImageArray(state.captures, 30);
vid.lastModifiedDate = new Date();
vid.name = "canvasVideo.webm";
const data = new FormData();
const id = `${username}_${new Date().getTime()}`;
data.append("id", id);
data.append("upload", vid);
const projectId = await dispatch(INSERT_PROJECT);
await dispatch(UPLOAD_TEMP_FILE, data);
const key = await dispatch(CONVERT_FILE_TYPE, { id, username, type, projectId});
const role = rootState.user.currentUser.role;
state.file = `/api/files/${key}`;
let message;
if(role!='banner'){
message =`<p>Your video is ready.</p> Download`;
} else {
message = `<p>Your video is ready. You may download your file from your banner account</p>`;
const resolution = rootState.dataClay.matrix[0];
await dispatch(EXPORT_TO_BANNER, { s3Id, fileUrl: key, extension: `.${type}`, resolution});
}
And here are the api routes called in the actions.
async [UPLOAD_TEMP_FILE]({ commit }, data) {
try {
const response = await axios.post("/api/canvas-editor/upload-temp", data);
return response.data;
} catch (error) {
console.log(error);
}
},
async [CONVERT_FILE_TYPE]({commit}, data) {
try{
const response = await axios.post("/api/canvas-editor/ffmpeg", data);
return response.data;
} catch(error){
console.log(error);
}
}
}
As I said all my routes work and the application runs as expected on localhost however when uploaded to aws I receive unexpected errors.
After some digging I found out that I did not set the ffmpeg path.
Once this was done it worked great.
const ffmpeg = require('fluent-ffmpeg');
const ffmpegPath = require('#ffmpeg-installer/ffmpeg').path;
ffmpeg.setFfmpegPath(ffmpegPath);
module.exports = ffmpeg;

cloud function to write in BigQuery (async function ... await bigquery ...) failing with Unhandled rejection/PartialFailureError?

On GCP, I created a CloudFunction which is trigged by billing events from Pub/Sub and is publishing some messages on Slack. I am using node.js 10.I have the following dependencies:
{
"name": "google-container-slack",
"version": "0.0.1",
"description": "Slack integration for Google Cloud Build, using Google Cloud Functions",
"main": "index.js",
"dependencies": {
"#slack/webhook": "5.0.3",
"#google-cloud/bigquery": "4.7.0",
"#google-cloud/pubsub": "1.6.0"
}
}
I have some issue when I add a function to write new budget info in BigQuery which is based on some official example:
https://github.com/googleapis/nodejs-bigquery/blob/master/samples/insertRowsAsStream.js
// writeInBigQuery update BigQuery table
async function writeInBigQuery(pubsubdata, createdAt, project, threshold) {
const bigquery = new BigQuery({projectId: billing_project});
const rows = [{createdAt: createdAt},
{budgetAmount:pubsubdata.budgetAmount},
{projectName: project},
{thresholdValue: threshold}];
console.log(rows);
console.log('start insert row in bigquery');
await bigquery
.dataset(dataset)
.table(table)
.insert(rows);
console.log('end insert row in bigquery');
console.log(`Inserted ${rows.length} rows`);
}
My guess is that the issue is related to async and await. It is my first code with node.js and the error message is cryptic for me:
Function execution took 266 ms, finished with status: 'ok'
Unhandled rejection
PartialFailureError
at request (/layers/google.nodejs.npm/npm/node_modules/#google-cloud/bigquery/build/src/table.js:1550:23)
at util.makeRequest.params (/layers/google.nodejs.npm/npm/node_modules/#google-cloud/common/build/src/util.js:367:25)
8 at Util.handleResp (/layers/google.nodejs.npm/npm/node_modules/#google-cloud/common/build/src/util.js:144:9)
8 at retryRequest (/layers/google.nodejs.npm/npm/node_modules/#google-cloud/common/build/src/util.js:432:22)
at onResponse (/layers/google.nodejs.npm/npm/node_modules/retry-request/index.js:206:7)
8 at /layers/google.nodejs.npm/npm/node_modules/teeny-request/build/src/index.js:233:13
at process._tickCallback (internal/process/next_tick.js:68:7)
8 Error: Process exited with code 16
at process.on.code (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:393:29)
at process.emit (events.js:198:13)
at process.EventEmitter.emit (domain.js:448:20)
at process.exit (internal/process/per_thread.js:168:15)
at logAndSendError (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:184:9)
at process.on.err (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:390:13)
at process.emit (events.js:198:13)
at process.EventEmitter.emit (domain.js:448:20)
at emitPromiseRejectionWarnings (internal/process/promises.js:140:18)
at process._tickCallback (internal/process/next_tick.js:69:34)
Maybe the issue is related to the overall structure of the code:
const { IncomingWebhook } = require('#slack/webhook');
const {BigQuery} = require('#google-cloud/bigquery');
const url = process.env.SLACK_WEBHOOK_URL;
const project =process.env.PROJECT_LIST.split(',');
const dataset = process.env.DATASET;
const table = process.env.TABLE;
const billing_project = process.env.PROJECT;
const webhook = new IncomingWebhook(url);
// subscribeSlack is the main function called by Cloud Functions.
module.exports.subscribeSlack= (pubSubEvent, context) => {
const pubsubdata = eventToBuild(pubSubEvent.data);
//select for which project to send budget alert
if (project.indexOf(pubsubdata.budgetDisplayName) === -1) {
console.log(`skip project: ${pubsubdata.budgetDisplayName.substr(0,pubsubdata.budgetDisplayName.indexOf(' '))}`);
return;
}
console.log(`project: ${pubsubdata.budgetDisplayName.substr(0,pubsubdata.budgetDisplayName.indexOf(' '))}`);
// Send message to Slack.
const message = createSlackMessage(pubsubdata);
webhook.send(message);
};
// eventToBuild transforms pubsub event message to a build object.
const eventToBuild = (data) => {
return JSON.parse(Buffer.from(data, 'base64').toString());
}
// writeInBigQuery update BigQuery table
async function writeInBigQuery(pubsubdata, createdAt, project, threshold) {
const bigquery = new BigQuery({projectId: billing_project});
const rows = [{createdAt: createdAt},
{budgetAmount:pubsubdata.budgetAmount},
{projectName: project},
{thresholdValue: threshold}];
console.log(rows);
console.log('start insert row in bigquery');
await bigquery
.dataset(dataset)
.table(table)
.insert(rows);
console.log('end insert row in bigquery');
console.log(`Inserted ${rows.length} rows`);
}
// createSlackMessage creates a message from a build object.
const createSlackMessage = (pubsubdata) => {
const formatter = new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR', minimumFractionDigits: 2})
const costAmount = formatter.format(pubsubdata.costAmount);
const budgetAmount = formatter.format(pubsubdata.budgetAmount);
const budgetName = pubsubdata.budgetDisplayName;
const createdAt = new Date().toISOString();
const project = budgetName.substr(0,budgetName.indexOf(' '))
let threshold = (pubsubdata.alertThresholdExceeded*100).toFixed(0);
if (!isFinite(threshold)){
threshold = 0;
}
// write current budget info in BigQuery
console.log('big query call start');
writeInBigQuery(pubsubdata, createdAt, project, threshold);
console.log('big query call end');
// create Slack message
const emoticon = threshold >= 90 ? ':fire:' : ':white_check_mark:';
notification = `${emoticon} Project: ${project}\nOverall cost: ${costAmount} \nTotal Budget: ${budgetAmount}\nThresold: ${threshold}%`
const message = {
text: notification
};
return message;
}
The issue had nothing to do with the asyn ... wait but was a mistake in the in the way I was preparing the data to write in BigQuery:
const rows = [{createdAt: createdAt},
{budgetAmount:pubsubdata.budgetAmount},
{projectName: project},
{thresholdValue: threshold}];
and it should be:
const rows = [{createdAt: createdAt,
budgetAmount:pubsubdata.budgetAmount,
projectName: project,
thresholdValue: threshold}];
With this fix this is working fine.
The final and is working find can be find here: index.js

Error upon Cloud Function for Firebase deployment

I've been trying to deploy a Cloud Function to my Firebase project.
It's my first time doing so, also my first time programming with JavaScript.
Here's my code in Node.JS:
'use strict'
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
const firebaseTriggers = functions.region('europe-west1').firestore;
const db = admin.firestore();
exports.postNotification = firebaseTriggers
.document('/post notifications/{notificatioId}').onWrite((snap, context) => {
const notifcationRecieverId = snap.data().mReciever;
const payload = {
data: {
notification_type: 'POST',
title: snap.data().mTitle,
body: snap.data().mDescription,
sender_id: snap.data().mSender,
reciever_id: snap.data().mReciever,
notification_id: context.params.notificatioId
}
};
return db.collection('dog owners')
.document(notifcationRecieverId)
.get()
.then(recieverDoc => {
console.log('Retrieving FCM tokens');
const tokens = recieverDoc.data().mTokens;
console.log('Sending notification payload');
return admin.message().sendToDevice(tokens, payload);
});
});
Upong deployment, I'm getting the following error:
Can someone help me understand why?
Firstly you have got space in your colleciton name. This is bad convetion.
post notifications => postNotifications

Resources