aurjson: use APCu/memcached for rate limiting

Message ID 20191102223228.27031-1-lfleischer@archlinux.org
State New
Headers show
Series aurjson: use APCu/memcached for rate limiting | expand

Commit Message

Lukas Fleischer Nov. 2, 2019, 10:32 p.m. UTC
There's no need to use permanent storage for rate limiting information;
try to keep it in memory if caching is enabled.

From experiments with our live setup, this reduces the number of
INSERT/DELETE operations per second from 15 to almost 0. Disk writes on
the server hosting the AUR are reduced by 90% (from ~3MB/s to ~300kB/s).

Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
---
 web/lib/aurjson.class.php | 47 ++++++++++++++++++++++++++-------------
 1 file changed, 32 insertions(+), 15 deletions(-)

Patch

diff --git a/web/lib/aurjson.class.php b/web/lib/aurjson.class.php
index 1c31a65..0ac586f 100644
--- a/web/lib/aurjson.class.php
+++ b/web/lib/aurjson.class.php
@@ -152,23 +152,26 @@  class AurJSON {
 			return false;
 		}
 
-		$window_length = config_get("ratelimit", "window_length");
 		$this->update_ratelimit($ip);
-		$stmt = $this->dbh->prepare("
-			SELECT Requests FROM ApiRateLimit
-			WHERE IP = :ip");
-		$stmt->bindParam(":ip", $ip);
-		$result = $stmt->execute();
 
-		if (!$result) {
-			return false;
-		}
+		$status = false;
+		$value = get_cache_value('ratelimit:' . $ip, $status);
+		if (!$status) {
+			$stmt = $this->dbh->prepare("
+				SELECT Requests FROM ApiRateLimit
+				WHERE IP = :ip");
+			$stmt->bindParam(":ip", $ip);
+			$result = $stmt->execute();
+
+			if (!$result) {
+				return false;
+			}
 
-		$row = $stmt->fetch(PDO::FETCH_ASSOC);
-		if ($row['Requests'] > $limit) {
-			return true;
+			$row = $stmt->fetch(PDO::FETCH_ASSOC);
+			$value = $row['Requests'];
 		}
-		return false;
+
+		return $value > $limit;
 	}
 
 	/*
@@ -182,9 +185,23 @@  class AurJSON {
 		$window_length = config_get("ratelimit", "window_length");
 		$db_backend = config_get("database", "backend");
 		$time = time();
-
-		// Clean up old windows
 		$deletion_time = $time - $window_length;
+
+		/* Try to use the cache. */
+		$status = false;
+		$value = get_cache_value('ratelimit-ws:' . $ip, $status);
+		if (!$status || ($status && $value < $deletion_time)) {
+			if (set_cache_value('ratelimit-ws:' . $ip, $time, $window_length) &&
+			    set_cache_value('ratelimit:' . $ip, 1, $window_length)) {
+				return;
+			}
+		} else {
+			$value = get_cache_value('ratelimit:' . $ip, $status);
+			if ($status && set_cache_value('ratelimit:' . $ip, $value + 1, $window_length))
+				return;
+		}
+
+		/* Clean up old windows. */
 		$stmt = $this->dbh->prepare("
 			DELETE FROM ApiRateLimit
 			WHERE WindowStart < :time");