7

I have action that fetches user model from db to check access.

Then I have method that in some cases requires same model.

function checkAccess(req, res, next) {
    var data = Data.fetch(req.params._id);
    if (data.userId === req.user._id) {
        return res.sendStatus(401);
    }
    return next();
}

Router()
    .get('/:_id',
        checkAccess,
        function (req, res, next) {
            var data = Data.fetch(req.params._id);
            update(data);
            return res.json(data);
        });

Question isn't about how to reuse data in exactly that case (the simplest solution I found is use res.local).

My confusion is about fetching same data from db twice, when it theoretically can be reused.

It isn't heavy request, so caching layer unlikely increase access speed in comparison with access to db. But using cache can introduce many headache with invalidation.

I found that using request-related (res.local) cache can particularly solve the problem, but it makes my code more verbose and complex. I check every piece of data whether it was fetched, and also should make decision is it possible that this data outdated. Also I pass arguments through several methods to reuse it, so it pollutes my internal API (e.g. when I send email I reuse data needed for rendering email template, so I can't just use mailService.sendInvite(userId), but mailService.sendInvite(userId, {user: user, event: event})).

Should I worry about fetching small pieces of the same data from db multiple times per request?

1
  • 1
    Don't forget about reverse proxies / web servers, which could cache resultsets too. Commented Jul 5, 2015 at 16:36

3 Answers 3

2

I'd suggest to make certain that your database is being leveraged to its full extent before conjuring up any kind of caching mechanism.

Just because a database is "big" that doesn't necessarily mean querying for a bit will be "slow". Slow is subjective, and if it can be done in the timeframe required for the application response time to be sufficient, then I'd say it isn't "slow". If the table being queried is indexed on the id column which you are querying, then checking the access will not take any longer than if the table holds 10 or 10 million.

Also, the database engine will be caching internally for you. It will be internally tracking if the row correlating with id X has been changed since it was last queried, and if it hasn't been changed it will retrieve from its own cache.

Because of these points, I do not see it as being a problem to check access every request. If something external can change the access privileges to the data in the system, then doing your own caching runs the risk of someone's access being severed but your app allowing access. The database layer is the best place for caching to take place, so let it.

0

It looks like you could integrate CheckAccess into a higher-level data-fetching function.

var GetSomeData = function(req) {
  var data = Data.fetch(req.param_id);
  if (data.userId === req.user._id) {
    return {'error': 401};
  }
  return {'success': data};
}

// ...
// in client code

var reply = GetSomeData(req);
if (reply.error) {
  res.sendStatus(reply.error);
  return res;
}
else {
  return res.json(reply.success);
}

I see how attractive and reusable methods like checkAccess() could be. But these need certain information to actually check access. You can either haul this security context around as an argument, or pass it into a constructor / factory method. Consider:

var securityContext = getSecurityContext(request);  // fetches security data
var mailService = new MailService(securityContext);
var orderProcessor = new OrderProcessor(securityContext);
try {
  var orderId = orderProcessor.createOrder(request);
  mailService.sendConfirmationEmail(orderId, ...);
} catch (error) {
  // report the details of the error
}

With securityContext passed in constructor, any of your services can reuse it without re-fetching. getSecurityContext() should fetch enough security-related data to avoid more round-trips for it, or at least make them improbable.

3
  • Thank you for idea. I didn't think about moving acl to model layer. It interesting idea worth a try. However it helps only in this case. What do you think about example with mailing? And about problem in general? Probably question about complexity vs optimization, so it doesn't have the right answer. But I'm looking for something that maybe I missed. Commented May 6, 2015 at 10:18
  • I've updated my answer. The approach really depends on your needs. In a commercial setting, every 100 ms of delay cost you a certain percent of visitors closing the tab. In an internal system, you might have other priorities before you start to optimize for speed, and your network is likely a fast LAN.
    – 9000
    Commented May 6, 2015 at 14:24
  • My question is more about reusing data in general than for security. In your example assume that MailService and OrderProcessor needs same data e.g. Discounts. MailService can require it for rendering email template. And OrderProcessor require it for some processing. It's possible to fetch it outside of services and then pass it into. But it complicates usage of services, and makes harder to maintain (if I need one more piece of data, I should update code everywhere service used) Commented May 7, 2015 at 12:50
0

Database access can be slow if the database is big. So if you have challenging performance needs, you are right to think about this.

The first thing is to consider whether you can provide a single point where checkAccess is called. If not, your application has a security risk, which is somewhere where access should be checked, it has not been.

Secondly, if you need caching, then remember you aren't the first person to face this question. Here is a general fix, for Postgresql users: https://www.postgresql.org/about/news/1296/ If you aren't using Postgresql, then there might be one for your database.

If the database is on another machine then each access involves the TCP stack, which is a resource hog. A query cache in the database won't fix this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.