[FIX] OPW 577963: ir_attachment: speed up ir.attachment search for large databases
authorOlivier Dony <odo@openerp.com>
Tue, 21 Aug 2012 14:39:26 +0000 (16:39 +0200)
committerXavier ALT <xal@openerp.com>
Tue, 21 Aug 2012 14:39:26 +0000 (16:39 +0200)
  Frequents calls to list.remove() were being a bottleneck for large
  document lists (100k+). Using a set make remove() calls much faster.

  Also turned a read() into a pure SQL command, as its high-level
  features were unnecessary (security checks performed before and
  after it anyways) and it was 50% slower than the direct SQL version.

  This commit has a corresponding addons patch in order to use a set
  instead of a list in the document module as well.

bzr revid: xal@openerp.com-20120821143926-ta75gd98qi30wbp9

bin/addons/base/ir/ir_attachment.py

index 2c3d4e6..82c0543 100644 (file)
@@ -65,10 +65,18 @@ class ir_attachment(osv.osv):
                 return 0
             return []
 
+        # Work with a set, as list.remove() is prohibitive for large lists of documents
+        # (takes 20+ seconds on a db with 100k docs during search_count()!)
+        orig_ids = ids
+        ids = set(ids)
+
         # For attachments, the permissions of the document they are attached to
         # apply, so we must remove attachments for which the user cannot access
         # the linked document.
-        targets = super(ir_attachment,self).read(cr, uid, ids, ['id', 'res_model', 'res_id'])
+        # Use pure SQL rather than read() as it is about 50% faster for large dbs (100k+ docs),
+        # and the permissions are checked in super() and below anyway.
+        cr.execute("""SELECT id, res_model, res_id FROM ir_attachment WHERE id = ANY(%s)""", (list(ids),))
+        targets = cr.dictfetchall()
         model_attachments = {}
         for target_dict in targets:
             if not (target_dict['res_id'] and target_dict['res_model']):
@@ -93,9 +101,10 @@ class ir_attachment(osv.osv):
             for res_id in disallowed_ids:
                 for attach_id in targets[res_id]:
                     ids.remove(attach_id)
-        if count:
-            return len(ids)
-        return ids
+
+        # sort result according to the original sort ordering
+        result = [id for id in orig_ids if id in ids]
+        return len(result) if count else list(result)
 
     def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
         self.check(cr, uid, ids, 'read', context=context)