43

I would like to delete data that is older than two hours. Currently, on the client-side, I loop through all the data and run a delete on the outdated data. When I do this, the db.on('value') function is invoked every time something is deleted. Also, things will only be deleted when a client connects, and what might happen if two clients connect at once?

Where can I set up something that deletes old data? I have a timestamp inside each object created by a JavaScript Date.now().

Emir Husic
  • 717
  • 5
  • 16
carterw485
  • 798
  • 1
  • 7
  • 13
  • you can use `$interval` which get fire in each given time interval in millisecond, set it to 7200000 i.e 2 hours – gaurav bhavsar Aug 14 '15 at 07:32
  • Would this still be a client side solution? I can only display the newer data without a problem, but I'd like a way for the database to automaticly delete everything older than two hours instead of me manually needing to run a script every now and again. – carterw485 Aug 14 '15 at 07:47
  • ok, then use `remove()` method, which delete all data from firebase location. check [this](https://www.firebase.com/docs/web/api/firebase/remove.html) – gaurav bhavsar Aug 14 '15 at 08:02
  • I saw there are now scheduled functions in Firebase: https://firebase.google.com/docs/functions/schedule-functions – Zeus May 28 '20 at 18:04

5 Answers5

50

Firebase does not support queries with a dynamic parameter, such as "two hours ago". It can however execute a query for a specific value, such as "after August 14 2015, 7:27:32 AM".

That means that you can run a snippet of code periodically to clean up items that are older than 2 hours at that time:

var ref = firebase.database().ref('/path/to/items/');
var now = Date.now();
var cutoff = now - 2 * 60 * 60 * 1000;
var old = ref.orderByChild('timestamp').endAt(cutoff).limitToLast(1);
var listener = old.on('child_added', function(snapshot) {
    snapshot.ref.remove();
});

As you'll note I use child_added instead of value, and I limitToLast(1). As I delete each child, Firebase will fire a child_added for the new "last" item until there are no more items after the cutoff point.

Update: if you want to run this code in Cloud Functions for Firebase:

exports.deleteOldItems = functions.database.ref('/path/to/items/{pushId}')
.onWrite((change, context) => {
  var ref = change.after.ref.parent; // reference to the items
  var now = Date.now();
  var cutoff = now - 2 * 60 * 60 * 1000;
  var oldItemsQuery = ref.orderByChild('timestamp').endAt(cutoff);
  return oldItemsQuery.once('value', function(snapshot) {
    // create a map with all children that need to be removed
    var updates = {};
    snapshot.forEach(function(child) {
      updates[child.key] = null
    });
    // execute all updates in one go and return the result to end the function
    return ref.update(updates);
  });
});

This function triggers whenever data is written under /path/to/items, so child nodes will only be deleted when data is being modified.

This code is now also available in the functions-samples repo.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 1
    In your exemple you fetch the records saved 2 hours ago or less. Maybe you should use endAt instead of startAt. But still thank you for your answer it helped me – 0xCrema.eth May 17 '16 at 07:03
  • 1
    Good catch! Fixed. – Frank van Puffelen May 17 '16 at 09:55
  • Hi Frank, I am attempting to do something similar in Swift however I can't seem to figure out what method you are calling in the listener. I've spent a few hours searching and trying but decided it was time to ask for help! – Abhi Sep 16 '16 at 07:46
  • `snapshot.ref().remove()` on Swift with a 3.x SDK would be `snapshot.ref.removeValue()`. See the [guide](https://firebase.google.com/docs/database/ios/save-data#delete_data) and [reference](https://firebase.google.com/docs/reference/ios/firebasedatabase/interface_f_i_r_data_snapshot.html#a0b0f3c17189d70f31c62b94d244e761f) docs. – Frank van Puffelen Sep 16 '16 at 16:51
  • Thank you, I had everything right it seems I made the novice blunder of not checking my reference paths! – Abhi Sep 16 '16 at 17:57
  • Hello, I am trying to implement your solution to periodically delete a pending users. Could you please have a look at my question ? http://stackoverflow.com/questions/41696708/how-to-automatically-delete-a-user-in-firebase – Coder1000 Jan 23 '17 at 17:04
  • That's exactly what I need. But, I need it in a format suitable for Cloud Functions. Thanks, Frank. –  Jun 18 '17 at 21:15
  • In other words, I need to put the same logic in an index.js file. –  Jun 18 '17 at 21:29
  • Thanks for the edit. But I'm getting this error when I **firebase deploy** : `! functions[deleteOldItems]: Deploy Error: Failed to configure trigger deleteOldItems (Firebase Realtime Database) Functions deploy had errors. To continue deploying other features (such as database), run: firebase deploy --except functions Error: Functions did not deploy properly.` –  Jun 19 '17 at 06:02
  • To make things clearer, I asked the question here: https://stackoverflow.com/questions/44622777/getting-deploy-error-failed-to-configure-trigger-myownfunction-firebase-realti –  Jun 19 '17 at 13:54
  • There is likely a syntax error in my code somewhere. Don't start yet another new question for that. Instead help find the syntax error and fix it here. – Frank van Puffelen Jun 19 '17 at 14:46
  • I can't find the syntax error, I apologise for writing a new question​. –  Jun 19 '17 at 14:55
  • Also, I'd like to say that in my new question, I corrected a few mistakes. But, it's still not working. –  Jun 19 '17 at 15:18
  • If you found issues with the code in this answer, make an edit to the answer. Opening a new question for this code just means we have two places to scan. – Frank van Puffelen Jun 19 '17 at 17:14
  • I made a few more fixed to the code after testing it. The first deploy failed, but that seems like it was an ephemeral problem in Cloud Functions (which is still in beta). After a redeploy this works without problems. – Frank van Puffelen Jun 19 '17 at 23:42
  • I get the following event message with your code: `Function execution took 60002 ms, finished with status: 'timeout'` –  Jun 21 '17 at 07:24
  • Is this event message normal? –  Jun 21 '17 at 14:56
  • Thanks for the Cloud Functions edit by the way! Really useful. –  Jun 22 '17 at 07:02
  • 3
    When i implemented this, all the data was removed after 1 sec instead of 2 hours. I have copied and paste exactly the code, so i don't whats going on? – Dani Kemper Sep 18 '17 at 19:13
  • @DaniKemper I'm having the same issue. It seems that data is getting deleted as soon as it is added not after 2 hours. Any solution to this? – vikrantnegi Jan 25 '18 at 11:44
  • @FrankvanPuffelen I'm having an issue where it seems that data is getting deleted as soon as it is added not after 2 hours. Any idea on why this is happening? – vikrantnegi Feb 16 '18 at 07:14
  • @FrankvanPuffelen, I also wanted to delete the GeoFire indices that I had in another list. How can I delete them along with the items in the current list? – SiddAjmera Apr 01 '18 at 21:03
  • Got it. @FrankvanPuffelen, I've updated the answer accordingly. Please update if you feel there's a need to. – SiddAjmera Apr 01 '18 at 21:21
  • Good to hear that you could expand the code from my answer to also delete data from other sources in your use-case @SiddharthAjmera. Since the original question was not about that, I rolled back the edit however. While it was a useful change for you, this (short) piece of code is already complex enough without that addition. – Frank van Puffelen Apr 02 '18 at 01:22
  • 3
    Note for anyone uploading a timestamp from iOS: timeIntervalSince1970 is in seconds and Date.now() is millis so make sure to convert as necessary – Simon Feb 03 '19 at 17:48
12

I have a http triggered cloud function that deletes nodes, depending on when they were created and their expiration date.

When I add a node to the database, it needs two fields: timestamp to know when it was created, and duration to know when the offer must expire.

enter image description here

Then, I have this http triggered cloud function:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

/**
 * @function HTTP trigger that, when triggered by a request, checks every message of the database to delete the expired ones.
 * @type {HttpsFunction}
 */
exports.removeOldMessages = functions.https.onRequest((req, res) => {
    const timeNow = Date.now();
    const messagesRef = admin.database().ref('/messages');
    messagesRef.once('value', (snapshot) => {
        snapshot.forEach((child) => {
            if ((Number(child.val()['timestamp']) + Number(child.val()['duration'])) <= timeNow) {
                child.ref.set(null);
            }
        });
    });
    return res.status(200).end();
});

You can create a cron job that every X minutes makes a request to the URL of that function: https://cron-job.org/en/

But I prefer to run my own script, that makes a request every 10 seconds:

watch -n10 curl -X GET https://(your-zone)-(your-project-id).cloudfunctions.net/removeOldMessages
Sergio
  • 1,610
  • 14
  • 28
6

In the latest version of Firebase API, ref() is changed to ref

var ref = new Firebase('https://yours.firebaseio.com/path/to/items/');
var now = Date.now();
var cutoff = now - 2 * 60 * 60 * 1000;
var old = ref.orderByChild('timestamp').endAt(cutoff).limitToLast(1);
var listener = old.on('child_added', function(snapshot) {
    snapshot.ref.remove();
});
Won Jun Bae
  • 5,140
  • 6
  • 43
  • 49
5

If someone will have the same problem, but in Firestore. I did a little script that at first read documents to console.log and then delete documents from a collection messages older than 24h. Using https://cron-job.org/en/ to refresh website every 24h and that's it. Code is below.

var yesterday = firebase.firestore.Timestamp.now();
  yesterday.seconds = yesterday.seconds - (24 * 60 * 60);
  console.log("Test");
  db.collection("messages").where("date",">",yesterday)
      .get().then(function(querySnapshote) {
        querySnapshote.forEach(function(doc) {
          console.log(doc.id," => ",doc.data());
        });
      })
  .catch(function(error) {
        console.log("Error getting documents: ", error);
  });

  db.collection("messages").where("date","<",yesterday)
    .get().then(function(querySnapshote) {
      querySnapshote.forEach(element => {
        element.ref.delete();
      });
    })
4

You could look into Scheduling Firebase Functions with Cron Jobs. That link shows you how to schedule a Firebase Cloud Function to run at a fixed rate. In the scheduled Firebase Function you could use the other answers in this thread to query for old data and remove it.