Giter VIP home page Giter VIP logo

con_cache's People

Contributors

aerosol avatar afulki avatar cy303 avatar dvic avatar elainenaomi avatar fcevado avatar jamonholmgren avatar jonzlin95 avatar jur0 avatar kennethito avatar kianmeng avatar linjunpop avatar liveforeverx avatar lowks avatar nagasaki45 avatar noaccos avatar praveenperera avatar rubemz avatar sasa1977 avatar scarfacedeb avatar secretmapper avatar slashmili avatar stefanchrobot avatar tap349 avatar tfwright avatar volgar1x avatar vorce avatar warmwaffles avatar zuckschwerdt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

con_cache's Issues

ConCache.update doesn't honor ttl configuration

Hi,

If I create a process with ttl setting and later use ConCache.update/3 to set the value:

ConCache.update(cache, :a, fn(_) -> {:ok, 1} end)

The :a item never gets expired. Is it correct behaviour? I'll push a failing scenario

(EXIT) time out when calling a get_or_store

I've had the following error happen a few times. At first I assumed it was the server under high load but it's happened under moderate/low load.

** (stop) exited in: GenServer.call(#PID<0.4240.0>, {:lock, 31185, #Reference<0.3051168309.1385955342.117529>}, 5000)
    ** (EXIT) time out
    (elixir 1.14.3) lib/gen_server.ex:1038: GenServer.call/3
    (con_cache 1.0.0) lib/con_cache/lock.ex:22: ConCache.Lock.exec/4
    (central 0.1.0) lib/teiserver/protocols/spring/spring_out.ex:485: Teiserver.Protocols.SpringOut.do_reply/2

The call that triggers this is ultimately a ConCache.get_or_store call. It's one of our more commonly called functions (converting a userid to a username) if that helps.

What would be the most appropriate way to deal with this, debug it and the like?

Crashed cache won't work anymore after restart

Unfortunately I don't have much more information to try to reproduce, but if ConCache crashes, after the Supervisor restarts it keeps repeating:

** (MatchError) no match of right hand side value: []
      (con_cache) lib/con_cache/registry.ex:18: ConCache.Registry.get/1
      (con_cache) lib/con_cache.ex:143: ConCache.get/2
    (fred_engine) lib/engine/message.ex:5: Fred.Engine.Message.work/1
         (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
         (stdlib) erl_eval.erl:228: :erl_eval.expr/5
         (elixir) lib/enum.ex:1628: anonymous fn/3 in Enum.reduce/3
         (elixir) lib/range.ex:92: Enumerable.Range.reduce/5
         (elixir) lib/enum.ex:1627: Enum.reduce/3

get_or_store for bag types

As we discussed in the pull request #26 , I'm opening this issue to discuss about the usage of get_or_store for bag types. I'll prepare a summary of what was discussed in the pr about this subject.

Unit Test Failure 'test balancer (LockTest)' on Elixir 1.0.0-rc2

$ mix test

17:31:52.946 [error] Error in process <0.133.0> with exit value: {badarg,[{erlang,send,[nil,{'$gen_cast',{lock,<0.133.0>,2}}],[]},{'Elixir.GenServer',do_send,2,[{file,"lib/gen_server.ex"},{line,424}]},{'Elixir.ConCache.Lock',exec,4,[{file,"lib/con_cache/lock.ex"},{line,24}]}]}

17:31:52.947 [error] Error in process <0.136.0> with exit value: {badarg,[{erlang,send,[nil,{'$gen_cast',{lock,<0.136.0>,2}}],[]},{'Elixir.GenServer',do_send,2,[{file,"lib/gen_server.ex"},{line,424}]},{'Elixir.ConCache.Lock',exec,4,[{file,"lib/con_cache/lock.ex"},{line,24}]}]}

17:31:52.947 [error] Error in process <0.139.0> with exit value: {badarg,[{erlang,send,[nil,{'$gen_cast',{lock,<0.139.0>,2}}],[]},{'Elixir.GenServer',do_send,2,[{file,"lib/gen_server.ex"},{line,424}]},{'Elixir.ConCache.Lock',exec,4,[{file,"lib/con_cache/lock.ex"},{line,24}]}]}

  1. test balancer (LockTest)
    test/lock_test.exs:8
    Assertion with == failed
    code: conduct_test({ConCache.BalancedLock, ConCache.BalancedLock.start_link()}) == [{0, 18}, {1, 22}, {2, 15}]
    lhs: [{0, 18}, {1, 22}]
    rhs: [{0, 18}, {1, 22}, {2, 15}]
    stacktrace:
    test/lock_test.exs:9

...............................

Finished in 5.2 seconds (0.3s on load, 4.9s on tests)
32 tests, 1 failures

Randomized with seed 937703

Caches not expiring

I have a few relatively simple caches that look like this:

      Supervisor.child_spec(
        {ConCache, [name: :active_users_cache, ttl_check_interval: :timer.seconds(10), global_ttl: :timer.minutes(1)]},
        id: {ConCache, :active_users_cache}
      ),
     # .... more with exp
      Supervisor.child_spec(
        {ConCache, [name: :jobs_cache, ttl_check_interval: false]},
        id: {ConCache, :jobs_cache}
      )

The active_users_cache just contains a user id and a timestamp (their latest request), added like so:

      ConCache.put(:active_users_cache, [id], %ConCache.Item{value: DateTime.utc_now()})

But AFAICT, it's never expiring. When I inspect the ets table I see entries like this:

  {"250", ~U[2024-02-02 18:08:06.498382Z]},
  {"26", ~U[2024-02-02 18:26:50.815029Z]},
  {"236", ~U[2024-02-02 18:49:55.533782Z]},
  {"279", ~U[2024-02-02 18:49:55.812741Z]},
  {"276", ~U[2024-02-02 18:07:00.917742Z]},
  {"34", ~U[2024-02-02 18:49:01.212477Z]},
  {"275", ~U[2024-02-02 16:48:50.586609Z]},
  {"14", ~U[2024-02-02 16:55:30.335637Z]}

My understanding would be with my ttl settings I should never expect to see 2 entries more than 70 secs apart since the put should overwrite the last value, meaning the timestamp should reflect the last time the ttl was set.

Aside from exp the values all look correct and it works as expected.

Is there anything obvious I am doing wrong? What is the recommended way to go about troubleshooting this?

get_or_store with a fallback feature to check for value elsewhere and load into cache

I would like to perform a get_or_store, check if the value exists in the cache, if not, I would like to call db store lookup function, reload the result of that into the cache and if that return is {:ok, nil} I would like to inform the cache that the value doesn't exist.

My use case is as follows: I want to save a copy of my cache entry to the database so that I can persist that value over my cluster (Riak). If a user checks for a value that is missing in the cache, the system would check if that value exists in the db store, if it does then it loads it into the cache.

On cache delete, I would also like to delete the instance of that key from the data store.

Misleading readme file

In the readme file the last line contains I don't maintain that project anymore, so I'm not aware of its current status. although that claim is more than 3 years old. Since then a lot of changes have been done.
I guess it should be removed as it can make people look for other similar projects instead of trying this one.

TTL expiry query

I think I got this wrong. My understanding was this:
ttl_check: :timer.seconds(15),

To me, that means the duration of the interval it takes to run/rerun the cache cleanup for expired cache

So in my code, I am setting the expiry to about 30days, I wanted this code to run every say 15 seconds and ONLY delete the expired cache. It is not working that way, what it's doing is deleting all my caches irrespective of their expiry time.

What was the intended use case for this and how can I achieve my original goal?

Identic return for get_or_store

The get_or_store function can not be properly used due to the fact that it returns different values whether it gets or stores.

If I'm missing the intended use case, please provide an example. Otherwise the correct implementation should be to always return the value, either after get, either after storing (instead of :ok).

Is there an easy way to flush the cache between tests?

First off, thanks for the great library. It's working exactly as advertised.

One issue I'm running into is how to manage ConCache in my tests. I was having order-dependent failures until I realized that the cached values were persisting between tests. I looked for a ConCache.flush (or similar) function but could not fine one. For now I am manually calling ConCache.delete for each key that has been cached. This is pretty cumbersome and brittle so a function to simplify this would be much appreciated.

Persistent cache with :file2tab doesn't work

Trying to follow #18 for persistent caching.

It seems that trying to save the cache to a tab using :file2tab works but restoring the cache doesn't

ConCache.put(:table_name, :test, "hello")

:table_name
  |> ConCache.ets
  |> :ets.tab2file('file-path.tab')

# restart and inside start_link
{:ok, ref} = :ets.file2tab('file-path.tab')
# reference returned by this is wrong and not the 
# same as `ConCache.ets(:table_name)`

:ets.tab2list(ref) # [test: "hello"]
ConCache.get(:table_name, :test) # nil

Support ttl: :keep to Not Update TTL on Updates

It would be nice to prevent touching the TTL when updating. I'm using con_cache as a rate limit store, so it should simply expire after a duration after the key is first created. Updates wouldn't affect the ttl.

Looking for something like:

{limit, duration} = {15, :min}

used = ConCache.get_or_store(:ratelimit, key, fn ->
  %ConCache.Item{value: 0, ttl: ttl(limit, duration)}
end)

if used >= limit do
  conn = conn |> Plug.Conn.send_resp(429, "Too many requests") |> Plug.Conn.halt
else
  ConCache.update(:tally, key, fn old_value ->
    {:ok, %ConCache.Item{value: min(old_value + 1, limit), ttl: :keep}
  end)

  used = ConCache.get(:tally, key)
  conn = conn |> Plug.Conn.merge_resp_headers(%{"x-ratelimit-remaining" => to_string(max(limit - used, 0))})
end

TTL Check should have a default value or produce a warning

I think it's a bit non-intuitive to not have a default TTL Check.

If a default one is unwieldy (as it should be based on an application's requirements) I think a more prominent warning on the console on development mode would be great

Support to ets table types

I've tried to start a :bag table, but when I retrieved the data only returned the last value inserted.
There is any plan on support other type of tables then :set?

concahe's weird behaviour on ConCache.get

I am using ConCahe for saving an image with its modified timestamps. It works fine, normally, and when I say normally then it works fine in logs as

  def update_cache_and_save_thumbnail(camera_exid, timestamp, image) do
    {last_save_date, t, img} = ConCache.dirty_get_or_store(:camera_thumbnail, camera_exid, fn() ->
      {Calendar.DateTime.now!("UTC"), timestamp, image}
    end)
    Logger.info "This is dirty get or store"
    IO.inspect ConCache.get(:camera_thumbnail, camera_exid)
    Logger.info "==========================="
    case Calendar.DateTime.diff(Calendar.DateTime.now!("UTC"), last_save_date) do
      {:ok, seconds, _, :after} ->
        thumbnail_save_seaweedfs(camera_exid, image, timestamp, last_save_date, seconds)
        Logger.info "This is after thumnail save_seaweedfs"
        IO.inspect ConCache.get(:camera_thumbnail, camera_exid)
        Logger.info "==========================="
      _ ->
        Logger.info "This is will never run i think"
        IO.inspect ConCache.get(:camera_thumbnail, camera_exid)
        Logger.info "==========================="
        ConCache.dirty_put(:camera_thumbnail, camera_exid, {last_save_date, timestamp, image})
    end
  end

when this function run and I am calling ConCache.get(:camera_thumbnail, "blessington_court") in between and it's giving me an updated value right away. such as

[info] This is dirty get or store
{#DateTime<2018-10-09 06:58:37.474918+00:00 UTC UTC>, 1539067937,
 <<255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 2, 0, 0, 1, 0, 1, 0, 0, 255,
   254, 0, 15, 10, 1, 91, 188, 80, 34, 11, 91, 188, 80, 34, 11, 1, 255, 254, 0,
   15, 10, 0, 1, 219, 5, 80, ...>>}
[info] ===========================
{#DateTime<2018-10-09 06:58:37.474918+00:00 UTC UTC>, 1539067919,
 <<255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 2, 0, 0, 1, 0, 1, 0, 0, 255,
   254, 0, 15, 10, 1, 91, 188, 80, 21, 58, 91, 188, 80, 21, 58, 1, 255, 254, 0,
   15, 10, 0, 1, 219, 5, 80, ...>>}
[info] This is after thumnail save_seaweedfs
[info] ===========================
[info] This is dirty get or store
{#DateTime<2018-10-09 06:58:37.474918+00:00 UTC UTC>, 1539067919,
 <<255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 2, 0, 0, 1, 0, 1, 0, 0, 255,
   254, 0, 15, 10, 1, 91, 188, 80, 21, 58, 91, 188, 80, 21, 58, 1, 255, 254, 0,
   15, 10, 0, 1, 219, 5, 80, ...>>}
[info] ===========================
{#DateTime<2018-10-09 06:58:37.474918+00:00 UTC UTC>, 1539067934,
 <<255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 2, 0, 0, 1, 0, 1, 0, 0, 255,
   254, 0, 15, 10, 1, 91, 188, 80, 31, 78, 91, 188, 80, 31, 78, 1, 255, 254, 0,
   15, 10, 0, 1, 219, 5, 80, ...>>}
[info] This is after thumnail save_seaweedfs
[info] ==========================

for once it gives duplicate but that's because it's been looking in cache. but whenever I do in the remote console as ConCache.get(:camera_thumbnail, "blessington_court"), It always gives the very old value. not the updated one.

{#DateTime<2018-10-09 06:25:56.691951+00:00 UTC UTC>, 1539065928,
 <<255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 2, 0, 0, 1, 0, 1, 0, 0, 255,
   254, 0, 15, 10, 1, 91, 188, 72, 63, 84, 91, 188, 72, 63, 84, 1, 255, 254, 0,
   15, 10, 0, 1, 219, 5, 80, ...>>}

I have read the module docs for concache, But I didn't find anything suitable which can pas the theory that it will give new value right away but old one when I can method by myself.? Can you guide me what is wrong happening in this? I tried this several times but it's giving me an old value always.

Items don't expire any more

Recently, I noticed that cache items don't expire anymore. Results from a new phoenix v1.3.2 app:

iex(2)> {:ok, cache} = ConCache.start_link(ttl_check_interval: :timer.seconds(1), global
_ttl: :timer.seconds(5))
{:ok, #PID<0.403.0>}
iex(3)> ConCache.put(cache, :foo, :bar)
:ok
iex(4)> ConCache.get(cache, :foo)
:bar
iex(5)> :timer.sleep(:timer.seconds(10))
:ok
iex(6)> ConCache.get(cache, :foo)
:bar
iex(7)>

Am I missing something? Thanks.

An easy way to create global caches

Hello,

I'm a Elixir/OTP very beginner and I'm struggling with workers and supervisors. Even if I'm learning how to use OTP correctly, is there any easy/faster way to create global caches ? Here is my thought translated to code :

defmodule MyApp.Cache do
   use ConCache
end

defmodule MyApp.Supervisor do
  use Supervisor

  def start_link do
    :supervisor.start_link(__MODULE__, [])
  end

  def init([]) do
    children = [
      worker(MyApp.Cache, []),
    ]

    supervise(children, strategy: :one_for_one)
  end
end

MyApp.Supervisor.start_link
value = MyApp.Cache.get :key

Compatibility with module-based supervisors

Version information:

  • Alpine Linux 3.7 (using Docker)
  • Elixir 1.5.3 (compiled with OTP 20)
  • ConCache 0.12.1

Currently attempting to integrate ConCache into my existing project, which uses a module-based supervisor to manage child processes:

defmodule MyService.Supervisor do
  use Supervisor

  def start_link(opts) do
    Supervisor.start_link(__MODULE__, :ok, opts)
  end

  def init(:ok) do
    children = [
      ...
      {ConCache, [name: :cache, ets_options: [:set]]}, id: :cache_1, type: :supervisor)
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end

Then, I attempt to call my cache from one of the other children processes in the above Supervision tree. However, my application is crashing with the following error:

The module ConCache was given as a child to a supervisor 
but it does not implement child_spec/1.

...

However, if you don't own the given module and it doesn't implement
child_spec/1, instead of passing the module name directly as a supervisor
child, you will have to pass a child specification as a map:

    %{
      id: ConCache,
      start: {ConCache, :start_link, [arg1, arg2]}
    }

See the Supervisor documentation for more information.

So, I replaced my Supervisor.child_spec/2 function as follows:

...
children = [
  ...
  %{
    id: :cache_1,
    start: {ConCache, :start_link, [[name: :cache, ets_options: [:set]]]},
    type: :supervisor
  }
]

This causes my application to crash with the following error:

[error] Task #PID<0.231.0> started from #PID<0.222.0> terminating
** (stop) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (con_cache) lib/con_cache/owner.ex:17: ConCache.Owner.cache/1
    (con_cache) lib/con_cache.ex:149: ConCache.put/3

Is there an existing workaround to get this functioning?

Problem with ConCache.update on the second update with ttl: :no_update

Hi,

I am using con_cache version 0.10.0

When I run these commands I face an error :

iex(1)> options = [ttl_check: :timer.seconds(1),ttl: :timer.seconds(1)]
[ttl_check: 1000, ttl: 1000]
iex(2)> gen_server_options = []
[]
iex(3)> {:ok, cache} = ConCache.start_link(options, gen_server_options)
{:ok, #PID<0.315.0>}
iex(4)> ConCache.update(cache, :a, fn(_old) -> {:ok, %ConCache.Item{value: 1, ttl: :timer.seconds(10)}} end)
:ok
iex(5)> ConCache.get(cache, :a)
1
iex(6)> ConCache.update(cache, :a, fn(value) -> {:ok, %ConCache.Item{value: value + 1, ttl: :no_update}} end)
:ok
iex(7)> ConCache.get(cache, :a)
2
** (EXIT from #PID<0.311.0>) an exception was raised:
    ** (FunctionClauseError) no function clause matching in ConCache.Owner.store_ttl/3
        (con_cache) lib/con_cache/owner.ex:153: ConCache.Owner.store_ttl(%ConCache.Owner{current_time: 1, max_time: 65535, monitor_ref: #Reference<0.0.1.237>, on_expire: #Function<0.74405522/1 in ConCache.Owner.start_ttl_loop/1>, pending: 159802, pending_ttl_sets: #HashDict<[a: :no_update]>, ttl_check: 1000, ttls: 163899}, :a, :no_update)
        (elixir) lib/enum.ex:610: anonymous fn/3 in Enum.each/2
        (elixir) lib/enum.ex:1478: anonymous fn/3 in Enum.reduce/3
        (elixir) lib/hash_dict.ex:188: HashDict.do_reduce_each/4
        (elixir) lib/enum.ex:1477: Enum.reduce/3
        (elixir) lib/enum.ex:609: Enum.each/2
        (con_cache) lib/con_cache/owner.ex:113: ConCache.Owner.apply_pending_ttls/1
        (con_cache) lib/con_cache/owner.ex:182: ConCache.Owner.handle_info/2
        (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
        (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
        (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

iex(8)> [error] GenServer #PID<0.315.0> terminating
** (FunctionClauseError) no function clause matching in ConCache.Owner.store_ttl/3
    (con_cache) lib/con_cache/owner.ex:153: ConCache.Owner.store_ttl(%ConCache.Owner{current_time: 1, max_time: 65535, monitor_ref: #Reference<0.0.1.237>, on_expire: #Function<0.74405522/1 in ConCache.Owner.start_ttl_loop/1>, pending: 159802, pending_ttl_sets: #HashDict<[a: :no_update]>, ttl_check: 1000, ttls: 163899}, :a, :no_update)
    (elixir) lib/enum.ex:610: anonymous fn/3 in Enum.each/2
    (elixir) lib/enum.ex:1478: anonymous fn/3 in Enum.reduce/3
    (elixir) lib/hash_dict.ex:188: HashDict.do_reduce_each/4
    (elixir) lib/enum.ex:1477: Enum.reduce/3
    (elixir) lib/enum.ex:609: Enum.each/2
    (con_cache) lib/con_cache/owner.ex:113: ConCache.Owner.apply_pending_ttls/1
    (con_cache) lib/con_cache/owner.ex:182: ConCache.Owner.handle_info/2
    (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: :check_purge
State: %ConCache.Owner{current_time: 1, max_time: 65535, monitor_ref: #Reference<0.0.1.237>, on_expire: #Function<0.74405522/1 in ConCache.Owner.start_ttl_loop/1>, pending: 159802, pending_ttl_sets: #HashDict<[a: :no_update]>, ttl_check: 1000, ttls: 163899}

Something strange is going on, I wanted to reproduce the same problem in con_cache tests but the same code works on the test

diff --git a/test/con_cache_test.exs b/test/con_cache_test.exs
index 9f6f60a..c6113f8 100644
--- a/test/con_cache_test.exs
+++ b/test/con_cache_test.exs
@@ -200,6 +200,16 @@ defmodule ConCacheTest do
       end)
   end

+  test "first update with ttl and second update without touching ttl" do
+    with_cache(
+      [ttl_check: :timer.seconds(1),ttl: :timer.seconds(1)],
+      fn(cache) ->
+        ConCache.update(cache, :a, fn(_old) -> {:ok, %ConCache.Item{value: 1, ttl: :timer.seconds(1)}} end)
+        ConCache.update(cache, :a, fn(value) -> {:ok, %ConCache.Item{value: value + 1, ttl: :no_update}} end)
+        assert ConCache.get(cache, :a) == 2
+      end)
+  end
+
   defp test_renew_ttl(cache, fun) do
     ConCache.put(cache, :a, 1)
     :timer.sleep(50)
mix test
....................................

Finished in 5.4 seconds (0.2s on load, 5.2s on tests)
36 tests, 0 failures

Randomized with seed 698014

No license indicated...

Maybe I missed it, but I couldn't find a reference to a license for this code. Is it meant to be in the public domain?

If you're looking for a license, I've created one, based on my particular preferences, which you're welcome to use... https://github.com/nirvana/TFL

Any performance data?

I can't find any benchmark result for performance,is there a report doc about this?
Thanks.

Putting into wrong cache name hangs

I started a cache with

supervisor(ConCache, [[], [name: :queue]])

And then, by mistake, called ConCache.put(:my_cache, :key, :value) (notice the wrong cache name). The call just hangs. Wouldn't it be better for it to throw an exception instead?

Elixir: 1.4.4
con_cache: 0.12.0

Any way to do key match on Cache?

I'm now trying to use Concache to replace redis in the project, it works pretty well. But one feature I'm missing here is redis.keys pattern.
Basically since all keys in redis is binary based, for erlang I'm planning to use a tuple key like
{name,age,index} = value and put into ConCache.

If I extend it using the ets match/select query I think this can be done for some pattern matches with the key, but I don't know the impact if will have to the performance, or if any drawbacks it might have in regards to TTL mechanism.

Any suggestions will be really helpful, thx for your efforts to create this project :)

Hit/miss ratio tracking

Hey, thanks for this library.

Would you be interested in an extension allowing querying each cache's hit rate? Happy to discuss implementation details if you're open to the idea.

Dirty Read

Hello, is there a way to call the callback function after delete operation is done instead of before?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.