kongkong-plugin

Kong rate limiting in cluster mode


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 ?


Solution

  • TL;DR

    Yes, the plugin makes 2 DB calls per request per service, when in cluster mode with Postgres as the backend.

    Long Version

    Kong's rate-limiting plugin, when configured in cluster mode, stores limit counters in a shared backend.

    Definitions and Plugin Configuration

    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:

    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,
      },
    

    Usage

    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.