{"action":"created","issue":{"url":"https://api.github.com/repos/ben-manes/caffeine/issues/44","labels_url":"https://api.github.com/repos/ben-manes/caffeine/issues/44/labels{/name}","comments_url":"https://api.github.com/repos/ben-manes/caffeine/issues/44/comments","events_url":"https://api.github.com/repos/ben-manes/caffeine/issues/44/events","html_url":"https://github.com/ben-manes/caffeine/issues/44","id":124531128,"number":44,"title":"Add the ability to asynchronously/proactively refresh recently used keys that are about to be expired","user":{"login":"abatkin","id":1243457,"avatar_url":"https://avatars.githubusercontent.com/u/1243457?v=3","gravatar_id":"","url":"https://api.github.com/users/abatkin","html_url":"https://github.com/abatkin","followers_url":"https://api.github.com/users/abatkin/followers","following_url":"https://api.github.com/users/abatkin/following{/other_user}","gists_url":"https://api.github.com/users/abatkin/gists{/gist_id}","starred_url":"https://api.github.com/users/abatkin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abatkin/subscriptions","organizations_url":"https://api.github.com/users/abatkin/orgs","repos_url":"https://api.github.com/users/abatkin/repos","events_url":"https://api.github.com/users/abatkin/events{/privacy}","received_events_url":"https://api.github.com/users/abatkin/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2016-01-01T05:07:51Z","updated_at":"2016-01-03T02:25:02Z","closed_at":"2016-01-03T02:25:02Z","body":"At a high-level, I want to cache a set of expensive computations for a set period of time (time-based, `expireAfterWrite`). I would also like to be able to proactively refresh keys (well, their values) that have been used \"recently\" (whatever that means) but are about to be expired/evicted. I don't want to refresh everything in the cache, since then the cache will grow forever.\r\n\r\nFor example: If I'm happy with ~1hr stale values, I can use `LoadingCache` and `expireAfterWrite(60, TimeUnit.MINUTES)`. Everything works well, except that every hour-or-so, all of the \"hot\" keys suddenly take a long time to return (since they have been expired and now need to be recomputed the next time they are accessed).\r\n\r\nThis type of setup would be valuable when, for example, some of your keys are accessed frequently and repeatedly, and others are only rarely used, and you want to avoid a short period of slow access to those \"hot\" values when they expire. In my case, this is particularly bad since the common access pattern is such that most of the keys will get loaded when the system initially starts up (i.e. all at the same time) so they will all expire at the same time and then need to be reloaded at the same time (the startup delay is long to do the initial population - now I have to pay that same cost every N Time Units (where N Time Units is my `expireAfterWrite()` time) instead of being proactive about it).\r\n\r\nOne potential generic solution would be to add a pre-expire event which would run at a configurable time offset before any item is to be expired/evicted. The event would need access to the \"last read timestamp\" (and possibly other metadata?) of the key (in my case, so it could decide if it wanted to refresh the value). You could do anything you wanted. In my case, I would just call `refresh(key)` whenever the last read timestamp was recent enough (i.e. this is up to my own business logic).\r\n\r\nThere are a bunch of other potential solutions to this problem that I can think of, but this one is the most generic that I have found (so hopefully other people can find other good uses).\r\n\r\nI think there would need to be a few rules around how this API would work:\r\n* Events are delivered on a best-effort basis (you might want to be notified 60 seconds before something is going to expire, but your callback gets run 53 seconds before)\r\n* Events happen asynchronously, possibly with their own Executor (they don't block anything else from happening)\r\n\r\nThe next logical step would be to make this a \"bulk\" API - then you would need to specify some granularity for how often to sweep for things that might expire. You might want to only check every X time units, which means your callback gets a collection of all keys/values that will expire during that X time unit period). For my use case, I'd also need a bulk `refresh()` API (similar to the way you can override `loadAll()` to load values in bulk) which doesn't currently exist either).\r\n\r\nThoughts?\r\n"},"comment":{"url":"https://api.github.com/repos/ben-manes/caffeine/issues/comments/168450419","html_url":"https://github.com/ben-manes/caffeine/issues/44#issuecomment-168450419","issue_url":"https://api.github.com/repos/ben-manes/caffeine/issues/44","id":168450419,"user":{"login":"ben-manes","id":378614,"avatar_url":"https://avatars.githubusercontent.com/u/378614?v=3","gravatar_id":"","url":"https://api.github.com/users/ben-manes","html_url":"https://github.com/ben-manes","followers_url":"https://api.github.com/users/ben-manes/followers","following_url":"https://api.github.com/users/ben-manes/following{/other_user}","gists_url":"https://api.github.com/users/ben-manes/gists{/gist_id}","starred_url":"https://api.github.com/users/ben-manes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ben-manes/subscriptions","organizations_url":"https://api.github.com/users/ben-manes/orgs","repos_url":"https://api.github.com/users/ben-manes/repos","events_url":"https://api.github.com/users/ben-manes/events{/privacy}","received_events_url":"https://api.github.com/users/ben-manes/received_events","type":"User","site_admin":false},"created_at":"2016-01-03T02:25:02Z","updated_at":"2016-01-03T02:25:02Z","body":"Great! =)\r\n\r\nAs a notification you might use `replace` to avoid a refresh from stampeding over an explicit write (if that's used). The races on the client would be the same internally, unfortunately. We would need to lock all entries, batch load, populate, and unlock. We can't do that with `synchronized` except in raw byte code, so a `getAll` is racy. The `AsyncLoadingCache` is able to perform a blocking `getAll` by inserting future that proxy to the batch operation. I can't see how to reuse that trick for a batch refresh, though, so I think it would always be racy by allowing duplicate loads.\r\n\r\nIf you come across ways to improve the documentation please let me know."}}