From 3285d493fe1e92cc26a1b94e010ad0ec5aaacd91 Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Thu, 16 Oct 2025 20:57:54 +0530 Subject: [PATCH 01/11] refactor(improvement): use secret URI as key for cache with success and failure --- apisix/plugins/ai-aws-content-moderation.lua | 2 +- apisix/plugins/authz-keycloak.lua | 2 +- apisix/plugins/limit-count.lua | 2 +- apisix/plugins/openid-connect.lua | 2 +- apisix/secret.lua | 109 +++++++++++-------- apisix/ssl/router/radixtree_sni.lua | 2 +- t/config-center-json/secret.t | 4 +- t/config-center-yaml/secret.t | 4 +- 8 files changed, 70 insertions(+), 57 deletions(-) diff --git a/apisix/plugins/ai-aws-content-moderation.lua b/apisix/plugins/ai-aws-content-moderation.lua index d229b47b25fe..2cf45d6d25e7 100644 --- a/apisix/plugins/ai-aws-content-moderation.lua +++ b/apisix/plugins/ai-aws-content-moderation.lua @@ -88,7 +88,7 @@ end function _M.rewrite(conf, ctx) - conf = fetch_secrets(conf, true, conf, "") + conf = fetch_secrets(conf, true) if not conf then return HTTP_INTERNAL_SERVER_ERROR, "failed to retrieve secrets from conf" end diff --git a/apisix/plugins/authz-keycloak.lua b/apisix/plugins/authz-keycloak.lua index 34a0533326f9..12b2fad5a5e2 100644 --- a/apisix/plugins/authz-keycloak.lua +++ b/apisix/plugins/authz-keycloak.lua @@ -764,7 +764,7 @@ end function _M.access(conf, ctx) -- resolve secrets - conf = fetch_secrets(conf, true, conf, "") + conf = fetch_secrets(conf, true) local headers = core.request.headers(ctx) local need_grant_token = conf.password_grant_token_generation_incoming_uri and ctx.var.request_uri == conf.password_grant_token_generation_incoming_uri and diff --git a/apisix/plugins/limit-count.lua b/apisix/plugins/limit-count.lua index 735779234b30..18beb5b78e3f 100644 --- a/apisix/plugins/limit-count.lua +++ b/apisix/plugins/limit-count.lua @@ -34,7 +34,7 @@ end function _M.access(conf, ctx) - conf = fetch_secrets(conf, true, conf, "") + conf = fetch_secrets(conf, true) return limit_count.rate_limit(conf, ctx, plugin_name, 1) end diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index 5afac47fefe4..3682e1bc07e3 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -550,7 +550,7 @@ end function _M.rewrite(plugin_conf, ctx) local conf_clone = core.table.clone(plugin_conf) - local conf = fetch_secrets(conf_clone, true, plugin_conf, "") + local conf = fetch_secrets(conf_clone, true) -- Previously, we multiply conf.timeout before storing it in etcd. -- If the timeout is too large, we should not multiply it again. diff --git a/apisix/secret.lua b/apisix/secret.lua index b8d7b19a522c..d5efc2deed41 100644 --- a/apisix/secret.lua +++ b/apisix/secret.lua @@ -51,7 +51,7 @@ local function check_secret(conf) end - local function secret_kv(manager, confid) +local function secret_kv(manager, confid) local secret_values secret_values = core.config.fetch_created_obj("/secrets") if not secret_values or not secret_values.values then @@ -164,13 +164,50 @@ end -- for test _M.fetch_by_uri = fetch_by_uri +-- Create separate LRU caches for success and failure +local function new_lrucache(cache_type) + local base_path = {"apisix", "lru", "secret", cache_type} + local ttl = core.table.try_read_attr(local_conf, unpack(base_path)) or + core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "ttl") + + if not ttl then + ttl = cache_type == "success" and 300 or 60 -- 5min success, 1min failure default + end + + local count = core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "count") + if not count then + count = 512 + end + + core.log.info("secret ", cache_type, " lrucache ttl: ", ttl, ", count: ", count) + return core.lrucache.new({ + ttl = ttl, count = count, invalid_stale = true, refresh_stale = true + }) +end -local function fetch(uri) +local secrets_success_cache = new_lrucache("success") +local secrets_failure_cache = new_lrucache("failure") + +-- cache-aware fetch function +local function fetch(uri, use_cache) -- do a quick filter to improve retrieval speed if byte(uri, 1, 1) ~= byte('$') then return nil end + -- Check cache first if enabled + if use_cache then + local cached_success = secrets_success_cache(uri) + if cached_success then + return cached_success + end + + local cached_failure = secrets_failure_cache(uri) + if cached_failure then + return nil + end + end + local val, err if string.has_prefix(upper(uri), core.env.PREFIX) then val, err = core.env.fetch_by_uri(uri) @@ -180,63 +217,39 @@ local function fetch(uri) if err then core.log.error("failed to fetch secret value: ", err) - return + if use_cache then + secrets_failure_cache(uri, true) -- cache the failure + end + return nil + end + + if val and use_cache then + secrets_success_cache(uri, val) -- cache the success end return val end -local function new_lrucache() - local ttl = core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "ttl") - if not ttl then - ttl = 300 - end - local count = core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "count") - if not count then - count = 512 - end - core.log.info("secret lrucache ttl: ", ttl, ", count: ", count) - return core.lrucache.new({ - ttl = ttl, count = count, invalid_stale = true, refresh_stale = true - }) -end -local secrets_lrucache = new_lrucache() - - -local fetch_secrets -do - local retrieve_refs - function retrieve_refs(refs) - for k, v in pairs(refs) do - local typ = type(v) - if typ == "string" then - refs[k] = fetch(v) or v - elseif typ == "table" then - retrieve_refs(v) - end +local function retrieve_refs(refs, use_cache) + for k, v in pairs(refs) do + local typ = type(v) + if typ == "string" then + refs[k] = fetch(v, use_cache) or v + elseif typ == "table" then + retrieve_refs(v, use_cache) end - return refs end + return refs +end - local function retrieve(refs) - core.log.info("retrieve secrets refs") - - local new_refs = core.table.deepcopy(refs) - return retrieve_refs(new_refs) +function _M.fetch_secrets(refs, use_cache) + if not refs or type(refs) ~= "table" then + return nil end - function fetch_secrets(refs, cache, key, version) - if not refs or type(refs) ~= "table" then - return nil - end - if not cache then - return retrieve(refs) - end - return secrets_lrucache(key, version, retrieve, refs) - end + local new_refs = core.table.deepcopy(refs) + return retrieve_refs(new_refs, use_cache) end -_M.fetch_secrets = fetch_secrets - return _M diff --git a/apisix/ssl/router/radixtree_sni.lua b/apisix/ssl/router/radixtree_sni.lua index f12be1298680..6104dcb10bc9 100644 --- a/apisix/ssl/router/radixtree_sni.lua +++ b/apisix/ssl/router/radixtree_sni.lua @@ -241,7 +241,7 @@ function _M.set(matched_ssl, sni) end ngx_ssl.clear_certs() - local new_ssl_value = secret.fetch_secrets(matched_ssl.value, true, matched_ssl.value, "") + local new_ssl_value = secret.fetch_secrets(matched_ssl.value, true) or matched_ssl.value ok, err = _M.set_cert_and_key(sni, new_ssl_value) diff --git a/t/config-center-json/secret.t b/t/config-center-json/secret.t index 178f19ef5b09..0adf82ff764b 100644 --- a/t/config-center-json/secret.t +++ b/t/config-center-json/secret.t @@ -396,8 +396,8 @@ env secret=apisix; key = "jack", secret = "$env://secret" } - local refs_1 = secret.fetch_secrets(refs, true, "key", 1) - local refs_2 = secret.fetch_secrets(refs, true, "key", 1) + local refs_1 = secret.fetch_secrets(refs, true) + local refs_2 = secret.fetch_secrets(refs, true) assert(refs_1 == refs_2) ngx.say(refs_1.secret) ngx.say(refs_2.secret) diff --git a/t/config-center-yaml/secret.t b/t/config-center-yaml/secret.t index 82fefd3a576b..e6eee130ec06 100644 --- a/t/config-center-yaml/secret.t +++ b/t/config-center-yaml/secret.t @@ -328,8 +328,8 @@ env secret=apisix; key = "jack", secret = "$env://secret" } - local refs_1 = secret.fetch_secrets(refs, true, "key", 1) - local refs_2 = secret.fetch_secrets(refs, true, "key", 1) + local refs_1 = secret.fetch_secrets(refs, true) + local refs_2 = secret.fetch_secrets(refs, true) assert(refs_1 == refs_2) ngx.say(refs_1.secret) ngx.say(refs_2.secret) From 982d5bcc6844fc33955533f44fd0c3383cd2daca Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Thu, 16 Oct 2025 21:18:23 +0530 Subject: [PATCH 02/11] fix lint --- apisix/cli/config.lua | 8 +++++++- apisix/secret.lua | 6 +++--- conf/config.yaml.example | 8 ++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua index 35212eea7cf1..81a7cf24edd5 100644 --- a/apisix/cli/config.lua +++ b/apisix/cli/config.lua @@ -83,7 +83,13 @@ local _M = { lru = { secret = { ttl = 300, - count = 512 + count = 512, + success = { + ttl = 300 + }, + failure = { + ttl = 60 + } } } }, diff --git a/apisix/secret.lua b/apisix/secret.lua index d5efc2deed41..459d722e43d5 100644 --- a/apisix/secret.lua +++ b/apisix/secret.lua @@ -169,16 +169,16 @@ local function new_lrucache(cache_type) local base_path = {"apisix", "lru", "secret", cache_type} local ttl = core.table.try_read_attr(local_conf, unpack(base_path)) or core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "ttl") - + if not ttl then ttl = cache_type == "success" and 300 or 60 -- 5min success, 1min failure default end - + local count = core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "count") if not count then count = 512 end - + core.log.info("secret ", cache_type, " lrucache ttl: ", ttl, ", count: ", count) return core.lrucache.new({ ttl = ttl, count = count, invalid_stale = true, refresh_stale = true diff --git a/conf/config.yaml.example b/conf/config.yaml.example index d06d689aa110..155938f11b36 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -148,8 +148,12 @@ apisix: # fine tune the parameters of LRU cache for some features like secret lru: secret: - ttl: 300 # seconds - count: 512 + ttl: 300 # Global TTL fallback + count: 512 # Cache size + success: + ttl: 300 # Success cache TTL (overrides global) + failure: + ttl: 60 # Failure cache TTL (overrides global) nginx_config: # Config for render the template to generate nginx.conf # user: root # Set the execution user of the worker process. This is only # effective if the master process runs with super-user privileges. From 82eec8d04a6699bff86fa2f65bc3c60c4403088b Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Thu, 16 Oct 2025 21:24:29 +0530 Subject: [PATCH 03/11] fix lint --- conf/config.yaml.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/config.yaml.example b/conf/config.yaml.example index 155938f11b36..68777c420749 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -152,7 +152,7 @@ apisix: count: 512 # Cache size success: ttl: 300 # Success cache TTL (overrides global) - failure: + failure: ttl: 60 # Failure cache TTL (overrides global) nginx_config: # Config for render the template to generate nginx.conf # user: root # Set the execution user of the worker process. This is only From 01b8d3f3738548df15069245b72e4572b62b4820 Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Thu, 16 Oct 2025 21:30:49 +0530 Subject: [PATCH 04/11] fix basepath --- apisix/secret.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/secret.lua b/apisix/secret.lua index 459d722e43d5..a6ac1ad51cb5 100644 --- a/apisix/secret.lua +++ b/apisix/secret.lua @@ -166,7 +166,7 @@ _M.fetch_by_uri = fetch_by_uri -- Create separate LRU caches for success and failure local function new_lrucache(cache_type) - local base_path = {"apisix", "lru", "secret", cache_type} + local base_path = {"apisix", "lru", "secret", cache_type, "ttl"} local ttl = core.table.try_read_attr(local_conf, unpack(base_path)) or core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "ttl") From e81d58ae4eae5b4b0e20b16e6cc447a802863358 Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Thu, 16 Oct 2025 22:31:17 +0530 Subject: [PATCH 05/11] fix config center test --- apisix/secret.lua | 47 +++++++++++++++-------------------- t/config-center-json/secret.t | 7 +++--- t/config-center-yaml/secret.t | 5 ++-- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/apisix/secret.lua b/apisix/secret.lua index a6ac1ad51cb5..834424199577 100644 --- a/apisix/secret.lua +++ b/apisix/secret.lua @@ -164,7 +164,7 @@ end -- for test _M.fetch_by_uri = fetch_by_uri --- Create separate LRU caches for success and failure +-- create separate LRU caches for success and failure local function new_lrucache(cache_type) local base_path = {"apisix", "lru", "secret", cache_type, "ttl"} local ttl = core.table.try_read_attr(local_conf, unpack(base_path)) or @@ -188,6 +188,7 @@ end local secrets_success_cache = new_lrucache("success") local secrets_failure_cache = new_lrucache("failure") + -- cache-aware fetch function local function fetch(uri, use_cache) -- do a quick filter to improve retrieval speed @@ -195,39 +196,31 @@ local function fetch(uri, use_cache) return nil end - -- Check cache first if enabled - if use_cache then - local cached_success = secrets_success_cache(uri) - if cached_success then - return cached_success - end - - local cached_failure = secrets_failure_cache(uri) - if cached_failure then + local fetch_by_uri + if string.has_prefix(upper(uri), core.env.PREFIX) then + fetch_by_uri = core.env.fetch_by_uri + elseif string.has_prefix(uri, PREFIX) then + fetch_by_uri = fetch_by_uri + end + if not use_cache then + local val, err = fetch_by_uri(uri) + if err then + core.log.error("failed to fetch secret value: ", err) return nil end + return val end - - local val, err - if string.has_prefix(upper(uri), core.env.PREFIX) then - val, err = core.env.fetch_by_uri(uri) - elseif string.has_prefix(uri, PREFIX) then - val, err = fetch_by_uri(uri) + local success_val, err = secrets_success_cache(uri, "", fetch_by_uri, uri) + if success_val then + return success_val end - + local err = secrets_failure_cache(uri, "", function () + return err + end) if err then core.log.error("failed to fetch secret value: ", err) - if use_cache then - secrets_failure_cache(uri, true) -- cache the failure - end - return nil end - - if val and use_cache then - secrets_success_cache(uri, val) -- cache the success - end - - return val + return nil end diff --git a/t/config-center-json/secret.t b/t/config-center-json/secret.t index 0adf82ff764b..ae3cc664bb01 100644 --- a/t/config-center-json/secret.t +++ b/t/config-center-json/secret.t @@ -385,7 +385,7 @@ qr/retrieve secrets refs/ -=== TEST 14: fetch_secrets env: cache +=== TEST 14: fetch_secrets env: cache (fetch data should be only called once and next call return from cache) --- main_config env secret=apisix; --- config @@ -398,7 +398,6 @@ env secret=apisix; } local refs_1 = secret.fetch_secrets(refs, true) local refs_2 = secret.fetch_secrets(refs, true) - assert(refs_1 == refs_2) ngx.say(refs_1.secret) ngx.say(refs_2.secret) } @@ -409,9 +408,9 @@ GET /t apisix apisix --- grep_error_log eval -qr/retrieve secrets refs/ +qr/fetching data from env uri/ --- grep_error_log_out -retrieve secrets refs +fetching data from env uri diff --git a/t/config-center-yaml/secret.t b/t/config-center-yaml/secret.t index e6eee130ec06..569b9b143afb 100644 --- a/t/config-center-yaml/secret.t +++ b/t/config-center-yaml/secret.t @@ -330,7 +330,6 @@ env secret=apisix; } local refs_1 = secret.fetch_secrets(refs, true) local refs_2 = secret.fetch_secrets(refs, true) - assert(refs_1 == refs_2) ngx.say(refs_1.secret) ngx.say(refs_2.secret) } @@ -341,9 +340,9 @@ GET /t apisix apisix --- grep_error_log eval -qr/retrieve secrets refs/ +qr/fetching data from env uri/ --- grep_error_log_out -retrieve secrets refs +fetching data from env uri From 67baf6f5c8ec595cd8bf052c9a7ebd4c9c61a5df Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Thu, 16 Oct 2025 22:43:06 +0530 Subject: [PATCH 06/11] fix --- apisix/secret.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apisix/secret.lua b/apisix/secret.lua index 834424199577..6457ab39bc3a 100644 --- a/apisix/secret.lua +++ b/apisix/secret.lua @@ -136,7 +136,7 @@ local function parse_secret_uri(secret_uri) end -local function fetch_by_uri(secret_uri) +local function fetch_by_uri_secret(secret_uri) core.log.info("fetching data from secret uri: ", secret_uri) local opts, err = parse_secret_uri(secret_uri) if not opts then @@ -162,7 +162,7 @@ local function fetch_by_uri(secret_uri) end -- for test -_M.fetch_by_uri = fetch_by_uri +_M.fetch_by_uri = fetch_by_uri_secret -- create separate LRU caches for success and failure local function new_lrucache(cache_type) @@ -200,7 +200,7 @@ local function fetch(uri, use_cache) if string.has_prefix(upper(uri), core.env.PREFIX) then fetch_by_uri = core.env.fetch_by_uri elseif string.has_prefix(uri, PREFIX) then - fetch_by_uri = fetch_by_uri + fetch_by_uri = fetch_by_uri_secret end if not use_cache then local val, err = fetch_by_uri(uri) From adab15396ea8a8e10c13ede08f8c46f797de67d4 Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Fri, 17 Oct 2025 11:55:52 +0530 Subject: [PATCH 07/11] refactor lrucache --- apisix/cli/config.lua | 8 ++----- apisix/core/lrucache.lua | 20 ++++++++++++++++ apisix/secret.lua | 52 ++++++++++++++++++++++------------------ conf/config.yaml.example | 6 ++--- 4 files changed, 53 insertions(+), 33 deletions(-) diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua index 81a7cf24edd5..1dd9364f476b 100644 --- a/apisix/cli/config.lua +++ b/apisix/cli/config.lua @@ -84,12 +84,8 @@ local _M = { secret = { ttl = 300, count = 512, - success = { - ttl = 300 - }, - failure = { - ttl = 60 - } + neg_ttl = 60, + neg_count = 512 } } }, diff --git a/apisix/core/lrucache.lua b/apisix/core/lrucache.lua index 374d33c908e5..6999685ab368 100644 --- a/apisix/core/lrucache.lua +++ b/apisix/core/lrucache.lua @@ -98,9 +98,23 @@ local function new_lru_fun(opts) local refresh_stale = opts and opts.refresh_stale local serial_creating = opts and opts.serial_creating local lru_obj = lru_new(item_count) + + local neg_lru_obj + if opts and opts.neg_ttl and opts.neg_count then + neg_lru_obj = lru_new(opts.neg_count) + end + stale_obj_pool[lru_obj] = {} return function (key, version, create_obj_fun, ...) + -- check negative cache first + if neg_lru_obj then + local neg_obj = neg_lru_obj:get(key) + if neg_obj and neg_obj.ver == version then + return nil, neg_obj.err + end + end + if not serial_creating or not can_yield_phases[get_phase()] then local cache_obj = fetch_valid_cache(lru_obj, invalid_stale, refresh_stale, item_ttl, key, version, create_obj_fun, ...) @@ -111,6 +125,9 @@ local function new_lru_fun(opts) local obj, err = create_obj_fun(...) if obj ~= nil then lru_obj:set(key, {val = obj, ver = version}, item_ttl) + elseif neg_lru_obj then + -- cache the failure in negative cache + neg_lru_obj:set(key, {err = err, ver = version}, opts.neg_ttl) end return obj, err @@ -146,6 +163,9 @@ local function new_lru_fun(opts) local obj, err = create_obj_fun(...) if obj ~= nil then lru_obj:set(key, {val = obj, ver = version}, item_ttl) + elseif neg_lru_obj then + -- cache the failure in negative cache + neg_lru_obj:set(key, {err = err, ver = version}, opts.neg_ttl) end lock:unlock() log.info("unlock with key ", key_s) diff --git a/apisix/secret.lua b/apisix/secret.lua index 6457ab39bc3a..6de59dce940c 100644 --- a/apisix/secret.lua +++ b/apisix/secret.lua @@ -164,14 +164,11 @@ end -- for test _M.fetch_by_uri = fetch_by_uri_secret --- create separate LRU caches for success and failure -local function new_lrucache(cache_type) - local base_path = {"apisix", "lru", "secret", cache_type, "ttl"} - local ttl = core.table.try_read_attr(local_conf, unpack(base_path)) or - core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "ttl") +local function new_lrucache() + local ttl = core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "ttl") if not ttl then - ttl = cache_type == "success" and 300 or 60 -- 5min success, 1min failure default + ttl = 300 end local count = core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "count") @@ -179,17 +176,33 @@ local function new_lrucache(cache_type) count = 512 end - core.log.info("secret ", cache_type, " lrucache ttl: ", ttl, ", count: ", count) + local neg_ttl = core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "neg_ttl") + if not neg_ttl then + neg_ttl = 60 -- 1 minute default for failures + end + + local neg_count = core.table.try_read_attr(local_conf, "apisix", "lru", "secret", "neg_count") + if not neg_count then + neg_count = 512 + end + + core.log.info("secret lrucache ttl: ", ttl, ", count: ", count, + ", neg_ttl: ", neg_ttl, ", neg_count: ", neg_count) + return core.lrucache.new({ - ttl = ttl, count = count, invalid_stale = true, refresh_stale = true + ttl = ttl, + count = count, + neg_ttl = neg_ttl, + neg_count = neg_count, + invalid_stale = true, + refresh_stale = true }) end -local secrets_success_cache = new_lrucache("success") -local secrets_failure_cache = new_lrucache("failure") +local secrets_cache = new_lrucache() + --- cache-aware fetch function local function fetch(uri, use_cache) -- do a quick filter to improve retrieval speed if byte(uri, 1, 1) ~= byte('$') then @@ -201,7 +214,10 @@ local function fetch(uri, use_cache) fetch_by_uri = core.env.fetch_by_uri elseif string.has_prefix(uri, PREFIX) then fetch_by_uri = fetch_by_uri_secret + else + return nil end + if not use_cache then local val, err = fetch_by_uri(uri) if err then @@ -210,19 +226,9 @@ local function fetch(uri, use_cache) end return val end - local success_val, err = secrets_success_cache(uri, "", fetch_by_uri, uri) - if success_val then - return success_val - end - local err = secrets_failure_cache(uri, "", function () - return err - end) - if err then - core.log.error("failed to fetch secret value: ", err) - end - return nil -end + return secrets_cache(uri, "", fetch_by_uri, uri) +end local function retrieve_refs(refs, use_cache) for k, v in pairs(refs) do diff --git a/conf/config.yaml.example b/conf/config.yaml.example index 68777c420749..139e30edc367 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -150,10 +150,8 @@ apisix: secret: ttl: 300 # Global TTL fallback count: 512 # Cache size - success: - ttl: 300 # Success cache TTL (overrides global) - failure: - ttl: 60 # Failure cache TTL (overrides global) + neg_ttl: 60 # Negative cache TTL + neg_count: 512 # Negative cache size nginx_config: # Config for render the template to generate nginx.conf # user: root # Set the execution user of the worker process. This is only # effective if the master process runs with super-user privileges. From dd03cbc780924cc6dc78c8dda9e7bdcb31745b98 Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Fri, 17 Oct 2025 12:03:13 +0530 Subject: [PATCH 08/11] add test for lrucache --- t/core/lrucache2.t | 273 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 t/core/lrucache2.t diff --git a/t/core/lrucache2.t b/t/core/lrucache2.t new file mode 100644 index 000000000000..58b7c021d2a7 --- /dev/null +++ b/t/core/lrucache2.t @@ -0,0 +1,273 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +log_level("info"); + +run_tests; + +__DATA__ + + +=== TEST 1: negative cache basic functionality +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + + local call_count = 0 + local function create_obj_fail() + call_count = call_count + 1 + return nil, "simulated failure" + end + + -- create LRU cache with negative caching + local lru_get = core.lrucache.new({ + ttl = 1, + count = 256, + neg_ttl = 0.5, -- shorter TTL for failures + neg_count = 128 + }) + + -- First call should execute the function and cache the failure + local obj, err = lru_get("fail_key", "v1", create_obj_fail) + ngx.say("call_count after first call: ", call_count) + ngx.say("first call result: obj=", tostring(obj), ", err=", tostring(err)) + + -- Second call should return from negative cache without calling create_obj_fail + obj, err = lru_get("fail_key", "v1", create_obj_fail) + ngx.say("call_count after second call: ", call_count) + ngx.say("second call result: obj=", tostring(obj), ", err=", tostring(err)) + + -- Different version should bypass negative cache + obj, err = lru_get("fail_key", "v2", create_obj_fail) + ngx.say("call_count after different version: ", call_count) + ngx.say("different version result: obj=", tostring(obj), ", err=", tostring(err)) + } + } +--- request +GET /t +--- response_body +call_count after first call: 1 +first call result: obj=nil, err=simulated failure +call_count after second call: 1 +second call result: obj=nil, err=simulated failure +call_count after different version: 2 +different version result: obj=nil, err=simulated failure + + + +=== TEST 2: negative cache TTL expiration +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + + local call_count = 0 + local function create_obj_fail() + call_count = call_count + 1 + return nil, "simulated failure" + end + + -- Create LRU cache with very short negative TTL + local lru_get = core.lrucache.new({ + ttl = 10, + count = 256, + neg_ttl = 0.1, -- very short TTL for failures + neg_count = 128 + }) + + -- First call + local obj, err = lru_get("fail_key", "v1", create_obj_fail) + ngx.say("call_count after first call: ", call_count) + + -- Immediate second call - should use negative cache + obj, err = lru_get("fail_key", "v1", create_obj_fail) + ngx.say("call_count after immediate call: ", call_count) + + -- Wait for negative cache to expire + ngx.sleep(0.15) + + -- This should call create_obj_fail again + obj, err = lru_get("fail_key", "v1", create_obj_fail) + ngx.say("call_count after TTL expiration: ", call_count) + } + } +--- request +GET /t +--- response_body +call_count after first call: 1 +call_count after immediate call: 1 +call_count after TTL expiration: 2 + + + +=== TEST 3: mixed success and failure caching +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + + local success_count = 0 + local fail_count = 0 + + local function create_obj_success() + success_count = success_count + 1 + return {value = "success_" .. success_count} + end + + local function create_obj_fail() + fail_count = fail_count + 1 + return nil, "failure_" .. fail_count + end + + local lru_get = core.lrucache.new({ + ttl = 1, + count = 256, + neg_ttl = 0.5, + neg_count = 128 + }) + + -- Test success caching + local obj1 = lru_get("success_key", "v1", create_obj_success) + ngx.say("success_count after first success: ", success_count) + ngx.say("success value: ", obj1.value) + + local obj2 = lru_get("success_key", "v1", create_obj_success) + ngx.say("success_count after cached success: ", success_count) + ngx.say("cached success value: ", obj2.value) + + -- Test failure caching + local obj3, err3 = lru_get("fail_key", "v1", create_obj_fail) + ngx.say("fail_count after first failure: ", fail_count) + ngx.say("failure error: ", err3) + + local obj4, err4 = lru_get("fail_key", "v1", create_obj_fail) + ngx.say("fail_count after cached failure: ", fail_count) + ngx.say("cached failure error: ", err4) + } + } +--- request +GET /t +--- response_body +success_count after first success: 1 +success value: success_1 +success_count after cached success: 1 +cached success value: success_1 +fail_count after first failure: 1 +failure error: failure_1 +fail_count after cached failure: 1 +cached failure error: failure_1 + + + +=== TEST 4: negative cache with different keys +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + + local call_count = 0 + local function create_obj_fail(key) + call_count = call_count + 1 + return nil, "failed for " .. key + end + + local lru_get = core.lrucache.new({ + ttl = 1, + count = 256, + neg_ttl = 0.5, + neg_count = 128 + }) + + -- First key + local obj1, err1 = lru_get("key1", "v1", create_obj_fail, "key1") + ngx.say("call_count after key1: ", call_count) + + -- Second key + local obj2, err2 = lru_get("key2", "v1", create_obj_fail, "key2") + ngx.say("call_count after key2: ", call_count) + + -- Repeat key1 - should use negative cache + local obj3, err3 = lru_get("key1", "v1", create_obj_fail, "key1") + ngx.say("call_count after key1 repeat: ", call_count) + ngx.say("key1 error: ", err3) + + -- Repeat key2 - should use negative cache + local obj4, err4 = lru_get("key2", "v1", create_obj_fail, "key2") + ngx.say("call_count after key2 repeat: ", call_count) + ngx.say("key2 error: ", err4) + } + } +--- request +GET /t +--- response_body +call_count after key1: 1 +call_count after key2: 2 +call_count after key1 repeat: 2 +key1 error: failed for key1 +call_count after key2 repeat: 2 +key2 error: failed for key2 + + + +=== TEST 5: negative cache respects version changes +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + + local call_count = 0 + local function create_obj_fail(version) + call_count = call_count + 1 + return nil, "failed for version " .. version + end + + local lru_get = core.lrucache.new({ + ttl = 10, + count = 256, + neg_ttl = 10, + neg_count = 128 + }) + + -- Call with version 1 + local obj1, err1 = lru_get("version_key", "v1", create_obj_fail, "v1") + ngx.say("call_count after v1: ", call_count) + + -- Call with version 1 again - should use negative cache + local obj2, err2 = lru_get("version_key", "v1", create_obj_fail, "v1") + ngx.say("call_count after v1 repeat: ", call_count) + + -- Call with version 2 - should bypass negative cache + local obj3, err3 = lru_get("version_key", "v2", create_obj_fail, "v2") + ngx.say("call_count after v2: ", call_count) + + -- Call with version 2 again - should use negative cache + local obj4, err4 = lru_get("version_key", "v2", create_obj_fail, "v2") + ngx.say("call_count after v2 repeat: ", call_count) + } + } +--- request +GET /t +--- response_body +call_count after v1: 1 +call_count after v1 repeat: 1 +call_count after v2: 2 +call_count after v2 repeat: 2 From 6175a5dda7b7c5f4061e61cfbb606790fd83523f Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Fri, 17 Oct 2025 12:03:39 +0530 Subject: [PATCH 09/11] fix lint --- apisix/secret.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/secret.lua b/apisix/secret.lua index 6de59dce940c..8ad1be260012 100644 --- a/apisix/secret.lua +++ b/apisix/secret.lua @@ -186,7 +186,7 @@ local function new_lrucache() neg_count = 512 end - core.log.info("secret lrucache ttl: ", ttl, ", count: ", count, + core.log.info("secret lrucache ttl: ", ttl, ", count: ", count, ", neg_ttl: ", neg_ttl, ", neg_count: ", neg_count) return core.lrucache.new({ From 14b643b7fe9f964ffef3e64fe0cc6aaf6b499948 Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Fri, 17 Oct 2025 12:06:01 +0530 Subject: [PATCH 10/11] fix lint --- t/core/lrucache2.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/core/lrucache2.t b/t/core/lrucache2.t index 58b7c021d2a7..31037fd16b0e 100644 --- a/t/core/lrucache2.t +++ b/t/core/lrucache2.t @@ -40,7 +40,7 @@ __DATA__ -- create LRU cache with negative caching local lru_get = core.lrucache.new({ - ttl = 1, + ttl = 1, count = 256, neg_ttl = 0.5, -- shorter TTL for failures neg_count = 128 @@ -127,7 +127,7 @@ call_count after TTL expiration: 2 local success_count = 0 local fail_count = 0 - + local function create_obj_success() success_count = success_count + 1 return {value = "success_" .. success_count} From 0babe3fb698b1bcbaffcb1ae956d8772e22819fe Mon Sep 17 00:00:00 2001 From: Ashish Tiwari Date: Fri, 17 Oct 2025 12:15:05 +0530 Subject: [PATCH 11/11] lint --- t/core/lrucache2.t | 1 - 1 file changed, 1 deletion(-) diff --git a/t/core/lrucache2.t b/t/core/lrucache2.t index 31037fd16b0e..1da3e4b2799d 100644 --- a/t/core/lrucache2.t +++ b/t/core/lrucache2.t @@ -25,7 +25,6 @@ run_tests; __DATA__ - === TEST 1: negative cache basic functionality --- config location /t {