{"id":16183702,"url":"https://github.com/chanzuckerberg/redis-memo","last_synced_at":"2025-07-29T01:32:20.645Z","repository":{"id":41521210,"uuid":"331758892","full_name":"chanzuckerberg/redis-memo","owner":"chanzuckerberg","description":"A Redis-based version addressable caching system. Memoize pure functions, aggregated database queries, and 3rd party API calls.","archived":false,"fork":false,"pushed_at":"2022-08-11T16:40:21.000Z","size":320,"stargazers_count":31,"open_issues_count":2,"forks_count":3,"subscribers_count":39,"default_branch":"main","last_synced_at":"2025-06-03T04:02:55.833Z","etag":null,"topics":["activerecord","cache","caching","memoization","performance","rails","redis","reliability","ruby","sql"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/redis-memo","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chanzuckerberg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null}},"created_at":"2021-01-21T21:24:54.000Z","updated_at":"2025-04-23T13:12:01.000Z","dependencies_parsed_at":"2022-08-11T00:51:12.526Z","dependency_job_id":null,"html_url":"https://github.com/chanzuckerberg/redis-memo","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/chanzuckerberg/redis-memo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chanzuckerberg%2Fredis-memo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chanzuckerberg%2Fredis-memo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chanzuckerberg%2Fredis-memo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chanzuckerberg%2Fredis-memo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chanzuckerberg","download_url":"https://codeload.github.com/chanzuckerberg/redis-memo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chanzuckerberg%2Fredis-memo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267516490,"owners_count":24100333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-28T02:00:09.689Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","cache","caching","memoization","performance","rails","redis","reliability","ruby","sql"],"created_at":"2024-10-10T07:06:04.605Z","updated_at":"2025-07-29T01:32:20.300Z","avatar_url":"https://github.com/chanzuckerberg.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg align=\"left\" src=\"https://github.com/chanzuckerberg/redis-memo/blob/main/docs/images/icon.png?raw=true\" alt=\"drawing\" width=\"120\"/\u003e\n\n# RedisMemo\n\n[![Gem Version](https://badge.fury.io/rb/redis-memo.svg)](https://badge.fury.io/rb/redis-memo)\n[![GitHub Actions CI](https://github.com/chanzuckerberg/redis-memo/actions/workflows/ci.yml/badge.svg)](https://github.com/chanzuckerberg/redis-memo/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/chanzuckerberg/redis-memo/branch/main/graph/badge.svg?token=XG83PSWPG0)](https://codecov.io/gh/chanzuckerberg/redis-memo)\n\n\n## Add to your Gemfile\n```ruby\n# -- Gemfile --\ngem 'redis-memo'\n```\n:warning: **Required Redis Version:** \u003e= 6.0.0 ([reference](https://github.com/chanzuckerberg/redis-memo/blob/91ec911766ad072b1e003f695c35594bb31f0e67/lib/redis_memo/memoizable/bump_version.lua#L14-L16))\n## Usage\n### Cache simple ActiveRecord queries\nIn the `User` model:\n```ruby\nclass User \u003c ApplicationRecord\n  extend RedisMemo::MemoizeQuery\n  memoize_table_column :id\nend\n```\n\n`SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" = $1` queries will **load the data from Redis** instead of the database:\n```\n[1] (rails console)\u003e Post.last.author\n  Post Load (0.5ms)  SELECT \"posts\".* FROM \"posts\" ORDER BY \"posts\".\"id\" DESC LIMIT $1\n[Redis] User Load SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" = $1 LIMIT $2\n[Redis] command=MGET args=\"RedisMemo::Memoizable:wBHc40/aONKsqhl6C51RyF2RhRM=\" \"RedisMemo::Memoizable:fMu973somRtsGSPlWfQjq0F8yh0=\" \"RedisMemo::Memoizable:xjlaWFZ6PPfdd8hCQ2OjJi6i0hw=\"\n[Redis] call_time=0.54 ms\n[Redis] command=MGET args=\"RedisMemo:SELECT \\\"users\\\".* FROM \\\"users\\\" WHERE \\\"users\\\".\\\"id\\\" = ? LIMIT ?::P+HaeUnujDi9eH7jZfkTzWuv6CA=\"\n[Redis] call_time=0.44 ms\n=\u003e #\u003cUser id: 1\u003e\n```\nLearn more [here](https://github.com/chanzuckerberg/redis-memo/wiki/Auto-Invalidation-with-ActiveRecord).\n\n### Cache aggregated ActiveRecord queries\nSome computation might depend on multiple database records, for example:\n```ruby\nclass Post \u003c ApplicationRecord\n  extend RedisMemo::MemoizeMethod\n  def display_title\n    \"#{title} by #{author.display_name}\"\n  end\n  memoize_method :display_title do |post|\n    depends_on Post.where(id: post.id)\n    depends_on User.where(id: post.author_id)\n  end\nend\n```\n* Note that calling `Post.where(id: post.id)` does not trigger any database queries -- it's just an [ActiveRecord Relation](https://api.rubyonrails.org/v6.1.3.1/classes/ActiveRecord/Relation.html) representing the SQL query.\n\nIn order to use `depends_on` to extract dependencies from a Relation, we need to memoize the referenced table columns on the `Post` and `User` model:\n```ruby\nclass Post \u003c ApplicationRecord\n  extend RedisMemo::MemoizeQuery\n  memoize_table_column :id\nend\n```\n\nIt's also possible to pull in existing dependencies on other memoized methods and perform [hierarchical caching](https://github.com/chanzuckerberg/redis-memo/wiki/Hierarchical-Caching).\n\n### Cache Pure Functions\nWhen a method does not have any dependencies other than its arguments, it is considered a pure function. Pure functions can be cached on Redis as follow:\n\n```ruby\nclass FibonacciSequence\n  extend RedisMemo::MemoizeMethod\n\n  def [](i); i \u003c= 2 ? 1 : self[i - 1] + self[i - 2]; end\n  memoize_method :[]\nend\n```\n\nThe method arguments are used as part of the cache key to store the actual computation result on Redis.\n\n### Cache Third-Party API calls (or any external dependencies)\nWhen a method’s result can not only be derived from its arguments, set dependencies explicitly as follow:\n*   Call  `invalidate` in `after_save`\n*   Set dependencies in `memoize_method`\n\n```ruby\nclass Document\n  extend RedisMemo::MemoizeMethod\n\n  def memoizable\n    @memoizable ||= RedisMemo::Memoizable.new(document_id: id)\n  end\n\n  def after_save\n     RedisMemo::Memoizable.invalidate([memoizable])\n  end\n\n  # Make an API request to load the document, for example, from AWS S3\n  def load; end\n\n  memoize_method :load do |doc|\n    depends_on doc.memoizable\n  end\nend\n```\nFor each `load` call, the cached result on Redis will be used until its dependencies have been invalidated.\n\n## Configure RedisMemo\nYou can configure various RedisMemo options in your initializer `config/initializers/redis_memo.rb`:\n```ruby\nRedisMemo.configure do |config|\n  config.expires_in = 3.hours\n  config.global_cache_key_version = SecureRandom.uuid\n  ...\nend\n```\nLearn more [here](https://github.com/chanzuckerberg/redis-memo/wiki/Configure-RedisMemo).\n\n## Why RedisMemo?\n\n1. [Database caching](https://github.com/chanzuckerberg/redis-memo/wiki/Database-caching): Quick review of why caching is important\n2. Challenges with application-level caching\n    2.1 [Forgetting to invalidate the cache](https://github.com/chanzuckerberg/redis-memo/wiki/Auto-Invalidation-with-ActiveRecord#forgetting-to-invalidate-the-cache)\n    2.2 [Cache invalidation during database transactions](https://github.com/chanzuckerberg/redis-memo/wiki/Cache-invalidation-during-database-transactions)\n    2.3 [Cache invalidation could be slow and expensive](https://github.com/chanzuckerberg/redis-memo/wiki/Cache-invalidation-could-be-slow-and-expensive-with-Rails-low-level-caching)\n    2.4 [Possible race conditions](https://github.com/chanzuckerberg/redis-memo/wiki/Possible-race-conditions-with-Rails-low-level-caching)\n    2.5 [Cache inconsistency during deployments](https://github.com/chanzuckerberg/redis-memo/wiki/Ensure-consistency-during-deployments)\n\n3. How caching is easily done with RedisMemo\n    3.1 [Performant and reliable cache invalidation](https://github.com/chanzuckerberg/redis-memo/wiki/Cache-invalidation-could-be-slow-and-expensive-with-Rails-low-level-caching#with-redismemo)\n    3.2 [Auto-invalidation](https://github.com/chanzuckerberg/redis-memo/wiki/Auto-Invalidation-with-ActiveRecord)\n    3.3 [Add caching without changing any call sites](https://github.com/chanzuckerberg/redis-memo/wiki/Add-caching-without-changing-any-call-sites)\n    3.4 Add caching confidently\n        \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.4.1 [Avoid mistakes by pulling in existing dependencies](https://github.com/chanzuckerberg/redis-memo/wiki/Hierarchical-Caching#reuse-dependency)\n        \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.4.2 [Monitoring](https://github.com/chanzuckerberg/redis-memo/wiki/Monitoring)\n        \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.4.3 [Safely roll out changes](https://github.com/chanzuckerberg/redis-memo/wiki/Configure-RedisMemo#cache-sample-validation)\n\n## Related Work\nWe’re aware of [Shopify/identity_cache](https://github.com/Shopify/identity_cache), a gem that provides query caching with automatic cache invalidation; however, it is affected by most of the other issues we want to address when caching queries at the application-level. You can learn more about the challenges with using the Rails low-level caching API or other caching technologies such as IdentityCache [here](https://github.com/chanzuckerberg/redis-memo/wiki).\n\nIdentityCache is [deliberately opt-in](https://github.com/Shopify/identity_cache#caveats) for all call sites that want to use caching. In comparison, RedisMemo is still deliberate in that clients should specify what computation and models should be cached. However, when caching does make sense, RedisMemo makes caching easy and robust by automatically using the cached code paths.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchanzuckerberg%2Fredis-memo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchanzuckerberg%2Fredis-memo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchanzuckerberg%2Fredis-memo/lists"}