Most Powerful Open Source ERP

How To Improve Performance

How To showing ways to improve performance of ERP5 applications.
  • Last Update:2016-02-09
  • Version:001
  • Language:en

The basic principle of optimization is not to optimize. This sounds contradictory, but this means that you should not try to optimize code until you know it is really necessary to optimize it, and how to optimize it. Until you realize optimization is required, you should respect readability and elegance.

Table of Contents

Profiling

The first thing you should do is to profile your code. Quite often, a bad performance is derived from one or a few points of your code, so-called bottlenecks. Even if you improve a piece of your code significantly, if that part is not a bottleneck, you see no performance gain as a whole. So you must devote yourself on figuring out bottlenecks and solving them.

The most useful tool in profiling Zope applications is ZopeProfiler. ZopeProfiler allows you to profile code both in the level of Zope and in the level of Python, and you can turn on and off profiling at run time.

Using ZopeProfiler is easy. Download the product, extract the tarball in your instance home, and restart the Zope server. Then, you will see the interface of ZopeProfiler in the Control Panel. When you enable the ZopeProfiler, it begins profiling every activity in the Zope server. When you disable it, it shows statistics during the profiling time. Since ZopeProfiler puts information on the memory, you should not enable it too long. Otherwise, Zope will eat all of your memory and crash.

To profile your code correctly, you should:

  1. stop all activities but what you want to check. For this reason, it is better to use your own Zope server.
  2. stop tic and distribute, if you use portal_activities, unless your intention is to check the performance in an usual environment.
  3. disable portal_activities (e.g. renaming it to something else), if you want to check activity processes as well.
  4. execute the same thing many times or use a task big enough. Because profiling is affected by random noises, an attempt does not produce the same result as another attempt. To reduce the effect of random factors, it is necessary to use long computations. In particular, caching affects the system very much.
  5. not assume that the result is the same as without ZopeProfiler. Because profiling adds a big overhead for tracing the code, this is not completely the same as a real run. Usually, profiling makes all operations very slow, but not equally slow. You should use the result as an estimate.

You may also find the profilehooks module usefull, to profile/time a single function.

Algorithms

Choosing good algorithms is the best. For example, if you can make O(n^2) to O(n), the performance gain would be extremely significant. Remind yourself of the theory of computational complexity. You should have learned it in your school... Otherwise, find a good web site or buy a good textbook.

Python

There are some tips on Python-specific optimizations. Look at Python Speed and Python Performance Tips.

Installing Psyco is very simple but very powerful. But, note that Psyco is available only on IA-32, and it cannot compile code if you use map or filter. This is a good reason why we should use list comprehension.

Z SQL Methods

Z SQL Methods are frequently bottlenecks. This is sometimes due to DTML expressions and sometimes due to MySQL. The principle is that fewer SQL queries are better, fewer DTML expressions are better.

Look at MySQL Optimization.

MySql Configuration

You must make sure that MySQL is well configured for your box. On some boxes, Innodb Commits are very slow, then you should try to set the parameter innodb_flush_method to O_DSYNC. If your cpu is not working so much while you are doing a lot of reindexing, this make reindexing is very slow, then may be the solution is this parameter.

Look at InnoDB Performance Tuning Tips.

Caching

ERP5 provides a generic way of caching methods. It is called CachingdMethod. If you must invoke a heavy method many times, caching may be useful for you. You must, however, take care of sub-effects of caching. Caching means that the user sees a bit older data for a while. This may lead to an inconsistent state, if you do not invalidate the cache appropriately.

Conflicts

You should avoid conflicts. Conflicts occur, when multiple transactions try to access the same object, and one or more of them tries to change the object. When conflicts happen, transactions are rolled back, and restarted from the beginning. This reduces the performance radically in a multi-user environment. (Where to look at more information? ZODB Development Wiki ?)

Conflict hunt

To list all exception raised by each attempt to render a page, it is necessary to modify Zope publisher. Add the following import and LOG lines in [path to your zope instane]/lib/python/ZPublisher/Publish.py in publish method in begining of the except block:

Toggle line numbers
   1     try:
   2         # (snip)
   3         return response
   4     except:
   5         import traceback
   6         import LOG
   7         LOG('ZPublisher_debug', 0, 'Publication of "%s" raised an exception: %s' % (path, repr(traceback.format_exc())))
Toggle line numbers try: # (snip) return response except: import traceback import LOG LOG('ZPublisher_debug', 0, 'Publication of "%s" raised an exception: %s' % (path, repr(traceback.format_exc())))

Once this code is added, you must reproduce the conflict error (ie. hammering the server) and log to see which exception is raised and its backtrace.

Once you have found the conflict you're searching for, its backtrace should give you hint about the object which caused the conflict. If its OID is given, you must then grab the object from its OID to get its type and guess its path (sadly, no way to reconstruct it directly from its OID, so guessing is needed). PDB helps much here:

Given that the OID you get is 0xBEEF and PDB is triggered on object 0x123456:

>>> p self._p_oid # Just to get the OID length
'\x00\x00\x00\x00\x00\x12\x34\x56'
>>> self._p_jar['\x00\x00\x00\x00\x00\x00\xBE\xEF']

Here, "FooClass" might help finding the culprit if you know where FooClass instance can reside in your Zope object tree. If it's not precise enough, then you can try to get its properties, and if it's a document type (Person, Organisation, ...) you can most probably read its UID which, after a catalog lookup, will lead you to its path.

When the object causing the conflict error is identified, you must get an idea of why it can become a bottleneck in this application, most likely it is it modified in a commonly executed code.

Then you must rethink the part of your design/setup which involves this object, and check if there is a way to avoid such massive concurrent use of the same object.

Addendum: You might reach at some point a yet-unsolved problem which causes approximately 1 conflict error every 3,000 requests with 60 simultaneous busy users. It seems to be caused by ID collisions in b-tree folders when modifying at the same time 2 distinct objects which end up in the same b-tree bucket. Some possibilities to escape this are:

  • Redesigning the way objects are stored in this b-tree to lower its load - but this can end up requiring a lot of time redesigning application configuration.
  • Change the way IDs are generated for objects in that b-tree so it generates fewer collisions, but that requires the knowledge of the hash function internally used in b-tree folder.

Activities

If there is no way to improve the performance only at the level of software, another way is to use many computers. Using portal_activities, you can distribute method calls very easily to a large number of computers.

Related Articles