1 /* $NetBSD: tier_bestof.c,v 1.2 2025/09/05 21:16:24 christos Exp $ */ 2 3 /* $OpenLDAP$ */ 4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 5 * 6 * Copyright 1998-2024 The OpenLDAP Foundation. 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted only as authorized by the OpenLDAP 11 * Public License. 12 * 13 * A copy of this license is available in the file LICENSE in the 14 * top-level directory of the distribution or, alternatively, at 15 * <http://www.OpenLDAP.org/license.html>. 16 */ 17 18 #include <sys/cdefs.h> 19 __RCSID("$NetBSD: tier_bestof.c,v 1.2 2025/09/05 21:16:24 christos Exp $"); 20 21 #include "portable.h" 22 23 #include <ac/string.h> 24 #include <math.h> 25 26 #include "lload.h" 27 #include "lutil.h" 28 29 static LloadTierInit bestof_init; 30 static LloadTierBackendConfigCb bestof_backend_options; 31 static LloadTierBackendCb bestof_add_backend; 32 static LloadTierBackendCb bestof_remove_backend; 33 static LloadTierSelect bestof_select; 34 35 struct lload_tier_type bestof_tier; 36 37 /* 38 * xorshift - we don't need high quality randomness, and we don't want to 39 * interfere with anyone else's use of srand() but we still want something with 40 * little bias. 41 * 42 * The PRNG here cycles thru 2^641 numbers. 43 */ 44 static uint64_t bestof_seed; 45 46 static void 47 bestof_srand( int seed ) 48 { 49 bestof_seed = seed; 50 } 51 52 static uint64_t 53 bestof_rand() 54 { 55 uint64_t val = bestof_seed; 56 val ^= val << 13; 57 val ^= val >> 7; 58 val ^= val << 17; 59 bestof_seed = val; 60 return val; 61 } 62 63 static int 64 bestof_cmp( const void *left, const void *right ) 65 { 66 const LloadBackend *l = left; 67 const LloadBackend *r = right; 68 struct timeval now; 69 uintptr_t count, diff; 70 float a = l->b_fitness, b = r->b_fitness, factor = 1; 71 72 gettimeofday( &now, NULL ); 73 /* We assume this is less than a second after the last update */ 74 factor = 1 / ( pow( ( 1 / factor ) + 1, now.tv_usec / 1000000.0 ) - 1 ); 75 76 count = __atomic_load_n( &l->b_operation_count, __ATOMIC_RELAXED ); 77 diff = __atomic_load_n( &l->b_operation_time, __ATOMIC_RELAXED ); 78 if ( count ) { 79 a = ( a * factor + (float)diff * l->b_weight / count ) / ( factor + 1 ); 80 } 81 82 count = __atomic_load_n( &r->b_operation_count, __ATOMIC_RELAXED ); 83 diff = __atomic_load_n( &r->b_operation_time, __ATOMIC_RELAXED ); 84 if ( count ) { 85 b = ( b * factor + (float)diff * r->b_weight / count ) / ( factor + 1 ); 86 } 87 88 return (a - b < 0) ? -1 : (a - b == 0) ? 0 : 1; 89 } 90 91 LloadTier * 92 bestof_init( void ) 93 { 94 LloadTier *tier; 95 int seed; 96 97 tier = ch_calloc( 1, sizeof(LloadTier) ); 98 99 tier->t_type = bestof_tier; 100 ldap_pvt_thread_mutex_init( &tier->t_mutex ); 101 LDAP_CIRCLEQ_INIT( &tier->t_backends ); 102 103 /* Make sure we don't pass 0 as a seed */ 104 do { 105 seed = rand(); 106 } while ( !seed ); 107 bestof_srand( seed ); 108 109 return tier; 110 } 111 112 int 113 bestof_add_backend( LloadTier *tier, LloadBackend *b ) 114 { 115 assert( b->b_tier == tier ); 116 117 LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, b, b_next ); 118 if ( !tier->t_private ) { 119 tier->t_private = b; 120 } 121 tier->t_nbackends++; 122 return LDAP_SUCCESS; 123 } 124 125 static int 126 bestof_remove_backend( LloadTier *tier, LloadBackend *b ) 127 { 128 LloadBackend *next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next ); 129 130 assert_locked( &tier->t_mutex ); 131 assert_locked( &b->b_mutex ); 132 133 assert( b->b_tier == tier ); 134 assert( tier->t_private ); 135 136 LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next ); 137 LDAP_CIRCLEQ_ENTRY_INIT( b, b_next ); 138 139 if ( b == next ) { 140 tier->t_private = NULL; 141 } else { 142 tier->t_private = next; 143 } 144 tier->t_nbackends--; 145 146 return LDAP_SUCCESS; 147 } 148 149 static int 150 bestof_backend_options( LloadTier *tier, LloadBackend *b, char *arg ) 151 { 152 struct berval weight = BER_BVC("weight="); 153 unsigned long l; 154 155 if ( !strncasecmp( arg, weight.bv_val, weight.bv_len ) ) { 156 if ( lutil_atoulx( &l, &arg[weight.bv_len], 0 ) != 0 ) { 157 Debug( LDAP_DEBUG_ANY, "bestof_backend_options: " 158 "cannot parse %s as weight\n", 159 arg ); 160 return 1; 161 } 162 b->b_weight = l; 163 return 0; 164 } 165 166 return 1; 167 } 168 169 static int 170 bestof_update( LloadTier *tier ) 171 { 172 LloadBackend *b, *first, *next; 173 time_t now = slap_get_time(); 174 175 checked_lock( &tier->t_mutex ); 176 first = b = tier->t_private; 177 checked_unlock( &tier->t_mutex ); 178 179 if ( !first ) return LDAP_SUCCESS; 180 181 do { 182 int steps; 183 checked_lock( &b->b_mutex ); 184 185 steps = now - b->b_last_update; 186 if ( b->b_weight && steps > 0 ) { 187 uintptr_t count, diff; 188 float factor = 1; 189 190 count = __atomic_exchange_n( 191 &b->b_operation_count, 0, __ATOMIC_RELAXED ); 192 diff = __atomic_exchange_n( 193 &b->b_operation_time, 0, __ATOMIC_RELAXED ); 194 195 /* Smear values over time - rolling average */ 196 if ( count ) { 197 float fitness = b->b_weight * diff; 198 199 /* Stretch factor accordingly favouring the latest value */ 200 if ( steps > 10 ) { 201 factor = 0; /* No recent data */ 202 } else if ( steps > 1 ) { 203 factor = 1 / ( pow( ( 1 / factor ) + 1, steps ) - 1 ); 204 } 205 206 b->b_fitness = ( factor * b->b_fitness + fitness / count ) / 207 ( factor + 1 ); 208 b->b_last_update = now; 209 } 210 } 211 212 next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next ); 213 checked_unlock( &b->b_mutex ); 214 b = next; 215 } while ( b != first ); 216 217 return LDAP_SUCCESS; 218 } 219 220 int 221 bestof_select( 222 LloadTier *tier, 223 LloadOperation *op, 224 LloadConnection **cp, 225 int *res, 226 char **message ) 227 { 228 LloadBackend *first, *next, *b, *b0, *b1; 229 int result = 0, rc = 0, n = tier->t_nbackends; 230 int i0, i1, i = 0; 231 232 checked_lock( &tier->t_mutex ); 233 first = b0 = b = tier->t_private; 234 checked_unlock( &tier->t_mutex ); 235 236 if ( !first ) return rc; 237 238 if ( tier->t_nbackends == 1 ) { 239 goto fallback; 240 } 241 242 /* Pick two backend indices at random */ 243 i0 = bestof_rand() % n; 244 i1 = bestof_rand() % ( n - 1 ); 245 if ( i1 >= i0 ) { 246 i1 += 1; 247 } else { 248 int tmp = i0; 249 i0 = i1; 250 i1 = tmp; 251 } 252 assert( i0 < i1 ); 253 254 /* 255 * FIXME: use a static array in t_private so we don't have to do any of 256 * this 257 */ 258 for ( i = 0; i < i1; i++ ) { 259 if ( i == i0 ) { 260 b0 = b; 261 } 262 checked_lock( &b->b_mutex ); 263 next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next ); 264 checked_unlock( &b->b_mutex ); 265 b = next; 266 } 267 b1 = b; 268 assert( b0 != b1 ); 269 270 if ( bestof_cmp( b0, b1 ) < 0 ) { 271 checked_lock( &b0->b_mutex ); 272 result = backend_select( b0, op, cp, res, message ); 273 checked_unlock( &b0->b_mutex ); 274 } else { 275 checked_lock( &b1->b_mutex ); 276 result = backend_select( b1, op, cp, res, message ); 277 checked_unlock( &b1->b_mutex ); 278 } 279 280 rc |= result; 281 if ( result && *cp ) { 282 checked_lock( &tier->t_mutex ); 283 tier->t_private = LDAP_CIRCLEQ_LOOP_NEXT( 284 &tier->t_backends, (*cp)->c_backend, b_next ); 285 checked_unlock( &tier->t_mutex ); 286 return rc; 287 } 288 289 /* Preferred backends deemed unusable, do a round robin from scratch */ 290 b = first; 291 fallback: 292 do { 293 checked_lock( &b->b_mutex ); 294 next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next ); 295 296 rc = backend_select( b, op, cp, res, message ); 297 checked_unlock( &b->b_mutex ); 298 299 if ( rc && *cp ) { 300 /* 301 * Round-robin step: 302 * Rotate the queue to put this backend at the end. The race here 303 * is acceptable. 304 */ 305 checked_lock( &tier->t_mutex ); 306 tier->t_private = next; 307 checked_unlock( &tier->t_mutex ); 308 return rc; 309 } 310 311 b = next; 312 } while ( b != first ); 313 314 return rc; 315 } 316 317 struct lload_tier_type bestof_tier = { 318 .tier_name = "bestof", 319 320 .tier_init = bestof_init, 321 .tier_startup = tier_startup, 322 .tier_update = bestof_update, 323 .tier_reset = tier_reset, 324 .tier_destroy = tier_destroy, 325 326 .tier_oc = BER_BVC("olcBkLloadTierConfig"), 327 .tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"), 328 329 .tier_add_backend = bestof_add_backend, 330 .tier_remove_backend = bestof_remove_backend, 331 332 .tier_select = bestof_select, 333 }; 334