Skip to content

Perf: Batcher policy refactor#1583

Open
tkan145 wants to merge 4 commits into
3scale:masterfrom
tkan145:batcher-refactor
Open

Perf: Batcher policy refactor#1583
tkan145 wants to merge 4 commits into
3scale:masterfrom
tkan145:batcher-refactor

Conversation

@tkan145

@tkan145 tkan145 commented May 28, 2026

Copy link
Copy Markdown
Contributor

What

Perf improvement for batcher policy

Perf result

Perf script

Details
local root = require('pl.path').currentdir()
package.path = root .. '/?.lua;' ..
               root .. '/gateway/src/?.lua;' ..
               root .. '/gateway/src/apicast/policy/3scale_batcher/?.lua;' ..
               package.path

local monotonic_msec = require("resty.core.time").monotonic_msec
local collectgarbage = collectgarbage
local update_time = ngx.update_time
local sleep = ngx.sleep
local print = print
local str_find = string.find
local str_sub = string.sub
local Usage = require('apicast.usage')
local keys_helper = require('apicast.policy.3scale_batcher.keys_helper')

local ITERATIONS = 1000000
local s, e

local function bench(name, fn)
    -- warmup
    for i = 1, math.min(ITERATIONS, 50) do fn(i) end

    collectgarbage()
    collectgarbage()
    collectgarbage()
    sleep(1)

    update_time()
    s = monotonic_msec()
    for i = 1, ITERATIONS do
      fn(i)
    end
    update_time()
    e = monotonic_msec()
    print(string.format("  %s: ", name), e - s, " ms")
end

-- Setup: build representative test data
local usage_single = Usage.new()
usage_single:add('hits', 1)

local usage_multi = Usage.new()
usage_multi:add('hits', 1)
usage_multi:add('requests', 1)
usage_multi:add('data_sent', 512)

local creds_user_key = { user_key = 'abc123def456' }
local creds_app_id_key = { app_id = 'app_001', app_key = 'secret_key_xyz' }
local creds_access_token = { access_token = 'tok_abcdefghijklmnop' }

-- Sample keys for report parsing (as produced by key_for_batched_report)
local report_key_uk = keys_helper.key_for_batched_report('svc_100', creds_user_key, 'hits')
local report_key_app = keys_helper.key_for_batched_report('svc_100', creds_app_id_key, 'hits')
local report_key_token = keys_helper.key_for_batched_report('svc_100', creds_access_token, 'hits')

-- Sample cached auth values (as produced by value_to_cache)
local cached_status_only = '200'
local cached_with_reason = '409:limits_exceeded'
local cached_with_colons = '409:reason:with:colons'

require('resty.core')
for _ = 1, 2 do

  ---------------------------------------------------------------------------
  -- Benchmark: report key parsing (detect_cred_type + single regex)
  ---------------------------------------------------------------------------
  print('\n--- Report key parsing ---')

  bench('report_from_key (user_key)', function()
    return keys_helper.report_from_key_batched_report(report_key_uk, 5)
  end)

  bench('report_from_key (app_id+key)', function()
    return keys_helper.report_from_key_batched_report(report_key_app, 10)
  end)

  bench('report_from_key (access_token)', function()
    return keys_helper.report_from_key_batched_report(report_key_token, 3)
  end)

  ---------------------------------------------------------------------------
  -- Benchmark: cached auth value parsing (string ops vs regex split)
  ---------------------------------------------------------------------------
  print('\n--- Cached auth value parsing ---')

  local re = require('ngx.re')
  local re_split = re.split

  -- Current: plain string operations
  local function parse_string_ops(cached_value)
    local colon = str_find(cached_value, ':', 1, true)
    if colon then
      return tonumber(str_sub(cached_value, 1, colon - 1)),
             str_sub(cached_value, colon + 1)
    end
    return tonumber(cached_value)
  end

  -- Previous: regex split
  local function parse_re_split(cached_value)
    local split_val = re_split(cached_value, ':', 'oj', nil, 2)
    return tonumber(split_val[1]), split_val[2]
  end

  bench('parse status-only (string ops)', function()
    return parse_string_ops(cached_status_only)
  end)

  bench('parse status-only (re.split)', function()
    return parse_re_split(cached_status_only)
  end)

  bench('parse status+reason (string ops)', function()
    return parse_string_ops(cached_with_reason)
  end)

  bench('parse status+reason (re.split)', function()
    return parse_re_split(cached_with_reason)
  end)

  bench('parse reason with colons (string ops)', function()
    return parse_string_ops(cached_with_colons)
  end)

  bench('parse reason with colons (re.split)', function()
    return parse_re_split(cached_with_colons)
  end)

end

Key parsing

Before

--- Report key parsing ---
  report_from_key (user_key): 321 ms
  report_from_key (app_id+key): 583 ms
  report_from_key (access_token): 454 ms

--- Report key parsing ---
  report_from_key (user_key): 343 ms
  report_from_key (app_id+key): 589 ms
  report_from_key (access_token): 424 ms

After

--- Report key parsing ---
  report_from_key (user_key): 289 ms
  report_from_key (app_id+key): 399 ms
  report_from_key (access_token): 287 ms

--- Report key parsing ---
  report_from_key (user_key): 284 ms
  report_from_key (app_id+key): 390 ms
  report_from_key (access_token): 344 ms

String ops vs regex

--- Cached auth value parsing ---
  parse status-only (string ops): 0 ms
  parse status-only (re.split): 23 ms
  parse status+reason (string ops): 0 ms
  parse status+reason (re.split): 112 ms
  parse reason with colons (string ops): 1 ms
  parse reason with colons (re.split): 103 ms

@tkan145 tkan145 requested a review from a team as a code owner May 28, 2026 03:17
@tkan145 tkan145 force-pushed the batcher-refactor branch from 6e7835f to 0b6b299 Compare July 8, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants