{"id":16624364,"url":"https://github.com/dcramer/django-orm-cache","last_synced_at":"2025-10-11T12:09:07.282Z","repository":{"id":66498054,"uuid":"303369","full_name":"dcramer/django-orm-cache","owner":"dcramer","description":"A caching layer for Django","archived":false,"fork":false,"pushed_at":"2011-06-04T07:48:51.000Z","size":124,"stargazers_count":87,"open_issues_count":2,"forks_count":10,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-09-27T05:50:53.226Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dcramer.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2009-09-10T18:44:17.000Z","updated_at":"2024-11-28T16:27:44.000Z","dependencies_parsed_at":"2023-02-20T05:15:28.957Z","dependency_job_id":null,"html_url":"https://github.com/dcramer/django-orm-cache","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dcramer/django-orm-cache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcramer%2Fdjango-orm-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcramer%2Fdjango-orm-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcramer%2Fdjango-orm-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcramer%2Fdjango-orm-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dcramer","download_url":"https://codeload.github.com/dcramer/django-orm-cache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcramer%2Fdjango-orm-cache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279007135,"owners_count":26084246,"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-10-11T02:00:06.511Z","response_time":55,"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":[],"created_at":"2024-10-12T03:46:08.933Z","updated_at":"2025-10-11T12:09:07.258Z","avatar_url":"https://github.com/dcramer.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"Django ORM Cache\n================\n\nThis project is an attempt to provide a low-level, somewhat magical, approach to handling row-level object caches.\n\nThe project page, including all descriptions and reference material, is still very much a work in progress. If you are interested in contributing to the projects, in code, in ideas, or fixing our ever so awesome project outline, please get in touch with one of the project admins (see below).\n\nSummary\n-------\n\nIn brief, it's goal is to store only unique objects in a cache key, instead of storing groups of objects in many different locations. This would make updates to a cache object as simple as updating a single cache instance, and also automatically handle updating any lists that hold that object when it is removed.\n\nApproach\n--------\n\nThe approach consists of several major pieces of functionality:\n\n* Unique row-level object cache keys.\n* Lists of cache keys (e.g. a QuerySet).\n* Model-based dependency via signals.\n* Row-based dependency via reverse mapping.\n\nThe caching layer will also provide a few additional items:\n\n* Handling cache stampedes via pre-expiration timestamps.\n* Namespace versioning and expirations.\n\nRow-level Caches\n----------------\n\nThe primary goal of the project was originally to handle invalidation of many instances of a single object. Typical caching setups would have you create copies of a single object, and place it in many cache keys. The goal of this project is to replace those many copies with a single unique instance, thus making updates and invalidation a much simpler task.\n\nCachedModel\n-----------\n\nThe row-level cache is primarily managed by the ``CachedModel`` class. This class override several key methods on the normal Model class:\n\n* save() -- upon saving it automatically updates the cache instance.\n* delete() -- upon delete (optional maybe? useless in memcache) it removes the cache instance.\n* objects -- a CacheManager instance.\n* nocache -- the default manager instance.\n\nUsage::\n\n\tfrom ormcache.models import CachedModel\n\tclass Article(CachedModel):\n\t    ...\n\nQuerySet Caches\n---------------\n\nOne key problem with having a unique instance of a cache is managing pointers to that instance, as well as the efficiency of those pointers. This approach will simply store a set of pointers (primary keys) to which the backend would automatically query for and invalidate as needed.\n\nCacheManager\n------------\n\nThe QuerySet caching consists of one key component, the CacheManager. It's responsibility is to handle the unique storage and retrieval of QuerySet caches. It is also in charge of informing the parent class (CachedModel) of any invalidation warnings.\n\nThe code itself should work exactly like the default Manager and QuerySet with a few additional methods:\n\n* clean() -- removes the queryset; executes but does not perform any SQL or cache.get methods).\n* reset() -- resets the queryset; executes the sql and updates the cache.\n* execute() -- forces execution of the current query; no return value.\n* cache(key=\u003cstring\u003e, timeout=\u003cminutes\u003e) -- changes several options of the current cache instance.\n\nOne thing to note is that we may possibly be able to get rid of clean, or merge it with reset. The execute() method exists because reset does not force execution of the queryset (maybe this should be changed?).\n\nFetching Sets\n-------------\n\nThe biggest use of the CacheManager will come in the former of sets. A set is simply a list of cache key pointers. For efficieny this will be stored in a custom format for Django models::\n\n\t(ModelClass, (*pks), (*select_related fields), key length)\n\nModelClass\n##########\n\nThe first item in our cache key is the ModelClass in which the list is pointing to. We may need a way to handle unions but that's up to further discussion. The ModelClass is needed to reference where the pks go.\n\nPointers\n########\nThe second item, is our list of pointers, or primary keys in this use-case. This could be anything from a list of your standard id integers, to a group of long strings.\n\nRelations\n#########\n\nThe third item, our select_related fields. These are needed to ensure that we can follow the depth when querying.\n\ne.g.\nWe do MyModel.objects.all().select_related('user'). We then fetch the key which says its these 10 pks, from MyModel. We now need to also know that user was involved so we can fetch that in batch (vs it automatically doing 1 cache call per key).\n\nSo the route would be:\n\n* Pull MyModel's cache key.\n* Batch pull all pks referenced from queryset.\n* Group all fields by their ModelClass, and then either:\n  1. Do a cachedqueryset call on them (which would be looking up another list of values, possibly reusable, bad idea?)\n  2. Pull (as much at a time) the caches in bulk.\n\nUpon failure of any cache pull it would fall back to the ORM, request all the rows it can (for a single model, in a single query), and then set those in the cache. If any one of those rows was not found in the database, it would throw up an Invalidation Warning, which, by default, would expire that parent object and repoll the database for it's dataset.\n\nKey Length\n##########\n\nThe key length is something which was brought up recently. Memcached has limits to how much you can store in a single key. We can work around those. The key length would simply tell the CacheManager how many keys are used for that single list.\n\nModel Dependencies\n------------------\n\nModel dependencies would be handling via registration in a way such as how signals are handled.\n\nObject Dependencies\n-------------------\n\nObject dependencies will be handled by storing a reverse mapping to keys. These dependencies would simply be attached (in a separate key) to the original object in which they are dependent on.\n\nReferences\n----------\n\n* [http://www.davidcramer.net/code/61/handling-cache-invalidation.html Handling Cache Invalidation] by David Cramer","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcramer%2Fdjango-orm-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdcramer%2Fdjango-orm-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcramer%2Fdjango-orm-cache/lists"}