Skip to content

Commit cb0d321

Browse files
committed
feat(lib): add keep_prefix functionality for long keys (#67)
Introduce `keep_prefix` parameter for `get` and `set` methods. The `keep_prefix` preserves the first 479 bytes of keys longer than 479 bytes, appending the SHA-256 hash to maintain prefix search capability with `lmdb.prefix()` while complying with LMDB's 511-byte key size limit. This transformation occurs specifically when keys exceed 479 bytes. If off, when the key exceeds LMDB's 511-byte key size limit, the entire key is automatically hashed with sha-256 and used as the actual key.
1 parent b8e3daa commit cb0d321

File tree

4 files changed

+239
-13
lines changed

4 files changed

+239
-13
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ interacting with the module to access/change data.
3737

3838
#### get
3939

40-
**syntax:** *value, err = lmdb.get(key, db?)*
40+
**syntax:** *value, err = lmdb.get(key, db?, keep_prefix?)*
4141

4242
**context:** *any context **except** init_by_lua**
4343

@@ -46,13 +46,15 @@ it defaults to `"_default"`.
4646

4747
If the key does not exist, `nil` will be returned.
4848

49+
The `keep_prefix` preserves the first 479 bytes of keys longer than 479 bytes, appending the SHA-256 hash to maintain prefix search capability with `lmdb.prefix()` while complying with LMDB's 511-byte key size limit. This transformation occurs specifically when keys exceed 479 bytes. If off, when the key exceeds LMDB's 511-byte key size limit, the entire key is automatically hashed with sha-256 and used as the actual key.
50+
4951
In case of error, `nil` and a string describing the error will be returned instead.
5052

5153
[Back to TOC](#table-of-contents)
5254

5355
#### set
5456

55-
**syntax:** *ok, err = lmdb.set(key, value, db?)*
57+
**syntax:** *ok, err = lmdb.set(key, value, db?, keep_prefix?)*
5658

5759
**context:** *any context **except** init_by_lua**
5860

@@ -61,6 +63,8 @@ it defaults to `"_default"`.
6163

6264
Setting a key's value to `nil` will remove that key from the corresponding database.
6365

66+
The `keep_prefix` preserves the first 479 bytes of keys longer than 479 bytes, appending the SHA-256 hash to maintain prefix search capability with `lmdb.prefix()` while complying with LMDB's 511-byte key size limit. This transformation occurs specifically when keys exceed 479 bytes. If off, when the key exceeds LMDB's 511-byte key size limit, the entire key is automatically hashed with sha-256 and used as the actual key.
67+
6468
In case of error, `nil` and a string describing the error will be returned instead.
6569

6670
[Back to TOC](#table-of-contents)

lib/resty/lmdb.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ local CAN_YIELD_PHASES = {
2626
}
2727

2828

29-
function _M.get(key, db)
29+
function _M.get(key, db, keep_prefix)
3030
CACHED_TXN:reset()
31-
CACHED_TXN:get(key, db)
31+
CACHED_TXN:get(key, db, keep_prefix)
3232
local res, err = CACHED_TXN:commit()
3333
if not res then
3434
return nil, err
@@ -38,9 +38,9 @@ function _M.get(key, db)
3838
end
3939

4040

41-
function _M.set(key, value, db)
41+
function _M.set(key, value, db, keep_prefix)
4242
CACHED_TXN:reset()
43-
CACHED_TXN:set(key, value, db)
43+
CACHED_TXN:set(key, value, db, keep_prefix)
4444
local res, err = CACHED_TXN:commit()
4545
if not res then
4646
return nil, err

lib/resty/lmdb/transaction.lua

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ local get_string_buf = base.get_string_buf
1212
local get_string_buf_size = base.get_string_buf_size
1313
local setmetatable = setmetatable
1414
local assert = assert
15+
local string_sub = string.sub
1516
local math_max = math.max
1617
local type = type
1718
local C = ffi.C
@@ -51,16 +52,26 @@ do
5152

5253
-- lmdb has 511 bytes limitation for key
5354
local MAX_KEY_SIZE = 511
55+
-- when keep prefix, the key size is limited to 479 bytes
56+
local MAX_KEY_SIZE_WHEN_KEEP_PREFIX = 511 - 32 -- it is 479
5457

5558
local sha256 = function(str)
5659
resty_sha256:reset()
5760
resty_sha256:update(str)
5861
return resty_sha256:final()
5962
end
6063

61-
normalize_key = function(key)
62-
if key and #key > MAX_KEY_SIZE then
63-
return assert(sha256(key))
64+
normalize_key = function(key, keep_prefix)
65+
if key then
66+
local key_len = #key
67+
if keep_prefix and key_len > MAX_KEY_SIZE_WHEN_KEEP_PREFIX then
68+
local prefix = string_sub(key, 1, MAX_KEY_SIZE_WHEN_KEEP_PREFIX)
69+
return prefix .. assert(sha256(key))
70+
end
71+
72+
if key_len > MAX_KEY_SIZE then
73+
return assert(sha256(key))
74+
end
6475
end
6576

6677
return key
@@ -114,18 +125,18 @@ function _TXN_MT:_new_op()
114125
end
115126

116127

117-
function _TXN_MT:get(key, db)
128+
function _TXN_MT:get(key, db, keep_prefix)
118129
local op = self:_new_op()
119130
op.opcode = "GET"
120-
op.key = normalize_key(key)
131+
op.key = normalize_key(key, keep_prefix)
121132
op.db = db or DEFAULT_DB
122133
end
123134

124135

125-
function _TXN_MT:set(key, value, db)
136+
function _TXN_MT:set(key, value, db, keep_prefix)
126137
local op = self:_new_op()
127138
op.opcode = "SET"
128-
op.key = normalize_key(key)
139+
op.key = normalize_key(key, keep_prefix)
129140
op.value = value
130141
op.db = db or DEFAULT_DB
131142
op.flags = 0

t/08-max-key-size.t

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,214 @@ value2
149149
[error]
150150
[warn]
151151
[crit]
152+
153+
154+
=== TEST 6: key size is 480 set() / get() with keep_prefix
155+
--- http_config eval: $::HttpConfig
156+
--- main_config eval: $::MainConfig
157+
--- config
158+
location = /t {
159+
content_by_lua_block {
160+
local l = require("resty.lmdb")
161+
162+
local key = string.rep("a", 480)
163+
ngx.say(l.set(key, "value", true))
164+
ngx.say(l.get(key, true))
165+
}
166+
}
167+
--- request
168+
GET /t
169+
--- response_body
170+
true
171+
value
172+
--- no_error_log
173+
[error]
174+
[warn]
175+
[crit]
176+
177+
178+
=== TEST 7: key size is 479 set() / get() with keep_prefix
179+
--- http_config eval: $::HttpConfig
180+
--- main_config eval: $::MainConfig
181+
--- config
182+
location = /t {
183+
content_by_lua_block {
184+
local l = require("resty.lmdb")
185+
186+
local key = string.rep("a", 479)
187+
ngx.say(l.set(key, "value", true))
188+
ngx.say(l.get(key, true))
189+
}
190+
}
191+
--- request
192+
GET /t
193+
--- response_body
194+
true
195+
value
196+
--- no_error_log
197+
[error]
198+
[warn]
199+
[crit]
200+
201+
202+
=== TEST 8: key size is 1024 set() / get() with keep_prefix
203+
--- http_config eval: $::HttpConfig
204+
--- main_config eval: $::MainConfig
205+
--- config
206+
location = /t {
207+
content_by_lua_block {
208+
local l = require("resty.lmdb")
209+
210+
local key = string.rep("a", 1024)
211+
ngx.say(l.set(key, "value", true))
212+
ngx.say(l.get(key, true))
213+
}
214+
}
215+
--- request
216+
GET /t
217+
--- response_body
218+
true
219+
value
220+
--- no_error_log
221+
[error]
222+
[warn]
223+
[crit]
224+
225+
226+
=== TEST 9: key size is 416 set() / get() with keep_prefix
227+
--- http_config eval: $::HttpConfig
228+
--- main_config eval: $::MainConfig
229+
--- config
230+
location = /t {
231+
content_by_lua_block {
232+
local l = require("resty.lmdb")
233+
234+
local key = string.rep("a", 416)
235+
ngx.say(l.set(key, "value", true))
236+
ngx.say(l.get(key, true))
237+
}
238+
}
239+
--- request
240+
GET /t
241+
--- response_body
242+
true
243+
value
244+
--- no_error_log
245+
[error]
246+
[warn]
247+
[crit]
248+
249+
250+
=== TEST 10: different keys are ok with keep_prefix
251+
--- http_config eval: $::HttpConfig
252+
--- main_config eval: $::MainConfig
253+
--- config
254+
location = /t {
255+
content_by_lua_block {
256+
local l = require("resty.lmdb")
257+
258+
local key1 = string.rep("a", 480)
259+
ngx.say(l.set(key1, "value1", true))
260+
ngx.say(l.get(key1, true))
261+
262+
local key2 = string.rep("b", 480)
263+
ngx.say(l.set(key2, "value2", true))
264+
ngx.say(l.get(key2, true))
265+
}
266+
}
267+
--- request
268+
GET /t
269+
--- response_body
270+
true
271+
value1
272+
true
273+
value2
274+
--- no_error_log
275+
[error]
276+
[warn]
277+
[crit]
278+
279+
280+
=== TEST 11: direct get value via normalized key with keep_prefix
281+
--- http_config eval: $::HttpConfig
282+
--- main_config eval: $::MainConfig
283+
--- config
284+
location = /t {
285+
content_by_lua_block {
286+
local l = require("resty.lmdb")
287+
local resty_sha256 = assert(require("resty.sha256").new())
288+
289+
local sha256 = function(str)
290+
resty_sha256:reset()
291+
resty_sha256:update(str)
292+
return resty_sha256:final()
293+
end
294+
295+
local key = string.rep("a", 480)
296+
ngx.say(l.set(key, "value", true))
297+
ngx.say(l.get(key, true))
298+
299+
local normalized_key = string.sub(key, 1, 479) .. assert(sha256(key))
300+
ngx.say(l.get(normalized_key))
301+
}
302+
}
303+
--- request
304+
GET /t
305+
--- response_body
306+
true
307+
value
308+
value
309+
--- no_error_log
310+
[error]
311+
[warn]
312+
[crit]
313+
314+
315+
=== TEST 12: get values via prefix with keep_prefix
316+
--- http_config eval: $::HttpConfig
317+
--- main_config eval: $::MainConfig
318+
--- config
319+
location = /t {
320+
content_by_lua_block {
321+
local l = require("resty.lmdb")
322+
323+
local prefix = string.rep("a", 240)
324+
325+
local key1 = prefix .. string.rep("b", 240)
326+
ngx.say(l.set(key1, "value1", true))
327+
ngx.say(l.get(key1, true))
328+
329+
local key2 = prefix .. string.rep("c", 240)
330+
ngx.say(l.set(key2, "value2", true))
331+
ngx.say(l.get(key2, true))
332+
333+
local key3 = string.rep("d", 480)
334+
ngx.say(l.set(key3, "value3", true))
335+
ngx.say(l.get(key3, true))
336+
337+
local key4 = string.rep("a", 239)
338+
ngx.say(l.set(key4, "value4", true))
339+
ngx.say(l.get(key4, true))
340+
341+
for k, v in l.prefix(prefix) do
342+
ngx.say(value: ", v)
343+
end
344+
}
345+
}
346+
--- request
347+
GET /t
348+
--- response_body
349+
true
350+
value1
351+
true
352+
value2
353+
true
354+
value3
355+
true
356+
value4
357+
value: value1
358+
value: value2
359+
--- no_error_log
360+
[error]
361+
[warn]
362+
[crit]

0 commit comments

Comments
 (0)