{"action":"created","issue":{"url":"https://api.github.com/repos/strongloop/loopback/issues/2076","repository_url":"https://api.github.com/repos/strongloop/loopback","labels_url":"https://api.github.com/repos/strongloop/loopback/issues/2076/labels{/name}","comments_url":"https://api.github.com/repos/strongloop/loopback/issues/2076/comments","events_url":"https://api.github.com/repos/strongloop/loopback/issues/2076/events","html_url":"https://github.com/strongloop/loopback/issues/2076","id":134220759,"number":2076,"title":"Cache query results","user":{"login":"bajtos","id":1140553,"avatar_url":"https://avatars.githubusercontent.com/u/1140553?v=3","gravatar_id":"","url":"https://api.github.com/users/bajtos","html_url":"https://github.com/bajtos","followers_url":"https://api.github.com/users/bajtos/followers","following_url":"https://api.github.com/users/bajtos/following{/other_user}","gists_url":"https://api.github.com/users/bajtos/gists{/gist_id}","starred_url":"https://api.github.com/users/bajtos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bajtos/subscriptions","organizations_url":"https://api.github.com/users/bajtos/orgs","repos_url":"https://api.github.com/users/bajtos/repos","events_url":"https://api.github.com/users/bajtos/events{/privacy}","received_events_url":"https://api.github.com/users/bajtos/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/strongloop/loopback/labels/feature","name":"feature","color":"bfe5bf"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":3,"created_at":"2016-02-17T09:04:16Z","updated_at":"2016-02-17T10:44:16Z","closed_at":null,"body":"https://github.com/strongloop/loopback-datasource-juggler/pull/812#issuecomment-178478203 describes the following use case:\r\n\r\nOur API is starting to have huge traffic and to gain perfomance, we need to implement a cache system. To do that, we want to use the operation hooks (Example):\r\n```\r\nModel.observe('access', function (context, cb) {\r\n  // check the cache\r\n  // short-cirtcuit to cb(null, cached) when found\r\n});\r\n\r\nModel.observe('all_loaded', function (context, cb) {\r\n  // update the cache\r\n});\r\n```\r\n\r\nImplementing a good caching solution is difficult, as there are many edge case to handle correctly. For example, should `findOrCreate` use the cached data too? `findById`? How should we invalidate the cached data, do we want WRITE operations like `create` to invalidate records, or is it enough to use a time-based invalidation, where cache records expire after a given time interval, and thus clients may receive stale data?\r\n\r\nI think LoopBack should provide a built-in caching mechanism with hooks for customizing cache fetch & update, possibly with components providing implementations of these hooks for common caching backends (in-memory, memcache, redis, etc.)"},"comment":{"url":"https://api.github.com/repos/strongloop/loopback/issues/comments/185149122","html_url":"https://github.com/strongloop/loopback/issues/2076#issuecomment-185149122","issue_url":"https://api.github.com/repos/strongloop/loopback/issues/2076","id":185149122,"user":{"login":"bajtos","id":1140553,"avatar_url":"https://avatars.githubusercontent.com/u/1140553?v=3","gravatar_id":"","url":"https://api.github.com/users/bajtos","html_url":"https://github.com/bajtos","followers_url":"https://api.github.com/users/bajtos/followers","following_url":"https://api.github.com/users/bajtos/following{/other_user}","gists_url":"https://api.github.com/users/bajtos/gists{/gist_id}","starred_url":"https://api.github.com/users/bajtos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bajtos/subscriptions","organizations_url":"https://api.github.com/users/bajtos/orgs","repos_url":"https://api.github.com/users/bajtos/repos","events_url":"https://api.github.com/users/bajtos/events{/privacy}","received_events_url":"https://api.github.com/users/bajtos/received_events","type":"User","site_admin":false},"created_at":"2016-02-17T10:44:16Z","updated_at":"2016-02-17T10:44:16Z","body":"> I like the idea with the base cached model :-)\r\n\r\nI am glad to hear that :)\r\n\r\n> But we will not be able to cache native model like ACLs, RoleMapping, ...\r\n> These models are requested for each REST Calls and the perfs will really increase with caching.\r\n\r\nThat's a very valid point. In that case I am proposing to use a [mixin](https://docs.strongloop.com/display/LB/Defining+mixins)-based approach.\r\n\r\n```js\r\n// server/mixins/cache-queries.js\r\nmodule.exports = function(Model, options) {\r\n    // wait until the datasource defined `find` method\r\n  CachedModel.on('dataAccessConfigured', function() {\r\n    var Model = this;\r\n    Model._findLive = CachedModel.find;\r\n    Model.find = function(filter,...,cb) {\r\n      var cached = // check the cache\r\n      if (cached) \r\n        cb(null, cached);\r\n      else\r\n        Model._findLive(filter,...,cb);\r\n    };\r\n  });\r\n};\r\n```\r\n\r\nNow I am not sure if `loopback-boot` supports applying mixins via `server/model-config.json` file. As a workaround, one can setup this mixin via a boot script.\r\n\r\n```js\r\n// server/boot/cache.js\r\nvar cacheQueries = require('../mixins/cache-queries.js');\r\nmodule.exports = function(app) {\r\n  cacheQueries(app.models.ACL);\r\n  cacheQueries(app.models.RoleMapping);\r\n  // etc.\r\n};\r\n```\r\n\r\nI can imagine this two bits of code can be neatly wrapped in a reusable component which I imagine will be configured as follows:\r\n\r\n```\r\n// server/component-config.json\r\n{\r\n  \"loopback-component-cache-queries\": {\r\n    \"models\": [\"ACL\", \"RoleMapping\"],\r\n    \"store\": \"memory\"\r\n  }\r\n}\r\n```\r\n"}}