4

I'm writing a Firebase Cloud Function that is called from an Android app. The function should get a random user from the users node in the Database (Any random user but not the one who sent the request) and return it to the client. The problem is that the data is not structured as an array (And it should not be and can't be).

Data structure in the database.

So I can't pick a random number and read that node, I also don't want to read all the users node and iterate it because it contains a lot of users and that would be time and resources-consuming. The Question:

How can I get a random user different from the one who sent the request, and changes with every time the function is called (completely random), while not querying a lot of data from the database?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Ameer Taweel
  • 949
  • 1
  • 11
  • 37

1 Answers1

3

The main problem:

Firebase doesn't keep track of the child count, which we need for the randomization. We can however use DataSnapshot.numChildren().

To avoid this, you can add a count element to the /users node, which you would have to maintain manually, using cloud functions (e.g. when adding a user, increment count, and decrement on deletion).

On the other hand, since the cloud functions are on the server, and firebase will manage the cache for you, it is not a big deal to actually use DataSnapshot.numChildren(), since the data will be cached once the function has been executed for the first time, and will be updated only when there are changes.

Therefore I would recommend to utilize the numChildren() function:

db.ref('/users').once('value').then(snapshot => 
    Math.floor((Math.random() * snapshot.numChildren()))
);

Alternatively using the count-method:

db.ref('/users/count').once('value').then(snapshot => 
    Math.floor((Math.random() * snapshot.val()))
);

Getting the random Node:

db.ref('/users').orderByKey().startAt(random).limitToFirst(1);

Method 1:

// https://stackoverflow.com/a/38423694/4161937
// attach permanent listener to force firebase caching (not sure if it works for this case)
db.ref('/users').on('value', () => {});
// will read #random amount of items
db.ref('/users').orderByKey().limitToFirst(random).once('value').then(users => Object.keys(users)[random]);

Method 2:

// https://stackoverflow.com/a/38423694/4161937
// attach permanent listener to force firebase caching
db.ref('/users').on('value', () => {});
db.ref('/users').once('value').then(users => Object.keys()[random])

You can read more about query order- and filtering in the official docs:

Retrieving Data - How Data is Ordered

FatalMerlin
  • 1,463
  • 2
  • 14
  • 25
  • I actually can't read all the users node, it's really very large, and I need to execute the function as fast as I can, this is not a n effective solution. – Ameer Taweel Jul 26 '18 at 13:27
  • @AmeerTaweel That's why I included the count method, you have to add a `count` node and add two functions that will update it when you add or remove users. Than you can use that to get the random user. Using the count-method will reduce the reading to the node `/users/count`, which is just a single read operation of a number, and therefore really quick. The only thing left to do is to check that the user at the `random position` is not the executing user. – FatalMerlin Jul 26 '18 at 13:55
  • But the data is not structured as an array, therefore I can't use a random index. – Ameer Taweel Jul 26 '18 at 14:17
  • Ah, sorry, I forgot to address that Issue, I'll update my answer – FatalMerlin Jul 26 '18 at 14:19
  • 1
    It actually doesn't work, because the `startAt()` method needs a `string` as a parameter not an `integer`,so I tried to convert the random to a string like this: startAt(`${random}`) but the result of the query is always the first user. – Ameer Taweel Jul 26 '18 at 17:24
  • @AmeerTaweel I see, I totally overlooked that, but it always returns the first one, since it can't find a node with the key `random`. I updated my answer once again, but there is no way around this without depending on the caching, only limiting the user count that has to be read. – FatalMerlin Jul 26 '18 at 18:14
  • I will try another way since I can't read the whole `users` node. – Ameer Taweel Jul 27 '18 at 13:03