branch: main
commit e611adcca75f483eab08d4f94c4c985d23ff1696
Author: Ludovic Courtès <l...@gnu.org>
AuthorDate: Sat Oct 5 17:46:59 2024 +0200

    database: Optimize ‘db-get-builds-{min,max}’.
    
    This query would be very slow:
    
      LOG:  duration: 10805.150 ms  plan:
              Query Text: SELECT stoptime, id FROM Builds
              WHERE evaluation =  $1  AND
              (( $2  = 'pending' AND Builds.status < 0) OR
               ( $3  = 'succeeded' AND Builds.status = 0) OR
               ( $4  = 'failed' AND Builds.status > 0) OR
               ( $5  = 'newly-failed' AND Builds.status =  $6
                AND Builds.weather =  $7 ) OR
                 $8 ::text IS NULL)
              ORDER BY stoptime DESC, id DESC
              LIMIT 1
              Limit  (cost=0.43..213.80 rows=1 width=8)
                ->  Index Scan using builds_stoptime_id on builds  
(cost=0.43..283141.54 rows=1327 width=8)
                      Filter: (evaluation = 242640)
    
    Its plain direct counterpart appears to be more efficient:
    
      cuirass=> explain analyze select stoptime,id from builds where evaluation 
= 242640 and builds.status = 0 order by stoptime desc, id desc limit 1;
                                                                        QUERY 
PLAN
    
      
-----------------------------------------------------------------------------------------------------------------------
      -----------------------
       Limit  (cost=0.43..464.06 rows=1 width=8) (actual 
time=6197.445..6197.447 rows=1 loops=1)
         ->  Index Scan using builds_stoptime_id on builds  
(cost=0.43..295794.55 rows=638 width=8) (actual time=6197.443..61
      97.443 rows=1 loops=1)
               Filter: ((evaluation = 242640) AND (status = 0))
               Rows Removed by Filter: 4597414
       Planning Time: 0.240 ms
       Execution Time: 6197.527 ms
      (6 rows)
    
      Time: 6198.353 ms (00:06.198)
    
    * src/cuirass/database.scm (db-get-builds-boundary): New procedure,
    based on ‘db-get-builds-min’.
    (db-get-builds-min, db-get-builds-max): Rewrite in terms of
    ‘db-get-builds-boundary’.
---
 src/cuirass/database.scm | 69 ++++++++++++++++++++++++++----------------------
 1 file changed, 37 insertions(+), 32 deletions(-)

diff --git a/src/cuirass/database.scm b/src/cuirass/database.scm
index bc9046c..3ae0a42 100644
--- a/src/cuirass/database.scm
+++ b/src/cuirass/database.scm
@@ -2130,43 +2130,48 @@ AND (Builds.system = :system OR :system IS NULL);")
         ((max) (and max
                     (list (string->number max))))))))
 
-(define (db-get-builds-min eval status)
-  "Return the min build (stoptime, rowid) pair for the given evaluation EVAL
-and STATUS."
-  (with-db-connection db
-    (match (expect-one-row
-            (exec-query/bind db "SELECT stoptime, id FROM Builds
-WHERE evaluation = " eval " AND
-((" status " = 'pending' AND Builds.status < 0) OR
- (" status " = 'succeeded' AND Builds.status = 0) OR
- (" status " = 'failed' AND Builds.status > 0) OR
- (" status " = 'newly-failed' AND Builds.status = " (build-status failed) "
-  AND Builds.weather = " (build-weather new-failure) ") OR
-  " status "::text IS NULL)
-ORDER BY stoptime ASC, id ASC
-LIMIT 1"))
-      ((stoptime id) (list (string->number stoptime)
-                           (string->number id)))
-      (else #f))))
-
-(define (db-get-builds-max eval status)
+(define (db-get-builds-boundary eval status kind)
   "Return the max build (stoptime, rowid) pair for the given evaluation EVAL
-and STATUS."
+and STATUS, a string such as \"pending\"."
+  (define (query . selector)
+    ;; Return the query for SELECTOR as a string.  This is more efficient than
+    ;; making STATUS a query parameter and adding conditionals on its value in
+    ;; 'WHERE'.
+    (let ((order (match kind
+                   ('min "ASC")
+                   ('max "DESC"))))
+      (string-append "SELECT stoptime, id FROM Builds WHERE evaluation = $1"
+                     " AND "
+                     (string-concatenate
+                      (map (match-lambda
+                             ((? string? str) str)
+                             ((? number? n) (number->string n)))
+                           selector))
+                     " ORDER BY stoptime " order
+                     ", id " order " LIMIT 1;")))
+
   (with-db-connection db
     (match (expect-one-row
-            (exec-query/bind db "SELECT stoptime, id FROM Builds
-WHERE evaluation = " eval " AND
-((" status " = 'pending' AND Builds.status < 0) OR
- (" status " = 'succeeded' AND Builds.status = 0) OR
- (" status " = 'failed' AND Builds.status > 0) OR
- (" status " = 'newly-failed' AND Builds.status = " (build-status failed) "
-  AND Builds.weather = " (build-weather new-failure) ") OR
-  " status "::text IS NULL)
-ORDER BY stoptime DESC, id DESC
-LIMIT 1"))
+            (exec-query db
+                        (match status
+                          ("pending" (query "status < 0"))
+                          ("succeeded" (query "status = 0"))
+                          ("failed" (query "status > 0"))
+                          ("newly-failed"
+                           (query "status = " (build-status failed)
+                                  " AND weather = "
+                                  (build-weather new-failure)))
+                          (_ (query "TRUE")))
+                        (list (number->string eval))))
       ((stoptime id) (list (string->number stoptime)
                            (string->number id)))
-      (else #f))))
+      (_ #f))))
+
+(define db-get-builds-min
+  (cut db-get-builds-boundary <> <> 'min))
+
+(define db-get-builds-max
+  (cut db-get-builds-boundary <> <> 'max))
 
 (define (db-get-evaluation-specification eval)
   "Return specification of evaluation with id EVAL."

Reply via email to