I am new to kong api gateway, I am planning to use rate limiting plugin in cluster ode and will use postgres as backend data store . I want to apply at my service level. SO for each request the plugin will make 2 db calls ( one to get and one to update) per request ?
Yes, the plugin makes 2 DB calls per request per service, when in cluster mode with Postgres as the backend.
Kong's rate-limiting plugin, when configured in cluster mode, stores limit counters in a shared backend.
The table is defined in migrations /000_base_rate_limiting.lua:
CREATE TABLE IF NOT EXISTS "ratelimiting_metrics" (
"identifier" TEXT NOT NULL,
"period" TEXT NOT NULL,
"period_date" TIMESTAMP WITH TIME ZONE NOT NULL,
"service_id" UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::UUID,
"route_id" UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::UUID,
"value" INTEGER,
PRIMARY KEY ("identifier", "period", "period_date", "service_id", "route_id")
);
Then, policies/cluster.lua defines two APIs to get (find) and update (increment) the rate limit metrics:
find:
find = function(identifier, period, current_timestamp, service_id, route_id)
local periods = timestamp.get_timestamps(current_timestamp)
find_pk.identifier = identifier
find_pk.period = period
find_pk.period_date = floor(periods[period] / 1000)
find_pk.service_id = service_id
find_pk.route_id = route_id
return kong.db.ratelimiting_metrics:select(find_pk)
end
increment:
increment = function(connector, limits, identifier, current_timestamp, service_id, route_id, value)
local buf = { "BEGIN" }
local len = 1
local periods = timestamp.get_timestamps(current_timestamp)
for _, period in ipairs(timestamp.timestamp_table_fields) do
local period_date = periods[period]
if limits[period] then
len = len + 1
buf[len] = fmt([[
INSERT INTO "ratelimiting_metrics" ("identifier", "period", "period_date", "service_id", "route_id", "value", "ttl")
VALUES (%s, %s, TO_TIMESTAMP(%s) AT TIME ZONE 'UTC', %s, %s, %s, CURRENT_TIMESTAMP AT TIME ZONE 'UTC' + INTERVAL %s)
ON CONFLICT ("identifier", "period", "period_date", "service_id", "route_id") DO UPDATE
SET "value" = "ratelimiting_metrics"."value" + EXCLUDED."value"
]],
connector:escape_literal(identifier),
connector:escape_literal(period),
connector:escape_literal(tonumber(fmt("%.3f", floor(period_date / 1000)))),
connector:escape_literal(service_id),
connector:escape_literal(route_id),
connector:escape_literal(value),
connector:escape_literal(tostring(EXPIRATION[period]) .. " second"))
end
end
if len > 1 then
local sql
if len == 2 then
sql = buf[2]
else
buf[len + 1] = "COMMIT;"
sql = concat(buf, ";\n")
end
local res, err = connector:query(sql)
if not res then
return nil, err
end
end
return true
end
These functions are set in init.lua, when rate-limiting is configured with cluster policy (conf.policy = cluster):
["cluster"] = {
increment = function(conf, limits, identifier, current_timestamp, value)
local db = kong.db
local service_id, route_id = get_service_and_route_ids(conf)
local policy = policy_cluster[db.strategy]
local ok, err = policy.increment(db.connector, limits, identifier,
current_timestamp, service_id, route_id,
value)
if not ok then
kong.log.err("cluster policy: could not increment ", db.strategy,
" counter: ", err)
end
return ok, err
end,
usage = function(conf, identifier, period, current_timestamp)
local db = kong.db
local service_id, route_id = get_service_and_route_ids(conf)
local policy = policy_cluster[db.strategy]
local row, err = policy.find(identifier, period, current_timestamp,
service_id, route_id)
if err then
return nil, err
end
if row and row.value ~= null and row.value > 0 then
return row.value
end
return 0
end,
},
These APIs are then called in handler.lua, during plugin execution:
local function get_usage(conf, identifier, current_timestamp, limits)
local usage = {}
local stop
for period, limit in pairs(limits) do
local current_usage, err = policies[conf.policy].usage(conf, identifier, period, current_timestamp)
if err then
return nil, nil, err
end
-- What is the current usage for the configured limit name?
local remaining = limit - current_usage
-- Recording usage
usage[period] = {
limit = limit,
remaining = remaining,
}
if remaining <= 0 then
stop = period
end
end
return usage, stop
end
-- [...]
local function increment(premature, conf, ...)
if premature then
return
end
policies[conf.policy].increment(conf, ...)
end
And used like this:
function RateLimitingHandler:access(conf)
-- [...]
local usage, stop, err = get_usage(conf, identifier, current_timestamp, limits)
-- [...]
if conf.sync_rate ~= SYNC_RATE_REALTIME and conf.policy == "redis" then
increment(false, conf, limits, identifier, current_timestamp, 1)
else
local ok, err = timer_at(0, increment, conf, limits, identifier, current_timestamp, 1)
if not ok then
kong.log.err("failed to create timer: ", err)
end
end
Therefore: for each request (call to rate-limiting's access()), unless an error occurs, there's a get (find()) and a update (increment()) call, i.e. 2 DB calls.