Skip to content

Commit c0bbc0c

Browse files
authored
Merge branch 'development-v2' into development-v2-rscan-hotfix
2 parents 946f6a3 + 4965161 commit c0bbc0c

File tree

14 files changed

+456
-31
lines changed

14 files changed

+456
-31
lines changed

README.md

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -174,37 +174,41 @@ This mode is not possible if you don't have any data/index already available.
174174

175175
# Supported commands
176176
- `PING`
177-
- `SET key value [timestamp]`
178-
- `GET key`
179-
- `MGET key`
180-
- `DEL key`
177+
- `SET <key> <value> [timestamp]`
178+
- `GET <key>`
179+
- `MGET <key> [key ...]`
180+
- `DEL <key>`
181181
- `STOP` (used only for debugging, to check memory leaks)
182-
- `EXISTS key`
183-
- `CHECK key`
184-
- `KEYCUR key`
182+
- `EXISTS <key>`
183+
- `CHECK <key>`
184+
- `KEYCUR <key>`
185185
- `INFO`
186-
- `NSNEW namespace`
187-
- `NSDEL namespace`
188-
- `NSINFO namespace`
186+
- `NSNEW <namespace>`
187+
- `NSDEL <namespace>`
188+
- `NSINFO <namespace>`
189189
- `NSLIST`
190-
- `NSSET namespace property value`
190+
- `NSSET <namespace> <property> <value>`
191191
- `NSJUMP`
192-
- `SELECT namespace [SECURE password]`
192+
- `SELECT <namespace> [SECURE password]`
193193
- `DBSIZE`
194194
- `TIME`
195-
- `AUTH password`
196-
- `AUTH SECURE`
197-
- `SCAN [optional cursor]`
198-
- `SCANX [optional cursor]` (this is just an alias for `SCAN`)
199-
- `RSCAN [optional cursor]`
195+
- `AUTH [password]`
196+
- `AUTH SECURE [password]`
197+
- `SCAN [cursor]`
198+
- `RSCAN [cursor]`
200199
- `WAIT command | * [timeout-ms]`
201-
- `HISTORY key [binary-data]`
200+
- `HISTORY <key> [binary-data]`
202201
- `FLUSH`
203202
- `HOOKS`
204203
- `INDEX DIRTY [RESET]`
204+
- `DATA RAW <fileid> <offset>`
205+
- `LENGTH <key>`
206+
- `KEYTIME <key>`
205207

206208
`SET`, `GET` and `DEL`, `SCAN` and `RSCAN` supports binary keys.
207209

210+
Arguments `<flags>` are mandatory, arguments `[flags]` are optionals.
211+
208212
> Compared to real redis protocol, during a `SET`, the key is returned as response.
209213
210214
## SET
@@ -513,6 +517,59 @@ List the current namespace index files id which were modified since last reset
513517
### INDEX DIRTY RESET
514518
Reset the dirty list
515519

520+
## DATA
521+
522+
This command have small internal operation on raw data file.
523+
524+
### DATA RAW
525+
526+
An internal call allows to retreive a raw data object from database only based on fileid and offset.
527+
This method of access should only be made by a valid fileid and offset, some protection are in
528+
place to avoid issues on wrong offset but not fully tested yet.
529+
530+
This command (only for admin) returns an array with the object requested:
531+
1. Key
532+
2. Previous Offset
533+
3. Integrity CRC32
534+
4. Flags
535+
5. Timestamp
536+
6. Payload
537+
538+
In addition with NSINFO, this command can be used to query a database based on fielid/offset
539+
from another database used eg, for replication.
540+
541+
You can use fields `data_current_id` and `data_current_offset` from `NSINFO` to query valid offsets.
542+
543+
## LENGTH
544+
545+
Returns payload size of a key or `(nil)` if not found.
546+
547+
```
548+
> SET hello world
549+
"hello"
550+
551+
> LENGTH hello
552+
(integer) 5
553+
554+
> LENGTH notfound
555+
(nil)
556+
```
557+
558+
## KEYTIME
559+
560+
Return last-update timestamp of a key or `(nil)` if not found.
561+
562+
```
563+
> SET hello world
564+
"hello"
565+
566+
> KEYTIME hello
567+
(integer) 1664996517
568+
569+
> KEYTIME notfound
570+
(nil)
571+
```
572+
516573
# Namespaces
517574
A namespace is a dedicated directory on index and data root directory.
518575
A namespace is a complete set of key/data. Each namespace can be optionally protected by a password

libzdb/api.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,10 @@ zdb_api_t *zdb_api_del(namespace_t *ns, void *key, size_t ksize) {
401401
return zdb_api_reply(ZDB_API_DELETED, NULL);
402402
}
403403

404+
time_t timestamp = time(NULL);
405+
404406
// update data file, flag entry deleted
405-
if(!data_delete(ns->data, entry->id, entry->idlength)) {
407+
if(!data_delete(ns->data, entry->id, entry->idlength, timestamp)) {
406408
zdb_debug("[-] api: del: deleting data failed\n");
407409
return zdb_api_reply(ZDB_API_INTERNAL_ERROR, NULL);
408410
}

libzdb/data.c

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,86 @@ static void data_open_final(data_root_t *root) {
305305
zdb_verbose("[+] data: active file: %s\n", root->datafile);
306306
}
307307

308+
data_raw_t data_raw_get_real(int fd, off_t offset) {
309+
data_raw_t raw;
310+
311+
memset(&raw, 0x00, sizeof(raw));
312+
313+
// moving to the header offset
314+
lseek(fd, offset, SEEK_SET);
315+
316+
// 1. fetch fixed header object
317+
// 2. that fixed header first byte contains id length
318+
// 3. fetch id with correct length (from 2)
319+
// 4. fetch payload with correct length (from 2)
320+
// 5. TODO check checksum ?
321+
322+
zdb_debug("[+] data: raw: fetching header from offset\n");
323+
if(read(fd, &raw.header, sizeof(data_entry_header_t)) != sizeof(data_entry_header_t)) {
324+
zdb_warnp("data: raw: incorrect data header read");
325+
return raw;
326+
}
327+
328+
if(raw.header.datalength > ZDB_DATA_MAX_PAYLOAD) {
329+
zdb_verbose("[-] data: raw: datalength from header too large\n");
330+
return raw;
331+
}
332+
333+
zdb_debug("[+] data: raw: fetching id from header offset\n");
334+
if(!(raw.id = malloc(raw.header.idlength))) {
335+
zdb_warnp("data: raw: id: malloc");
336+
return raw;
337+
}
338+
339+
if(read(fd, raw.id, raw.header.idlength) != raw.header.idlength) {
340+
zdb_warnp("data: raw: incorrect id read");
341+
return raw;
342+
}
343+
344+
if(raw.header.flags & DATA_ENTRY_DELETED) {
345+
zdb_debug("[+] data: raw: entry deleted\n");
346+
return raw;
347+
}
348+
349+
zdb_debug("[+] data: raw: fetching payload from header offset\n");
350+
if(!(raw.payload.buffer = malloc(raw.header.datalength))) {
351+
zdb_warnp("data: raw: payload: malloc");
352+
return raw;
353+
}
354+
355+
if(read(fd, raw.payload.buffer, raw.header.datalength) != raw.header.datalength) {
356+
zdb_warnp("data: raw: incorrect payload read");
357+
return raw;
358+
}
359+
360+
// this validate return object to be valid
361+
raw.payload.length = raw.header.datalength;
362+
363+
return raw;
364+
}
365+
366+
// fetch data full object from specific offset
367+
data_raw_t data_raw_get(data_root_t *root, fileid_t dataid, off_t offset) {
368+
int fd;
369+
data_raw_t raw;
370+
371+
// initialize everything
372+
memset(&raw, 0x00, sizeof(raw));
373+
374+
zdb_debug("[+] data: raw request: id %u, offset %lu\n", dataid, offset);
375+
376+
// acquire data id fd
377+
if((fd = data_grab_dataid(root, dataid)) < 0)
378+
return raw;
379+
380+
raw = data_raw_get_real(fd, offset);
381+
382+
// release dataid
383+
data_release_dataid(root, dataid, fd);
384+
385+
return raw;
386+
}
387+
308388
// jumping to the next id close the current data file
309389
// and open the next id file, it will create the new file
310390
size_t data_jump_next(data_root_t *root, fileid_t newid) {
@@ -547,7 +627,7 @@ int data_entry_is_deleted(data_entry_header_t *entry) {
547627
// was deleted
548628
// this is needed in order to rebuild an index from data file and
549629
// for compaction process
550-
int data_delete(data_root_t *root, void *id, uint8_t idlength) {
630+
int data_delete(data_root_t *root, void *id, uint8_t idlength, time_t timestamp) {
551631
unsigned char *empty = (unsigned char *) "";
552632

553633
data_request_t dreq = {
@@ -557,6 +637,7 @@ int data_delete(data_root_t *root, void *id, uint8_t idlength) {
557637
.idlength = idlength,
558638
.flags = DATA_ENTRY_DELETED,
559639
.crc = 0,
640+
.timestamp = timestamp,
560641
};
561642

562643
zdb_debug("[+] data: delete: insert empty flagged data\n");

libzdb/data.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
// split datafile after 256 MB
55
#define ZDB_DEFAULT_DATA_MAXSIZE 256 * 1024 * 1024
6+
#define ZDB_DATA_MAX_PAYLOAD 8 * 1024 * 1024
67

78
// data statistics
89
typedef struct data_stats_t {
@@ -73,6 +74,13 @@
7374

7475
} data_payload_t;
7576

77+
typedef struct data_raw_t {
78+
data_entry_header_t header;
79+
uint8_t *id;
80+
data_payload_t payload;
81+
82+
} data_raw_t;
83+
7684
// scan internal representation
7785
// we use a status and a pointer to the header
7886
// in order to know what to do
@@ -124,12 +132,13 @@
124132
fileid_t data_dataid(data_root_t *root);
125133
void data_delete_files(char *datadir);
126134

135+
data_raw_t data_raw_get(data_root_t *root, fileid_t dataid, off_t offset);
127136
data_payload_t data_get(data_root_t *root, size_t offset, size_t length, fileid_t dataid, uint8_t idlength);
128137
int data_check(data_root_t *root, size_t offset, fileid_t dataid);
129138

130139
// size_t data_match(data_root_t *root, void *id, uint8_t idlength, size_t offset, fileid_t dataid);
131140

132-
int data_delete(data_root_t *root, void *id, uint8_t idlength);
141+
int data_delete(data_root_t *root, void *id, uint8_t idlength, time_t timestamp);
133142

134143
// size_t data_insert(data_root_t *root, unsigned char *data, uint32_t datalength, void *vid, uint8_t idlength, uint8_t flags);
135144
size_t data_insert(data_root_t *root, data_request_t *source);

libzdb/index_loader.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ static size_t index_load_file(index_root_t *root) {
331331
.offset = entry->offset,
332332
.flags = entry->flags,
333333
.idxoffset = offset,
334+
.timestamp = entry->timestamp,
334335
.crc = entry->crc,
335336
.parentid = entry->parentid,
336337
.parentoff = entry->parentoff,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import redis
2+
import time
3+
4+
class ZDBIncremental:
5+
def __init__(self, master, slave):
6+
self.master = redis.Redis(unix_socket_path="/tmp/zdb.sock")
7+
self.slave = redis.Redis(port=9900)
8+
9+
# disable defaults callbacks
10+
for target in [self.master, self.slave]:
11+
target.set_response_callback("NSINFO", redis.client.parse_info)
12+
target.set_response_callback("DEL", redis.client.bool_ok)
13+
target.set_response_callback("SET", bytes)
14+
15+
def sync(self, master, slave):
16+
raw = self.master.execute_command("DATA", "RAW", slave['dataid'], slave['offset'])
17+
18+
print(raw)
19+
20+
if raw[3] == 0:
21+
# SET
22+
response = self.slave.execute_command("SET", raw[0], raw[5], raw[4])
23+
if response != raw[0]:
24+
raise RuntimeError(f"incorrect set {response}")
25+
26+
else:
27+
# DEL
28+
self.slave.execute_command("DEL", raw[0], raw[4])
29+
30+
31+
def run(self):
32+
namespace = "default"
33+
34+
while True:
35+
master = {}
36+
slave = {}
37+
38+
nsmaster = self.master.execute_command("NSINFO", namespace)
39+
master['dataid'] = int(nsmaster['data_current_id'])
40+
master['offset'] = int(nsmaster['data_current_offset'])
41+
42+
nsslave = self.slave.execute_command("NSINFO", namespace)
43+
slave['dataid'] = int(nsslave['data_current_id'])
44+
slave['offset'] = int(nsslave['data_current_offset'])
45+
46+
print("master: %d:%d" % (master['dataid'], master['offset']))
47+
print("slave: %d:%d" % (slave['dataid'], slave['offset']))
48+
49+
if slave['dataid'] > master['dataid']:
50+
raise RuntimeError("slave ahead from master")
51+
52+
if master['dataid'] == slave['dataid']:
53+
if master['offset'] == slave['offset']:
54+
print("sync, waiting")
55+
time.sleep(10)
56+
continue
57+
58+
self.sync(master, slave)
59+
60+
61+
if __name__ == '__main__':
62+
incremental = ZDBIncremental("hello", "world")
63+
incremental.run()

zdbd/commands.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ static command_t commands_handlers[] = {
121121
{.command = "AUTH", .handler = command_auth}, // custom AUTH command to authentifcate admin
122122
{.command = "HOOKS", .handler = command_hooks}, // custom HOOKS command to list running hooks
123123
{.command = "INDEX", .handler = command_index}, // custom INDEX command to query internal index
124+
{.command = "DATA", .handler = command_data}, // custom DATA command to query internal data
124125

125126
// dataset
126127
{.command = "SET", .handler = command_set}, // default SET command
@@ -136,6 +137,8 @@ static command_t commands_handlers[] = {
136137
{.command = "KSCAN", .handler = command_kscan}, // custom command to iterate over keys matching pattern
137138
{.command = "HISTORY", .handler = command_history}, // custom command to get previous version of a key
138139
{.command = "KEYCUR", .handler = command_keycur}, // custom command to get cursor id from a key
140+
{.command = "LENGTH", .handler = command_length}, // custom command to get value length of a key
141+
{.command = "KEYTIME", .handler = command_keytime}, // custom command to get last updated timestamp of a key
139142

140143
// query
141144
{.command = "INFO", .handler = command_info}, // returns 0-db server name

zdbd/commands.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@
2121

2222
int command_error_locked(redis_client_t *client);
2323
int command_error_frozen(redis_client_t *client);
24+
25+
// defined in commands_set.c
26+
// used by commands_set.c and commands_dataset.c
27+
time_t timestamp_from_set(resp_request_t *request, int field);
2428
#endif

0 commit comments

Comments
 (0)