Home > Mobile >  Firebase Efficiently Query Thousands of Values with Varying Permissions
Firebase Efficiently Query Thousands of Values with Varying Permissions

Time:07-30

I am building a web app where users view "targets" and write comments on them. I am currently trying to create a Profile page, where the user can view every target that they have not made a comment on yet.

My data is structured as followed:

targets: {
    100: {
      value1: 2,
      value2: 5,
      group: 1,
      numberOfComments: 2,
    },
    101: {
      value1: 3,
      value2: 1,
      group: 2,
      numberOfComments: 0,
    },
    102: {
      value1: 0,
      value2: 10,
      group: 1,
      numberOfComments: 0,
    },
  },
  groups: {
    1: {
      public: true,
    },
    2: {
      public: false,
    },
  },
  comments: {
    100: {
      user1: 'My Comment',
      user2: 'Different Comment',
    },
    101: {},
    102: {}
  },
  users: {
    user1: {
      name: 'User 1',
      admin: false,
    },
    user2: {
      name: 'User 2',
      admin: true,
    },
  },

I have the following rules configured for reading comments:

"comments": {
    "$target_id": {
        ".read": "root.child('groups/'   root.child('targets/'   $target_id   '/group').val()   '/public').val() === true || auth.token.admin === true",
        "$user_id": {
            ".write": "auth.uid === $user_id && (root.child('groups/'   root.child('targets/'   $target_id   '/group').val()   '/public').val() === true || auth.token.admin === true)",
        }
    }
},

This rule ensures that anyone can read/write comments on targets in a public group, but only admins can read/write comments on targets in a private group.

In the example, there are two users, user1 and user2, and three targets, 100, 101, and 102. user2 is an admin, and target 101 is in a private group.

The goal is to display to the user every target that they have permission to comment on, but have not yet commented on. This means that user1 should only see "Target 102", but user2 should see "Target 101" and "Target 102".

The list of targets can be read by all users, so to do this, I currently read the list of targets, and for each target id, I try to read that target's comments:

async function findTargetsWithoutUserComments() {
  let arr = [];
  for (let targetId of targetIds) {
    let comments = (await get(ref(db, `comments/${targetId}`))).val();
    if (!Object.keys(comments).find((userId) => userId === user.uid)) {
      arr.push(targetId);
    }
  }

  return arr;
}

This code works as intended, but is incredibly slow since it must individually get data from thousands of References. It would be much faster to get every comment at once, but I cannot grant read permission comments as a whole, and Firebase rules do not act as filters. Is there a more efficient way to accomplish this? Thank you.

CodePudding user response:

One of the problems in your code is that you're using await on each get call, which pretty much prevents most of the pipelining that Firebase would be able to do otherwise.

If you change your code to this, it should allow for much more paralelization of the network calls:

async function findTargetsWithoutUserComments() {
  const promises = [];
  for (let targetId of targetIds) {
    promises.push(get(ref(db, `comments/${targetId}`)));
  }

  const results = await Promise.all(promises);
  const arr = [];
  results.forEach((snapshot) => {
    const comments = snapshot.val();
    if (!Object.keys(comments).find((userId) => userId === user.uid)) {
      arr.push(tic);
    }
  });
  return arr;
}

This will speed things up immensely, but if you still want it to be even faster, consider duplicating (some of) the comment data everywhere that you might need it. This type of data duplication may seem unusual if you come from a background with relational data models, but is actually very common in NoSQL databases and is one of the reasons their read-performance scales so well.

  • Related