Files
2026-07-13 12:32:21 +08:00

222 lines
5.9 KiB
Lua

local hostname_type = require("kong.tools.ip").hostname_type
local constants = require "kong.constants"
local buffer = require "string.buffer"
local acl_groups
local load_module_if_exists = require "kong.tools.module".load_module_if_exists
if load_module_if_exists("kong.plugins.acl.groups") then
acl_groups = require "kong.plugins.acl.groups"
end
local cache_warmup = {}
local encode = buffer.encode
local tostring = tostring
local ipairs = ipairs
local math = math
local max = math.max
local floor = math.floor
local kong = kong
local type = type
local ngx = ngx
local now = ngx.now
local log = ngx.log
local NOTICE = ngx.NOTICE
local DEBUG = ngx.DEBUG
local NO_TTL_FLAG = require("kong.resty.mlcache").NO_TTL_FLAG
local GLOBAL_QUERY_OPTS = { workspace = ngx.null, show_ws_id = true }
function cache_warmup._mock_kong(mock_kong)
kong = mock_kong
end
local function warmup_dns(premature, hosts, count)
if premature then
return
end
log(NOTICE, "warming up DNS entries ...")
local start = now()
local upstreams_dao = kong.db["upstreams"]
local upstreams_names = {}
if upstreams_dao then
local page_size
if upstreams_dao.pagination then
page_size = upstreams_dao.pagination.max_page_size
end
for upstream, err in upstreams_dao:each(page_size, GLOBAL_QUERY_OPTS) do
if err then
log(NOTICE, "failed to iterate over upstreams: ", err)
break
end
upstreams_names[upstream.name] = true
end
end
for i = 1, count do
local host = hosts[i]
local must_warm_up = upstreams_names[host] == nil
-- warmup DNS entry only if host is not an upstream name
if must_warm_up then
kong.dns.toip(host)
end
end
local elapsed = floor((now() - start) * 1000)
log(NOTICE, "finished warming up DNS entries",
"' into the cache (in ", tostring(elapsed), "ms)")
end
function cache_warmup.single_entity(dao, entity)
local entity_name = dao.schema.name
local cache_store = constants.ENTITY_CACHE_STORE[entity_name]
local cache_key = dao:cache_key(entity)
local cache = kong[cache_store]
local ok, err
if cache then
ok, err = cache:safe_set(cache_key, entity)
else
cache_key = "kong_core_db_cache" .. cache_key
local ttl = max(kong.configuration.db_cache_ttl or 3600, 0)
local value = encode(entity)
ok, err = ngx.shared.kong_core_db_cache:safe_set(cache_key, value, ttl, ttl == 0 and NO_TTL_FLAG or 0)
end
if not ok then
return nil, err
end
return true
end
function cache_warmup.single_dao(dao)
local entity_name = dao.schema.name
local cache_store = constants.ENTITY_CACHE_STORE[entity_name]
log(NOTICE, "Preloading '", entity_name, "' into the ", cache_store, "...")
local start = now()
local hosts_array, hosts_set, host_count
if entity_name == "services" then
hosts_array = {}
hosts_set = {}
host_count = 0
end
local page_size
if dao.pagination then
page_size = dao.pagination.max_page_size
end
for entity, err in dao:each(page_size, GLOBAL_QUERY_OPTS) do
if err then
return nil, err
end
if entity_name == "services" then
if hostname_type(entity.host) == "name"
and hosts_set[entity.host] == nil then
host_count = host_count + 1
hosts_array[host_count] = entity.host
hosts_set[entity.host] = true
end
end
local ok, err = cache_warmup.single_entity(dao, entity)
if not ok then
return nil, err
end
if entity_name == "acls" and acl_groups ~= nil then
log(DEBUG, "warmup acl groups cache for consumer id: ", entity.consumer.id , "...")
local _, err = acl_groups.warmup_groups_cache(entity.consumer.id)
if err then
log(NOTICE, "warmup acl groups cache for consumer id: ", entity.consumer.id , " err: ", err)
end
end
end
if entity_name == "services" and host_count > 0 then
ngx.timer.at(0, warmup_dns, hosts_array, host_count)
end
local elapsed = floor((now() - start) * 1000)
log(NOTICE, "finished preloading '", entity_name,
"' into the ", cache_store, " (in ", tostring(elapsed), "ms)")
return true
end
-- Loads entities from the database into the cache, for rapid subsequent
-- access. This function is intended to be used during worker initialization.
function cache_warmup.execute(entities)
if not kong.cache or not kong.core_cache then
return true
end
for _, entity_name in ipairs(entities) do
if entity_name == "routes" then
-- do not spend shm memory by caching individual Routes entries
-- because the routes are kept in-memory by building the router object
kong.log.notice("the 'routes' entity is ignored in the list of ",
"'db_cache_warmup_entities' because Kong ",
"caches routes in memory separately")
goto continue
end
if entity_name == "plugins" then
-- to speed up the init, the plugins are warmed up upon initial
-- plugin iterator build
kong.log.notice("the 'plugins' entity is ignored in the list of ",
"'db_cache_warmup_entities' because Kong ",
"pre-warms plugins automatically")
goto continue
end
local dao = kong.db[entity_name]
if not (type(dao) == "table" and dao.schema) then
kong.log.warn(entity_name, " is not a valid entity name, please check ",
"the value of 'db_cache_warmup_entities'")
goto continue
end
local ok, err = cache_warmup.single_dao(dao)
if not ok then
if err == "no memory" then
kong.log.warn("cache warmup has been stopped because cache ",
"memory is exhausted, please consider increasing ",
"the value of 'mem_cache_size' (currently at ",
kong.configuration.mem_cache_size, ")")
return true
end
return nil, err
end
::continue::
end
return true
end
return cache_warmup