Home | History | Annotate | Line # | Download | only in lloadd
      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