tortoise / tortoise/tortoise-orm

[advise] Global cache

Open
#332 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

discussion
Dominant language
Python
Stars
5.6k
Forks
516
Avg merge
2d 21h
Merged PRs (30d)
9

Description

I feel a bit uncertain posting an issue on something debatable, but I'm afraid I'm not sure how to reach to developers otherwise. Feel free to redirect this topic and close the issue if you feel it isn't appropriate.

I would like some feedback on feasibility and best practices regarding what I'm trying to do with tortoise-orm.

The problem

I would like to cache all database objects in the memory, not that queries become useless, but that they might hold identical results. In other words:

obj1 = await Model.get(id=1)
obj2 = await Model.get(id=2)
obj1 is obj2

... should be true. The first time an object appears, it should be added (with identifiers) in a cache. The next time the object is queried, it should return the older one to respect identity.

Things might be easier to understand with a more complete example. Consider these two models:

class Account(Model):
    id = fields.IntField(pk=True)
    username = fields.CharField(max_length=64, description="Account username")
    session: fields.ReverseRelation

class Session(Model):
    uuid = fields.UUIDField(pk=True)
    account = fields.OneToOneField("app.Account", related_name="session", on_delete=fields.SET_NULL, null=True)

If you initialize these objects, one could run the following code:

account1 = await Account.create(username="me")
session1 = await Session.create(account=account1)
# Test identity
account1.session is session1 # True
session1.account is account1 # True
One approach

My first attempt was to override the __new__ method of both models. This seems to work... to some extent: if the object exists in the cache, the older one is returned, if not a new object is created. Object identification is performed with the app name, the class name and the model primary key. I obtained something like this (work in progress, rather ugly, just to test things out):

CACHED = {}

class CachedModel:

    """Use as a mixin on model classes."""

    def __new__(cls, **kwargs):
        app = cls._meta.app
        pk_attr = cls._meta.pk_attr
        pk_value = kwargs.get(pk_attr)
        id_obj = None
        if pk_value:
            pk_field = cls._meta.fields_map[pk_attr]
            pk_value = pk_field.to_python_value(pk_value)
            obj_id = (app, cls.__name__, pk_value)
            obj = memory.get(obj_id)
            if obj:
                return obj

        obj = object.__new__(cls)
        if obj_id:
            memory[obj_id] = obj

        return obj

This kind of works, when getting an existing object in the database, though it requires _init_from_db to send keyword arguments to __new__. This could be a simple pull request. But it won't work if you call Create for instance and let the database find the available primary key, because then __new__ will be called and the primary key won't be known. One could cache such objects, watching __setattr__ for the moment when their primary key is created, but this might lead to a lot of errors and situations in which two objects, destined to represent the same thing, actually get instantiated twice.

Better approach?

What I've built is obviously quite buggy and might not be the proper approach to solving this situation. Any thought on how I could implement such a cache system for my application?

Thanks,

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the model construction path around new, _init_from_db, and primary-key assignment, then review how Create instantiates and updates models. Compare the proposed identity cache behavior with relation handling in the Account and Session example. Done would require an agreed feasibility and design direction, rather than the issue's current exploratory prototype.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
database
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.