10 | | i'll add this example later! |
| 11 | class Operand(models.Model): |
| 12 | value = models.IntField() |
| 13 | |
| 14 | class Operation(models.Model): |
| 15 | arg = models.ForeignKey(Operand) |
| 16 | |
| 17 | for operation in Operation.objects.all(): |
| 18 | operation.arg.value += 1 |
| 19 | operation.arg.save() |
| 20 | |
| 21 | # so far so good, but I want to make it tighter ! Let's use select_related to save a DB query per iteration |
| 22 | for operation in Operation.objects.all().select_related(): |
| 23 | operation.arg.value += 1 |
| 24 | operation.arg.save() |
12 | | * The original goal of #17 was reducing memory use and query count by reusing the same instance instead of having a new one. Let's see a typical example of that: |
| 26 | Here we have a problem if two operations point to the same operand. The first version works well but the second preloads all operands from the DB and the same DB operand will result in multiple operand instances in memory. So we're basically modifying and saving the original operand every time instead of cumulating the changes. Here's a band-aid for your foot. |
| 27 | |
| 28 | Models with self FKs will exhibit exactly the same issues: |
| 29 | {{{ |
| 30 | |
| 31 | class Directory(models.Model): |
| 32 | name = models.CharField() |
| 33 | parent = models.ForeignKey('self') |
| 34 | |
| 35 | for dir in Directory.objects.all(): |
| 36 | if condition(dir): |
| 37 | dir.modify() |
| 38 | dir.parent.modify() |
| 39 | dir.parent.save() |
| 40 | }}} |
| 41 | Now let's see, what if condition returns True for a directory and its parent ? If the parent comes first in the main query, it will be modified, saved, reloaded when its child comes up later. If the child comes up first the parent is loaded, modified, saved, and then later on the original value from the outer query will be modified and saved, thus erasing the first change. |
| 42 | |
| 43 | === Performance === |
| 44 | The original goal of #17 was reducing memory use and query count by reusing the same instance instead of having a new one. Let's see a typical example of that: |
35 | | The basic idea of #17 is to simply reuse existing instances. This works by keeping track of instantiated objects on a per model basis. Whenever we try to instantiate a model, we check if there is already an instance in memory and reuse it if so. This would solve both issues mentioned above. Please note that the proposal is absolutely NOT a caching system. Whenever an instance goes out of scope it is still discarded. Also, it would most likely be turned off by default, and only work for Models which have this feature explicitly enabled. |
| 68 | === Overview === |
| 69 | The basic idea is to simply reuse existing instances. This works by keeping track of instantiated objects on a per model basis. Whenever we try to instantiate a model, we check if there is already an instance in memory and reuse it if so. This would solve both issues mentioned above. |
| 70 | |
| 71 | Please note that the proposal is absolutely NOT a caching system: whenever an instance goes out of scope it is discarded and will have to be reloaded from the DB next time. |
| 72 | |
| 73 | This feature would be turned off by default, and only operate for Models which have this feature explicitly enabled. Good candidates for this feature are Models like the articletype above, or models with self references. |
53 | | * the Model dict should be set threadlocally |
54 | | |
55 | | |
56 | | |
57 | | |
| 104 | * the Model dict should be set threadlocally. |
| 105 | * it should be possible to force instantiation, because the serializers want that (solved by next item). |
| 106 | * the internals of the patch should be changed to be less magic: overriding __call__ is a neat trick but I'd rather have something like Model.get_singleton(pk, kwargs = None) and use this where needed (the places are few!). If the user wants to do Model(id = something, kwargs) he should get a fresh instance. If he wanted to have the DB one he'd have used get or something like that. |
| 107 | * For the sake of completeness, it should be possible to do Model.objects.get_fresh_instance(id=something) to bypass the singleton |