Repository: valkey-io/valkey
File: src/expire.c (C)

Code before:
/* activeExpireCycle
 *
 * This function performs active expiration of both normal keys (with TTL)
 * and hash fields (with field-level TTL via volatile sets). Its purpose is to
 * reclaim memory from logically expired entries.
 *
 * The expiry is performed incrementally over multiple databases, respecting
 * a CPU time budget derived from the configured active-expire-effort.
 *
 * There are two separate expiry mechanisms for keys and for hash fields
 * because their iteration models are fundamentally different:
 * - key expiry operates on db->key entries, scanning random keys
 *   with attached TTL entries.
 * - field expiry operates on db->key->volatile_set entries, scanning
 *   fields within a hash that each have their own TTL.
 * This hierarchy and lookup pattern are entirely different, requiring
 * separate cursors, iteration logic, and data structure handling.
 *
 * The function uses an alternating scheme across event loop cycles: on one
 * cycle it will prioritize key expiry first, then hash field expiry if time
 * permits; on the next cycle, it will prioritize hash field expiry first,
 * then key expiry if time permits. This ensures fairness and prevents
 * starvation of either mechanism. Since the memory reclaim pace and iteration
 * model of keys versus hash fields are different and unpredictable,
 * alternating naturally balances the overall expiry effort when both are
 * fully consuming their available time budget. */
long long activeExpireCycle(int type) {
    /* If 'expire' action is paused, for whatever reason, then don't expire any key.
     * Typically, at the end of the pause we will properly expire the key OR we
     * will have failed over and the new primary will send us the expire. */
    if (isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)) return 0;

    /* Adjust the running parameters according to the configured expire
     * effort. The default effort is 1, and the maximum configurable effort
     * is 10. Also make sure not to run fast cycles back to back. */
    long long timelimit_us;
    if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
        long long config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION + ACTIVE_EXPIRE_CYCLE_FAST_DURATION / 4 * activeExpireEffort();

        /* Never repeat a fast cycle for the same period
         * as the fast cycle total duration itself. */
        static monotime last_fast_cycle_start_time; /* When last fast cycle ran. */
        monotime start = getMonotonicUs();
        if (start < last_fast_cycle_start_time + config_cycle_fast_duration * 2) return 0;

        last_fast_cycle_start_time = start;
        timelimit_us = config_cycle_fast_duration;
    } else {
        /* We can use at max 'config_cycle_slow_time_perc' percentage of CPU
         * time per iteration. Since this function gets called with a frequency of
         * server.hz times per second, the following is the max amount of

Review comment (suggestion):
Now that it returns a number instead of void, let's document it.

```suggestion
 * fully consuming their available time budget.
 *
 * Returns the time spend on active expiration in microseconds. */
```

Code after:
/* activeExpireCycle
 *
 * This function performs active expiration of both normal keys (with TTL)
 * and hash fields (with field-level TTL via volatile sets). Its purpose is to
 * reclaim memory from logically expired entries.
 *
 * The expiry is performed incrementally over multiple databases, respecting
 * a CPU time budget derived from the configured active-expire-effort.
 *
 * There are two separate expiry mechanisms for keys and for hash fields
 * because their iteration models are fundamentally different:
 * - key expiry operates on db->key entries, scanning random keys
 *   with attached TTL entries.
 * - field expiry operates on db->key->volatile_set entries, scanning
 *   fields within a hash that each have their own TTL.
 * This hierarchy and lookup pattern are entirely different, requiring
 * separate cursors, iteration logic, and data structure handling.
 *
 * The function uses an alternating scheme across event loop cycles: on one
 * cycle it will prioritize key expiry first, then hash field expiry if time
 * permits; on the next cycle, it will prioritize hash field expiry first,
 * then key expiry if time permits. This ensures fairness and prevents
 * starvation of either mechanism. Since the memory reclaim pace and iteration
 * model of keys versus hash fields are different and unpredictable,
 * alternating naturally balances the overall expiry effort when both are
 * fully consuming their available time budget.
 *
 * Returns the time spend on active expiration in microseconds. */
long long activeExpireCycle(int type) {
    /* If 'expire' action is paused, for whatever reason, then don't expire any key.
     * Typically, at the end of the pause we will properly expire the key OR we
     * will have failed over and the new primary will send us the expire. */
    if (isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)) return 0;

    /* Adjust the running parameters according to the configured expire
     * effort. The default effort is 1, and the maximum configurable effort
     * is 10. Also make sure not to run fast cycles back to back. */
    long long timelimit_us;
    if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
        long long config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION + ACTIVE_EXPIRE_CYCLE_FAST_DURATION / 4 * activeExpireEffort();

        /* Never repeat a fast cycle for the same period
         * as the fast cycle total duration itself. */
        static monotime last_fast_cycle_start_time; /* When last fast cycle ran. */
        monotime start = getMonotonicUs();
        if (start < last_fast_cycle_start_time + config_cycle_fast_duration * 2) return 0;

        last_fast_cycle_start_time = start;
        timelimit_us = config_cycle_fast_duration;
    } else {
        /* We can use at max 'config_cycle_slow_time_perc' percentage of CPU