How to (quickly) download huge amounts of data in Firebase? - javascript

I'm sending data to the Firestore at 33Hz. This means that over time, a lot of data is stored.
I need to be able to download this data. For that, I have made a Http Function in firebase, that receives the user uid, device's serial number, start date/time and end date/time. The function then tests if the user exists and also if he does have that serial number. Then it queries firestore and append data to a JSON object, that eventually is send as a response.
But, depending on how long the query has been made, the function will timeout. It does work for short periods.
How should I do to make the function faster? Am I using the wrong tool?
[...]
const nD1 = db.collection('grind-bit').doc(req.query.serial).collection('history').where('date', '>=', startdate).where('date', '<=', enddate).get().then(snapshot => {
snapshot.forEach(doc => {
elem = {};
elem.date = doc.data().date;
elem.rms0 = doc.data().rms0;
elem.rms1 = doc.data().rms1;
elem.rms2 = doc.data().rms2;
data[key].push(elem);
});
if(data[key].length) {
let csv = json2csv(data[key]);
csv = JSON.stringify(csv);
res.set("Content-Type", "text/csv");
return promises.push(res.status(200).send(csv));
} else {
return promises.push(res.status(401).send('No data has been found in this interval.'));
}
[...]

If the timeout is caused by the fact that you're trying to read/return too much data in one go, you might want to consider limiting how much data you return. By adding a limit() clause to your query, you can limit how much data it returns at most.
const nD1 = db.collection('grind-bit').doc(req.query.serial).collection('history')
.where('date', '>=', startdate).where('date', '<=', enddate)
.limit(100)
.get().then(snapshot => {
snapshot.forEach(doc => {
...
This of course means that you may have to call the HTTP function multiple times to ensure you process all data. The simplest approach for that is: if you get 100 results (or whatever limit your specify), try another time after processing them.
Also see:
The Firebase documentation on limiting query results

Related

Is it possible if I can get the last key (latest message) added from the realtime database?

I would like to get the last key (the latest message) from my realtime database but not sure how this can be achieved.
I see from this link i need to get Last child of my firebase databse that I can use orderByKey().limitToLast(1) to get this but it looks like I need to specify the complete ref in order to achieve this. Is that correct? Or is it possible if I can orderByKey().limitToLast(1) on the val()? Or is there another way I can achieve this?
Here is my messages structure in the database:
I have a timestamp child under each key as shown above which I thought I could query in order to extract the latest key but I really don't know how to do this. Can someone please help? Below is my code so far:
database().ref(`messages/`).once(`value`, snapshot => {
if(snapshot.exists()) {
snapshot.forEach(function (childSnapshot) {
if(childSnapshot.key.includes(auth().currentUser.uid)) {
console.log("show me the key: "+childSnapshot.key)
//not working
console.log("show last message: "+ JSON.stringify(childSnapshot.val().orderbyKey().limitToLast(1)))
}
})
}
})
console.log(JSON.stringify(messages)) => [{"-MfqYBzbusp1Cljgxpan":{"unreadMessage":true,"user":{"name":"Mike","avatar":"xxxxxx","_id":"tFhmw5oQoPhk8nF2sx5rE5BFqw93"},"timestamp":1627634061437,"senderId":"tFhmw5oQoPhk8nF2sx5rE5BFqw93","notification":{"body":"Hey","title":"Project","imageUrl":"./assets/xxxxx.png"},"text":"Hey"}}]
console.log(JSON.stringify(unreadMsgs)) => []
Firebase Realtime Database queries work on a flat list of nodes. So if you have a specific path /messages/nodeid already, you can find the latest message under that, but you can't find the latest message across all of /messages.
Reading all messages from all chatrooms, just to find the latest message for each chatroom this user is in is really wasteful though. As you add more users to the app, you're driving up the bandwidth cost for them, and for yourself too.
I recommend keeping a separate node where you track the chat rooms for each user, as explained in my answer on Best way to manage Chat channels in Firebase. With such a node you can then easily determine just the chat rooms for the current user, and then load the latest message for each of them with something like:
database().ref(`user_chatrooms/${auth().currentUser.uid}`).once(`value`, indexSnapshot => {
indexSnapshot.forEach((indexSnapshotChild) => {
let chatroomId = indexSnapshotChild.key;
let query = database().ref(`messages/${chatroomId}`).orderByChild("timestamp").limitToLast(1)
query.once(`value`, (msgSnapshot) => {
console.log(`Last message in ${chatroomId} was ${msgSnapshot.val().text}`);
})
}
})
The orderByKey and limitToLast methods exists on a DatabaseReference and not on the value you fetch from the snapshot fetched earlier. It seems the parent key for all messages is of format userId1userId2. If you know this combination then you run your query this way.
const uidsKey = "uid1" + "uid2"
const query = database().ref(`messages/${uidsKey}`).orderByChild("timestamp").limitToLast(1)
query.once("value").then((snapshot) => {
console.log(snapshot.val())
})
But it seems you are trying to get UIDs of others users who have chats with user1 and trying to real all nodes first. I won't recommend doing that as that might have issues with security rules and so on. Instead if you keep list of those UIDs somewhere else, it'll be better. But if you want to keep what you have right now, try this:
const userUID = auth().currentUser.uid
database().ref("messages/").once("value").then(async (msgSnapshot) => {
const keys = Object.keys(msgSnapshot.val() || {})
const userChatKeys = keys.filter(k => k.includes(userUID))
//const otherUserIDs = userChatKeys.map(k => k.replace(userUID, ""))
//userChatKeys will be chat IDs where current user is included
//now follow the same steps mentioned in first code snippet
const queries = userChatKeys.map(chat => database().ref(`messages/${chat}`).orderByChild("timestamp").limitToLast(1).once("value"))
const lastMessagesSnap = await Promise.all(queries)
const messages = lastMessagesSnap.map(m => Object.values(m.val())[0]))
console.log(`messages: ${messages}`)
const unreadMsgs = messages.filter((msg) => msg.unreadMessage === true)
console.log(unreadMsgs.length)
})
This will logs last message from each of user's chat.

how to maintain the order of http requests using node js?

I have a bunch of data that I want to send to a server through http. However in the server side I need to process the data in the same order as they were sent(e.g. if the order of sending is elem1, elem2 and elem3, I would like to process elem1 first, then elem2 and then elem3). Since in http, there is no grantee that the order will be maintained I need some way to maintain the order.
Currently I am keeping the data in a queue and I send one element and await for the response. Once the response reaches me I send the next element.
while (!queue.isEmpty()) {
let data = queue.dequeue();
await sendDataToServer(data);
}
I am not very sure if this will actually work in a production environment and what will be the impact on the performance.
Any sort of help is much appreciated. Thank you
Sorry, I don't have enough reputation to comment, thus I am posting this as an answer.
Firstly, your code will work as intended.
However, since the server has to receive them in order, the performance won't be good. If you can change the server, I suggest you implement it like this:
Add an ID to each data item.
Send all the data items, no need to ensure order.
Create a buffer on the server, the buffer will be able to contain all the data items.
The server receives the items and puts them into the buffer in the right position.
Example code:
Client (see Promise.all)
let i = 0;
let promises = [];
await sendDataLengthToServer(queue.length());
while (!queue.isEmpty()) {
let data = queue.dequeue();
data.id = i;
// no need to wait for a request to finish
promises.push(sendDataToServer(data));
}
await Promise.all(promises);
Server (pseudo-code)
length = receiveDataLengthFromClient()
buffer = new Array(length)
int received = 0
onDataReceivedFromClient(data, {
received = received + 1
buffer[data.id] = data
if (received == length) {
// the buffer contains the data in the right order
}
})

Prevent the delay time to fetch the data from firebase (Please check details for clarity)

Note:
The asynchronous nature of firebase has been discussed thousands of times here, but my low reputation number does not allow for a comment on an existing question. That's why I have asked this question.
I am a noob, so please help me understand the implementation in an easy to understand manner.
Steps to implement:
User enters a value in the HTML input box
Search the input value in the firebase db (showMessage() gets called)
Display an appropriate result based on the search result in step 2
Problem faced:
The message displayed in step 3 takes almost an average of 1.75 seconds to display. This experience is not user-friendly. I want to display the message as soon as possible i.e. want to reduce the fetch time.
Probable root causes:
Either my way of fetching the data from firebase dB is incorrect (I still don't understand how to keep a promise :()
Or The mechanism of search and display is not right
var full_name;
function showMessage(){
extractData();
}
function extractData(){
test(function(returnValue) {
custom_message = searchMessage(returnValue);
var container = document.querySelector('#placeholder');
var para = document.createElement('p');
var custom_message = "Happy happy, buds!";
para.innerHTML = custom_message;
para.className = "message";
container.appendChild(para);
});
}
function test(callback) {
var ref = firebase.database().ref();
ref.on('value', function(snapshot) {
var data = snapshot.val();
callback(data);
}, function (error) {
console.log("Error: " + error.code);
});
}
function searchMessage(data){
for(var i = 0; i < data.length; i++)
{
name_f_data = data[i].firstName.concat(" ", data[i].lastName);
if(full_name.toLowerCase() == name_f_data.toLowerCase())
{
console.log(name_f_data.toLowerCase());
console.log(full_name.toLowerCase());
return data[i].message;
}
}
}
The time a read operation takes depends on:
The latency of your connection to Firebase's servers
The amount of data you are reading
The bandwidth of your connection
The time it takes Firebase to process the request
In most cases, the time Firebase takes is only a very small portion of the total time, and most of your time actually goes to the data transfer, which depends purely on the bandwidth and amount of data. If this is the first time you're reading data from Firebase in the page, the latency also matters more, as Firebase has to establish a connection, which takes a few roundtrips.
Your current code is downloading all data from the database, and then searching in the JavaScript code for a child node that matches a certain value. The best way to reduce the time that takes (apart from upgrading to a fast connection) is to transfer less data, which you can do by using Firebase's query mechanism to do the filtering on the server.
You can get pretty close with:
var ref = firebase.database().ref();
var query = ref.orderByChild().startAt(firstName).endAt(firstName+"~");
query.once('value', function(snapshot) {
var data = snapshot.val();
callback(data);
This will significantly reduce the amount of data transferred. A few notes though:
The query returns just the people that have the first name you're looking for. It does not yet filter on the last name, so you'll still need to filter that in the client-side code.
To further optimize this, store the full name (which you now compose in the client-side code) in the database so that you can query on that and reduce data transfer even more.
Firebase queries are case sensitive, so the query only returns data where the case matches exactly. If you want to query case-indifferent, consider storing a toLowerCase() value in the database.
Be sure to define an index on firstName, as otherwise the Firebase database will still send all data to the client, and the SDK will perform the filtering client-side.

AngularFire / Firestore - Return collections and documents as a service

I have several pages that reference the same node in firestore, each pulling different segments from the firestore node. For example, a summary page might pull through album title, date, genre and image, whilst another page might pull through just the title, artist and record label. A couple of questions:
Is it possible to turn one of the firestore queries into a service?
If so, does that mean the data is only read once whilst navigating across different pages (angular components) that use the same service?
Will the query only run again when data is modified in firestore through the observable? ("return Observable.create(observer => {" )
I have tried a service with the code below. However, the issue observed is that on page refresh, the data isn't present. It is however present whilst navigating through the site. I believe this is because my page is running before the observable is returned. Is there a way to wrap up the query as an observable?
Any assistance would be greatly appreciated.
getAlbumData() {
this.albumDoc = this.afs.doc(`albums/${this.albumId}`);
this.album = this.albumDoc.snapshotChanges();
this.album.subscribe((value) => {
// The returned Data
const data = value.payload.data();
// Firebase Reference
var storage = firebase.storage();
// If album cover exists
if (data.project_signature != undefined) {
// Get the Image URL
var image = data.album_cover_image;
// Create an image reference to the storage location
var imagePathReference = storage.ref().child(image);
// Get the download URL and set the local variable to the result (url)
imagePathReference.getDownloadURL().then((url) => {
this.album_cover = url;
});
}
});
}
When I build my observables, I try to use operators as much as I can until I get the data I want to display in my UI.
You don't want to implement too much code in the subscribe method because you break the reactive paradigm by doing so.
Instead, extract you data in your observable and display it in your template.
Don't forget to use the async pipe in your template to display your data when it gets fetched by your application.
I would do something like this:
// In AlbumService
getAlbumCover(albumId: string) {
const albumDoc = this.afs.doc(`albums/${albumId}`);
const album_cover$ = this.albumDoc.snapshotChanges().pipe(
// get the album data from firestore request
map(data => {
return {
id: data.payload.id,
...data.payload.data()
};
}),
// emits data only if it contains a defined project_signature
filter(album => album.project_signature),
// prepare the imagePath and get the album cover from the promise
mergeMap(album => {
const storage = firebase.storage();
const image = album.album_cover_image;
const imagePathReference = storage.ref().child(image);
return imagePathReference.getDownloadURL();
})
);
return album_cover$;
}
By doing so, when your data is updated in firestore, it will be fetched automatically by your application since you use an observable.
In your component, in the onInit() method after getting your album id from the url:
this.album_cover$ = this.albumService.getAlbumCover(albumId);
Finally, in my template, I would do :
<div>{{album_cover$ | async}}</div>

Querying By Multiple Keys in Firebase

I have a list of known keys in my Firebase database
-Ke1uhoT3gpHR_VsehIv
-Ke8qAECkZC9ygGW3dEJ
-Ke8qMU7OEfUnuXSlhhl
Rather than looping through each of these keys to fetch a snapshot of their respective object, how can I query for each of these keys in one single, unified request? Does Firebase provide this?
I've discovered the Promise.all() function which looks promising (no pun intended I swear) but I'm not sure how to implement it using the standard way of fetching firebase data like so
var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then(function(snapshot) {
var username = snapshot.val().username;
});
Thanks for any help!
As David's comment suggested: if the items are in some way related, you may be able to built a query to get them all.
Otherwise this would do the trick:
var keys = [
"-Ke1uhoT3gpHR_VsehIv",
"-Ke8qAECkZC9ygGW3dEJ",
"-Ke8qMU7OEfUnuXSlhhl"
];
var promises = keys.map(function(key) {
return firebase.database().ref("/items/").child(key).once("value");
});
Promise.all(promises).then(function(snapshots) {
snapshots.forEach(function(snapshot) {
console.log(snapshot.key+": "+snapshot.val());
});
});
Note that retrieving each item with a separate request is not as slow as you may think, since the requests are all sent over a single connection. For a longer explanation of that, see Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly.

Categories