56

I'm looking for simple but recommended way in Django to store a variable in memory only. When Apache restarts or the Django development server restarts, the variable is reset back to 0. More specifically, I want to count how many times a particular action takes place on each model instance (database record), but for performance reasons, I don't want to store these counts in the database. I don't care if the counts disappear after a server restart. But as long as the server is up, I want these counts to be consistent between the Django shell and the web interface, and I want to be able to return how many times the action has taken place on each model instance.

I don't want the variables to be associated with a user or session because I might want to return these counts without being logged in (and I want the counts to be consistent no matter what user is logged in). Am I describing a global variable? If so, how do I use one in Django? I've noticed the files like the urls.py, settings.py and models.py seems to be parsed only once per server startup (by contrast to the views.py which seems to be parsed eache time a request is made). Does this mean I should declare my variables in one of those files? Or should I store it in a model attribute somehow (as long as it sticks around for as long as the server is running)? This is probably an easy question, but I'm just not sure how it's done in Django.

Any comments or advice is much appreciated. Thanks, Joe

Joe J
  • 9,985
  • 16
  • 68
  • 100
  • 1
    The caching docs are a bit 'caching-a-page' centric. Jump to 'low-level' for details on using it for server variables: https://docs.djangoproject.com/en/1.3/topics/cache/#the-low-level-cache-api – John Mee Sep 21 '11 at 23:17
  • 1
    This might help you: [https://github.com/andres-torres-marroquin/django-system-globals](https://github.com/andres-torres-marroquin/django-system-globals) – Andrés Torres Marroquín Dec 23 '11 at 05:27

6 Answers6

76

Why one mustn't declare global variables? O_o. It just looks like a propaganda. If the author knows what he wants and what side-effects will be, why not. Maybe it's just a quick experiment.

You could declare your counter as a model class-member. Then to deal with race condition you have to add a method that will wait if some other client, from another thread works with counter. Something like this:

import threading

class MyModel(ModelBase):
    _counter = 0
    _counter_lock = threading.Lock()

    @classmethod
    def increment_counter(cls):
        with cls._counter_lock:
            cls._counter += 1

    def some_action(self):
        # core code
        self.increment_counter()


# somewhere else
print MyModel._counter

Remember however: you have to have your application in one process. So if you've deployed the application under Apache, be sure it is configured to spawn many threads, but not many processes. If you're experimenting with ./manage.py run no actions are required.

nkrkv
  • 7,030
  • 4
  • 28
  • 36
  • 1
    This is exactly what I want. I need to generate a serial id that would be incremented for each request for testing purpose. In actual system, the id is coming from external API. – k4ml Feb 22 '11 at 10:56
  • 2
    This might be a viable approach for toy projects. But once you start using this class you've guaranteed that your application is not scalable. There is a reason why we have the cache sub-system. – muhuk Jul 15 '11 at 05:34
  • 1
    i used this approach to instantiate once a client to a machine service, instantiate it every time slow down my webserver... – thrantir Sep 23 '11 at 12:57
  • 24
    Does EVERY application need to be scalable? – dariopy May 22 '12 at 21:55
  • 1
    Considering the GIL, is the threading part necessary? Are there django configurations where actual multi-threading is happening within a process and race conditions are a concern? – MrColes Aug 03 '15 at 16:08
  • @nailxx I've a question regarding global variables in Django views. Can please provide your views ? http://stackoverflow.com/questions/39490843/django-app-level-variables – Pankaj Singhal Sep 15 '16 at 16:34
24

You mustn't declare global variables. Settings (constants) are OK if done right. But variables violate with shared-nothing architecture and might cause a lot of trouble. (best case they'll be inconsistent)

I would simply store those statistics in the cache. (Well, actually I would store them in the database but you made clear you believe it will have a negative impact on performance, so...)

The new incr() and decr() methods are especially suitable for counting. See docs for more info.

muhuk
  • 15,777
  • 9
  • 59
  • 98
  • I like this approach, and am implementing it. Currently I am using localmem for the cache backend on the Django development server and have noticed that the counts don't persist after browsing through several web pages in my site. (In which time I did NOT restart the django development server) I realize that there could be multiple reasons for this. (Eventually I'll use memcached). Just wondering if the localmem cache / django development server doesn't hold onto cached values long or if I have a config setting wrong somehow. – Joe J Apr 22 '10 at 17:40
  • @Joe: I always use db cache in the absence of a proper cache backend such as memcached. Just to be sure. This goes together with "not worrying at all about performance problems until they are apparent" and "measure before optimize". – muhuk Apr 23 '10 at 07:56
  • @muhuk I've a question regarding global variables in Django views. Can please provide your views ? http://stackoverflow.com/questions/39490843/django-app-level-variables – Pankaj Singhal Sep 15 '16 at 16:34
  • @PankajSinghal I've read your question. Middleware seems like a good way to go. But I'm probably not the best person to answer, it's been a long time I've worked with Django. – muhuk Dec 05 '16 at 13:58
  • What about global object such as GeoIP database readers? Opening it in every request hurts a lot :) – artem Feb 21 '17 at 03:50
  • @arts777 that is a good use case IMO. Unfortunately variables are, well, mutable in Python. I would suggest providing such immutable values with a function and caching its result. That way you would prevent the database accidentally being overwritten. Of course you would probably want to make sure the value is somehow immutable as well. – muhuk Feb 21 '17 at 23:18
  • @muhuk can you please take a look at this question, related to global GeoIP bases? https://stackoverflow.com/questions/42358525/maxmind-geoip2-single-instance-in-django – artem Feb 22 '17 at 11:24
  • 1
    I did. It's been a while since I've written Python code professionally. Better to get feedback from more up-to-date people. – muhuk Feb 22 '17 at 11:56
3

I would create a "config.py" file on the root directory. and put all global variables inside:

x = 10
my_string = ''

at "view.py":

from your_project import config

def MyClass(reuqest):
    y = config.x + 20
    my_title = config.my_string
...

The benefit of creating this file is the variables can cross multiple .py files and it is easy to manage.

Ken
  • 1,234
  • 10
  • 16
0

If you want to avoid the hassle with Django database, e.g. migrations or performance issues, you might consider in-memory database redis. Redis guarantees consistency even if there are multiple Django processes.

Juuso Ohtonen
  • 8,826
  • 9
  • 65
  • 98
0

You can use variables from settings.py

see below example. It's an app that counts requests

settings.py

REQ_COUNTER = 0

View.py

from {**your project folder name **}.settings import REQ_COUNTER 

def CountReq(request):
 global = REQ_COUNTER
 REQ_COUNTER = REQ_COUNTER + 1
 return HttpResponse(REQ_COUNTER)

Thanks :)

Soulduck
  • 569
  • 1
  • 6
  • 17
0

multiprocessing.Manager may be used to share variables between all Django sub-processes started by a main process:

from multiprocessing import Manager
from rest_framework.decorators import api_view
from rest_framework.response import Response

total_count = Manager().Value('i', 0)

@api_view(['GET'])
def hello_count(request):
    total_count.value = total_count.value + 1
    return Response(data={'count': total_count.value})

total_count will be increased for all running processes (checked with multiple workers running by gunicorn app server).

However, if you will run python manage.py shell the total_count will be 0 for this process, because that is a separated master process with own memory space. If you want to get the same shared value for all processes inside server (or even multiple servers) you have to use in-memory DB (redis, etc), what is better and more stable practice for production environments.

rzlvmp
  • 7,512
  • 5
  • 16
  • 45