180180""" )
181181
182182
183- _GET_BEST_NODE = sqltext ("""\
183+ # MySQL: log(0) returns NULL, and NULLs sort first with ASC — zero-load
184+ # nodes naturally win. Original query unchanged.
185+ _GET_BEST_NODE_MYSQL = sqltext ("""\
184186 select
185187 id, node
186188from
196198limit 1
197199""" )
198200
201+ # PostgreSQL: ln() is the natural log equivalent of MySQL's log(). NULLIF
202+ # converts zero to NULL to avoid InvalidArgumentForLogarithm. NULLS FIRST
203+ # replicates MySQL's default NULL-first ASC sort order, ensuring zero-load
204+ # nodes are always preferred.
205+ _GET_BEST_NODE_POSTGRES = sqltext ("""\
206+ select
207+ id, node
208+ from
209+ nodes
210+ where
211+ service = :service
212+ and available > 0
213+ and capacity > current_load
214+ and downed = 0
215+ and backoff = 0
216+ order by
217+ ln(NULLIF(current_load, 0)) / ln(capacity) NULLS FIRST
218+ limit 1
219+ """ )
220+
199221
200222_RELEASE_NODE_CAPACITY = sqltext ("""\
201223 update
@@ -616,11 +638,14 @@ def add_service(self, service_name, pattern, **kwds):
616638 pattern = pattern ,
617639 ** kwds ,
618640 )
619- res .close ()
620641 if self .db_mode == "postgresql" :
621- return res .fetchone ()[0 ]
642+ row = res .fetchone ()[0 ]
643+ res .close ()
644+ return row
622645 else :
623- return res .lastrowid
646+ lastrowid = res .lastrowid
647+ res .close ()
648+ return lastrowid
624649
625650 def add_node (self , node , capacity , ** kwds ):
626651 """Add definition for a new node."""
@@ -653,8 +678,10 @@ def add_node(self, node, capacity, **kwds):
653678 capacity = capacity ,
654679 available = available ,
655680 current_load = kwds .get ("current_load" , 0 ),
656- downed = kwds .get ("downed" , 0 ),
657- backoff = kwds .get ("backoff" , 0 ),
681+ # Cast to int: optparse action="store_true" produces Python bools,
682+ # which postgres rejects for INTEGER columns (MySQL coerces silently).
683+ downed = int (kwds .get ("downed" , 0 )),
684+ backoff = int (kwds .get ("backoff" , 0 )),
658685 )
659686 res .close ()
660687
@@ -676,6 +703,11 @@ def update_node(self, node, **kwds):
676703 query += """
677704 where service = :service and node = :node
678705 """
706+ # Cast boolean fields to int: Python bools are rejected by postgres
707+ # INTEGER columns. MySQL coerces silently; postgres does not.
708+ for field in ("downed" , "backoff" ):
709+ if field in values :
710+ values [field ] = int (values [field ])
679711 values ["service" ] = self ._get_service_id (SERVICE_NAME )
680712 values ["node" ] = node
681713 if kwds :
@@ -747,8 +779,16 @@ def get_best_node(self):
747779 # capacity. This loop allows a maximum of five retries before
748780 # bailing out.
749781 for _ in range (5 ):
782+ # Select the appropriate query variant — postgres requires
783+ # explicit NULL handling for log(0) and NULL sort order that
784+ # MySQL handles implicitly.
785+ best_node_query = (
786+ _GET_BEST_NODE_POSTGRES
787+ if self .db_mode == "postgresql"
788+ else _GET_BEST_NODE_MYSQL
789+ )
750790 res = self ._execute_sql (
751- _GET_BEST_NODE , service = self ._get_service_id (SERVICE_NAME )
791+ best_node_query , service = self ._get_service_id (SERVICE_NAME )
752792 )
753793 row = res .fetchone ()
754794 res .close ()
0 commit comments