rapidity 0.0.7.362805 → 0.0.7.369741

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec1c0e662f963921cf709fa7769897774ab2814b9c1c6b5017dc38379e25614a
4
- data.tar.gz: 978b48d1149569859268054e0cbd8eeff7577c9b2fc5cceec2040a1361b974e1
3
+ metadata.gz: b9b41e5968e93c6bcb0d1a323f73890ea6f8ada6e47ff143a7c1fd631083e318
4
+ data.tar.gz: d6ec9a04b1c674362757670101dd82fa752c219ade4717a0f08d89cb97f811a2
5
5
  SHA512:
6
- metadata.gz: ca19db773ea028961e6dd593faf29d27a0039b809e22b4be258cbbd1a237b5b14d306ef57928d53a5ffd7578e113e4701d933416bd1ed49ad86a29e65f9edd7b
7
- data.tar.gz: 5d43836d52cda325188b2628e92037e54e44d58972c36ea2eefc024e64aa011191c16971326c01ad86b2eb7db84af6b8a8dc68e8a17374f5b901227202fec437
6
+ metadata.gz: 52c1cb448e7769412f83aa7636a1339d28b04288a3af798b3563ed8d1b2f0904077deb211b556b2660d32bda3f24971424df8ccf812f12f1032bb6d4e705234f
7
+ data.tar.gz: e8feb958fae4dbf853d669d75f1caa503aab0b2fb23a9df490c3deceea3b7865f17bdb44f89a7918a76f846018f03a76a8c44e0ff8a2d997e455beba4f4b352f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rapidity (0.0.7.362805)
4
+ rapidity (0.0.7.369741)
5
5
  activesupport
6
6
  connection_pool
7
7
  ostruct
@@ -7,13 +7,13 @@
7
7
  -- данные между чтением и записью) и высокую производительность.
8
8
  --
9
9
  -- Описание функционала файла acquire.lua
10
- -- 1. Атомарный захват по нескольким лимитам ("Всё или ничего"):
10
+ -- 1. Атомарный захват по нескольким лимитам ("Всё или ничего"):
11
11
  -- Скрипт принимает список ключей (лимитов) и запрошенное количество токенов.
12
12
  -- Он проверяет, есть ли нужное количество токенов во всех переданных лимитах одновременно.
13
13
  -- 2. Ленивое пополнение (Lazy Refill): Токены не пополняются каким-то фоновым процессом.
14
14
  -- Вместо этого при каждом обращении (в функции Limit:update) вычисляется разница во времени
15
15
  -- с момента последнего обращения (current_time - self.last_used) и добавляется количество токенов, пропорциональное скорости rate.
16
- -- 3. Проверка условий (Validation):
16
+ -- 3. Проверка условий (Validation):
17
17
  -- - Если запрашивается <= 0 токенов или передан пустой список ключей — сразу возвращается ошибка.
18
18
  -- - Если хотя бы одного ключа не существует в Redis, возвращается key_not_found.
19
19
  -- - Если хотя бы в одном лимите не хватает токенов, скрипт возвращает ошибку not_limits и ничего не списывает из других лимитов (откат транзакции).
@@ -48,7 +48,7 @@ function Limit:new(limit_key)
48
48
  local obj = { key = limit_key, exists = false }
49
49
  setmetatable(obj, self)
50
50
 
51
- -- Оптимизация: вместо EXISTS + HMGET делаем только HMGET.
51
+ -- Оптимизация: вместо EXISTS + HMGET делаем только HMGET.
52
52
  -- Если ключа нет, values[1] будет nil.
53
53
  local values = redis.call("HMGET", limit_key,
54
54
  "max_tokens",
@@ -57,7 +57,7 @@ function Limit:new(limit_key)
57
57
  "last_used",
58
58
  "rate"
59
59
  )
60
-
60
+
61
61
  if not values[1] then
62
62
  return obj -- Ключ не существует
63
63
  end
@@ -68,24 +68,28 @@ function Limit:new(limit_key)
68
68
  obj.last_used = tonumber(values[4]) or 0
69
69
  obj.rate = tonumber(values[5]) or 0
70
70
 
71
- obj.exists = (obj.max_tokens > 0) and (obj.interval > 0)
72
-
71
+ obj.exists = (obj.max_tokens > 0) and (obj.interval > 0)
72
+
73
73
  return obj
74
74
  end
75
75
 
76
76
  -- "Ленивое" пополнение корзины токенов на основе прошедшего времени
77
77
  function Limit:update(current_time)
78
- if not self.exists then return self end
79
-
78
+ if not self.exists then return self end
79
+
80
80
  local time_passed = current_time - self.last_used
81
81
  if time_passed <= 0 then return self end
82
-
82
+
83
83
  local tokens_to_add = math.floor(time_passed * self.rate)
84
84
  self.tokens = math.min(self.tokens + tokens_to_add, self.max_tokens)
85
-
85
+
86
86
  if tokens_to_add > 0 then
87
- local time_consumed = tokens_to_add / self.rate
88
- self.last_used = math.min(self.last_used + time_consumed, current_time)
87
+ if self.tokens == self.max_tokens then
88
+ self.last_used = current_time
89
+ else
90
+ local time_consumed = tokens_to_add / self.rate
91
+ self.last_used = math.min(self.last_used + time_consumed, current_time)
92
+ end
89
93
  end
90
94
 
91
95
  return self
@@ -94,10 +98,10 @@ end
94
98
  -- Сохранение обновленного стейта лимита в Redis
95
99
  function Limit:save()
96
100
  if not self.exists then return end
97
-
101
+
98
102
  redis.call("HMSET", self.key,
99
- "tokens", self.tokens,
100
- "last_used", self.last_used
103
+ "tokens", self.tokens,
104
+ "last_used", self.last_used
101
105
  )
102
106
  end
103
107
 
@@ -128,14 +132,14 @@ local function process_all_limits(limit_keys, requested, current_time)
128
132
  if #limit_keys == 0 then
129
133
  return { "result", "false", "retryable", "false", "error", "no keys passed" }
130
134
  end
131
-
135
+
132
136
  local limits = {}
133
-
137
+
134
138
  -- Фаза 1: Проверка всех лимитов (Read & Validate)
135
139
  for i = 1, #limit_keys do
136
140
  local key = limit_keys[i]
137
141
  local limit = Limit:new(key)
138
-
142
+
139
143
  if not limit.exists then
140
144
  return { "result", "false", "retryable", "false", "error", "key_not_found", "key", key }
141
145
  end
@@ -153,22 +157,22 @@ local function process_all_limits(limit_keys, requested, current_time)
153
157
  "key", key
154
158
  }
155
159
  end
156
-
160
+
157
161
  table.insert(limits, limit)
158
162
  end
159
-
163
+
160
164
  -- Фаза 2: Применение изменений (Write)
161
165
  -- Выполняется только если всем лимитам хватило токенов
162
166
  for i = 1, #limits do
163
167
  local limit = limits[i]
164
168
  limit:acquire(requested)
165
169
  limit:save()
166
-
167
- -- Продлеваем жизнь ключу, чтобы он не удалился, если к нему активно обращаются.
170
+
171
+ -- Продлеваем жизнь ключу, чтобы он не удалился, если к нему активно обращаются.
168
172
  -- "GT" обновляет TTL только если новый TTL больше текущего.
169
173
  redis.call("EXPIRE", limit.key, key_ttl, "GT")
170
174
  end
171
-
175
+
172
176
  return { "result", "true", "keys", limit_keys }
173
177
  end
174
178
 
@@ -100,8 +100,9 @@ function Limit:available_in(needed)
100
100
  end
101
101
 
102
102
  local deficit_tokens = needed - self.tokens
103
+ local time_passed = current_time - self.last_used
103
104
  -- Округляем вверх (math.ceil), так как нам нужно дождаться появления ЦЕЛОГО токена
104
- return math.ceil(deficit_tokens / self.rate)
105
+ return math.ceil(deficit_tokens / self.rate) - time_passed
105
106
  end
106
107
 
107
108
  -------------------------------------------------------------------------------
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rapidity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7.362805
4
+ version: 0.0.7.369741
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yurusov Vlad
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-03-19 00:00:00.000000000 Z
12
+ date: 2026-04-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport