During daily development of ODOO8 modules, one thing I have been noticing is the slowness when ODOO first boot up. We are observing up to minutes of a bootup time. This plainly is too slow. Watching debug prints indicates that ODOO scans through all modules listed on Python path and builds a map in memory before dev server starts responding to request. Intuitively I suspected such loading practice is causing an unnecessary delay. This aggravates further when ODOO server becomes completly unusable if any of the module chokes during this loading process. But this is too common a scenario during development. One consequence of this is that debugging becomes difficult. Often enough ODOO doesn't yield useful trace track to help determine the root cause of an error. Another problem of such approach is the availability of servic becoming binary — it either fully works or nothing runs. By analogy, the ODOO loading process is like compiling phase in running a C code. No executable is available until the compiler and linker are happy.

    Under this light, this article analyzes this booting process with a hope to understand why ODOO takes such an approach and how we could improve or circumvent during development in order to gain efficiency. Considering a developer lives with this bootup process as a daily need, it becomes interesting how we can reduce this bootup time.

    Quick glance

    Tracing through ODOO boot code, we have illustrated steps of ODOO's loading process in diagram below:

    ODOO8 loading sequence

    Why does ODOO call tools.config.parse_config twice? The function fullfills the same purpose by parsing the configuration file and builds the in memory tools.config global variable. This is redundant.

    Server selection

    The meat of this loading is to boot up a HTTP server. ODOO offers three types of servers:

    1. ThreadedServer
    2. PreforkServer
    3. GeventServer

    But which one to use? If configuration option config['workers'] is non zero, it will pick the PreforkServer; otherwise, it takes default ThreadedServer.

    if openerp.evented:
        server = GeventServer(openerp.service.wsgi_server.application)
    elif config['workers']:
        server = PreforkServer(openerp.service.wsgi_server.application)
        server = ThreadedServer(openerp.service.wsgi_server.application)

    ThreadedServer runs in a single thread, where PreforServer uses select to handle multiprocessing. This choice apparently makes the prefork server more appealing for production site. ODOO's own manual on deployment also confirms this:

    Odoo includes built-in HTTP servers, using either multithreading or multiprocessing. For production use, it is recommended to use the multiprocessing server as it increases stability, makes somewhat better use of computing resources and can be better monitored and resource-restricted. * Multiprocessing is enabled by configuring a non-zero number of worker processes, the number of workers should be based on the number of cores in the machine (possibly with some room for cron workers depending on how much cron work is predicted) * Worker limits can be configured based on the hardware configuration to avoid resources exhaustion

    Threaded server

    How good is ODOO's default server then? Diagram below illustrates the truth of an ODOO threaded server. Under layers of function call, ODOO's threaded server is simply a Python native HTTPServer instance. So the question boils down to how good Python's native http server is comparing to some well known names — nginx, Apache, lighthttpd, or even further, WSGI servers such as Guicorn and uwsgi.

    ODOO8 ThreadedServer

    Runtime call trace

    Diagram below illustrates a full call trace while ODOO8 boots up. This was generated by Pycallgraph.

    pycallgraph -v -d -s -i openerp.\*
    -e openerp.report.\*
    -e openerp.tools.config.configmanager.\*
    -e openerp.loglevels.\*
    -e openerp.tools.\*
    --max-depth=10 graphviz
    -- odoo.py -c ../works/contract/8290.conf

    The decision to exclude these patters, in particular, the openerp.report.* is purely out of layout consideration — it clutters the diagram too much. This, on the other hand, could be considered as a clue why bootup is slow.

    Note that no tool can do static analysis of code and generate a call stack. The only sure way to create this is to run the code and monitor its runtime stack, in which case language such as Python shines due its interpreting nature. The Python GIL will keep track the entire call trace in its memory and knows details of call stack.

    ODOO 8 loading call trace diagram


    After examining ODOO code and its run time, two areas I would recommend ODOO to reconsider its approach:

    1. how configuration options are being shared among modules
    2. using ThreadedServer as default

    Configuration options

    First concern coming out of this analysis is how ODOO handles values in the configuration file. In openerp.tools ODOO defined a variable config.

    import openerp.tools.config

    thus will expose the config to the caller. This essentially makes the configuration object globally. I don't know whether I should call this a trick or a bad practice. So in short, ODOO's configuration is global.

    The proper way to handle this is to either set it up as global, or making caller to parse the disk conf file if it needs information. The former will build an in memory image. As long as it is in read-only mode, no locking mechanism is needed. The downside is that disck changes won't necessarily update the in memory object. The latter is better, though more expansive since disk file is accessed each time.


    My suspecion is that native server is suitable for development environment, but lacks features to be production ready. I'm considering features such as plugin loading (think Apache2), configuration, security, HTTP protocol support, SSL...?

    In short, ODOO's ThreadedServer works fine as development tool, but should not be used for production deployment unless, well, the client is the least sophisticated (oh boy there are so many of them that it is actually creating a whole nine yard of different problems than this one) and creates minimal load in reality.

    — by Feng Xia