pixl-server-storage 4.1.0 → 4.1.1
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.
- package/README.md +17 -2
- package/engines/Filesystem.js +59 -27
- package/engines/Postgres.js +60 -20
- package/engines/Redis.js +129 -28
- package/engines/RedisCluster.js +128 -28
- package/engines/S3.js +53 -25
- package/engines/SQLite.js +60 -20
- package/package.json +1 -1
- package/test/data/backups/backup-2026-06-08-20-33-01.db.gz +0 -0
- package/test/data/backups/backup-2026-02-15-18-10-33.db.gz +0 -0
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ Here is the table of contents for this current document:
|
|
|
58
58
|
* [Redis](#redis)
|
|
59
59
|
+ [RedisCluster](#rediscluster)
|
|
60
60
|
* [SQLite](#sqlite)
|
|
61
|
+
* [Postgres](#postgres)
|
|
61
62
|
* [Hybrid](#hybrid)
|
|
62
63
|
- [Key Normalization](#key-normalization)
|
|
63
64
|
- [Basic Functions](#basic-functions)
|
|
@@ -589,7 +590,12 @@ Then configure your storage thusly:
|
|
|
589
590
|
"host": "127.0.0.1",
|
|
590
591
|
"port": 6379,
|
|
591
592
|
"keyPrefix": "",
|
|
592
|
-
"keyTemplate": ""
|
|
593
|
+
"keyTemplate": "",
|
|
594
|
+
"cache": {
|
|
595
|
+
"enabled": true,
|
|
596
|
+
"maxItems": 100000,
|
|
597
|
+
"maxBytes": 104857600
|
|
598
|
+
}
|
|
593
599
|
}
|
|
594
600
|
}
|
|
595
601
|
```
|
|
@@ -600,6 +606,8 @@ The optional `keyPrefix` property works similarly to the [S3 Key Prefix](#s3-key
|
|
|
600
606
|
|
|
601
607
|
The optional `keyTemplate` property works similarly to the [S3 Key Template](#s3-key-template) feature. It allows you to specify an exact layout of MD5 hash characters, which can be prefixed, mixed in with or postfixed after the key.
|
|
602
608
|
|
|
609
|
+
See [S3 Cache](#s3-cache) for details on the `cache` section, as it works in the same way.
|
|
610
|
+
|
|
603
611
|
### RedisCluster
|
|
604
612
|
|
|
605
613
|
If you want to use a Redis cluster (e.g. [AWS ElastiCache](https://aws.amazon.com/elasticache/)), then here is how to do that. First, you will need to manually install the following module into your app:
|
|
@@ -625,7 +633,12 @@ Then configure your storage thusly:
|
|
|
625
633
|
}
|
|
626
634
|
},
|
|
627
635
|
"keyPrefix": "",
|
|
628
|
-
"keyTemplate": ""
|
|
636
|
+
"keyTemplate": "",
|
|
637
|
+
"cache": {
|
|
638
|
+
"enabled": true,
|
|
639
|
+
"maxItems": 100000,
|
|
640
|
+
"maxBytes": 104857600
|
|
641
|
+
}
|
|
629
642
|
}
|
|
630
643
|
}
|
|
631
644
|
```
|
|
@@ -640,6 +653,8 @@ The optional `keyPrefix` property works similarly to the [S3 Key Prefix](#s3-key
|
|
|
640
653
|
|
|
641
654
|
The optional `keyTemplate` property works similarly to the [S3 Key Template](#s3-key-template) feature. It allows you to specify an exact layout of MD5 hash characters, which can be prefixed, mixed in with or postfixed after the key.
|
|
642
655
|
|
|
656
|
+
See [S3 Cache](#s3-cache) for details on the `cache` section, as it works in the same way.
|
|
657
|
+
|
|
643
658
|
## SQLite
|
|
644
659
|
|
|
645
660
|
If you want to use [SQLite](https://sqlite.com/) as a backing store, here is how to do so. First, you need to manually install the [better-sqlite3](https://www.npmjs.com/package/better-sqlite3) module into your app:
|
package/engines/Filesystem.js
CHANGED
|
@@ -215,7 +215,7 @@ module.exports = Class.create({
|
|
|
215
215
|
}
|
|
216
216
|
else {
|
|
217
217
|
this.logDebug(9, "Storing JSON Object: " + key, this.debugLevel(10) ? value : file);
|
|
218
|
-
value = this.pretty ? JSON.stringify( value, null, "\t" ) : JSON.stringify( value );
|
|
218
|
+
value = Buffer.from( this.pretty ? JSON.stringify( value, null, "\t" ) : JSON.stringify( value ) );
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
var dir = path.dirname( file );
|
|
@@ -378,13 +378,22 @@ module.exports = Class.create({
|
|
|
378
378
|
if (this.cache && this.cache.has(key)) {
|
|
379
379
|
var item = this.cache.getMeta(key);
|
|
380
380
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
381
|
+
if (item.value.length == 0) {
|
|
382
|
+
process.nextTick( function() {
|
|
383
|
+
var err = new Error("Failed to head key: " + key + ": File not found");
|
|
384
|
+
err.code = "NoSuchKey";
|
|
385
|
+
callback( err );
|
|
386
386
|
} );
|
|
387
|
-
}
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
process.nextTick( function() {
|
|
390
|
+
self.logDebug(9, "Cached head complete: " + key);
|
|
391
|
+
callback( null, {
|
|
392
|
+
mod: item.date,
|
|
393
|
+
len: item.value.length
|
|
394
|
+
} );
|
|
395
|
+
} );
|
|
396
|
+
}
|
|
388
397
|
return;
|
|
389
398
|
} // cache
|
|
390
399
|
|
|
@@ -393,6 +402,11 @@ module.exports = Class.create({
|
|
|
393
402
|
if (err.message.match(/ENOENT/)) {
|
|
394
403
|
err.message = "File not found";
|
|
395
404
|
err.code = "NoSuchKey";
|
|
405
|
+
|
|
406
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
407
|
+
// store 'empty' stub in cache
|
|
408
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
409
|
+
}
|
|
396
410
|
}
|
|
397
411
|
else {
|
|
398
412
|
// log fs errors that aren't simple missing files (i.e. I/O errors)
|
|
@@ -421,30 +435,42 @@ module.exports = Class.create({
|
|
|
421
435
|
|
|
422
436
|
// check cache first
|
|
423
437
|
if (this.cache && !is_binary && this.cache.has(key)) {
|
|
424
|
-
var
|
|
438
|
+
var item = this.cache.getMeta(key);
|
|
425
439
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
callback(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
440
|
+
if (item.value.length == 0) {
|
|
441
|
+
process.nextTick( function() {
|
|
442
|
+
var err = new Error("Failed to fetch key: " + key + ": File not found");
|
|
443
|
+
err.code = "NoSuchKey";
|
|
444
|
+
callback( err );
|
|
445
|
+
} );
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
process.nextTick( function() {
|
|
449
|
+
var data = null;
|
|
450
|
+
try { data = JSON.parse( item.value.toString() ); }
|
|
451
|
+
catch (e) {
|
|
452
|
+
self.logError('file', "Failed to parse JSON record: " + key + ": " + e);
|
|
453
|
+
callback( e, null );
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
|
|
457
|
+
|
|
458
|
+
callback( null, data );
|
|
459
|
+
} );
|
|
460
|
+
}
|
|
437
461
|
return;
|
|
438
462
|
} // cache
|
|
439
463
|
|
|
440
|
-
|
|
441
|
-
if (!this.storage.isBinaryKey(key)) opts = { encoding: 'utf8' };
|
|
442
|
-
|
|
443
|
-
fs.readFile(file, opts, function (err, data) {
|
|
464
|
+
fs.readFile(file, function (err, data) {
|
|
444
465
|
if (err) {
|
|
445
466
|
if (err.message.match(/ENOENT/)) {
|
|
446
467
|
err.message = "File not found";
|
|
447
468
|
err.code = "NoSuchKey";
|
|
469
|
+
|
|
470
|
+
if (self.cache && !is_binary) {
|
|
471
|
+
// store 'empty' stub in cache
|
|
472
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
473
|
+
}
|
|
448
474
|
}
|
|
449
475
|
else {
|
|
450
476
|
// log fs errors that aren't simple missing files (i.e. I/O errors)
|
|
@@ -461,7 +487,7 @@ module.exports = Class.create({
|
|
|
461
487
|
}
|
|
462
488
|
|
|
463
489
|
if (!is_binary) {
|
|
464
|
-
try { data = JSON.parse( data ); }
|
|
490
|
+
try { data = JSON.parse( data.toString() ); }
|
|
465
491
|
catch (e) {
|
|
466
492
|
self.logError('file', "Failed to parse JSON record: " + key + ": " + e);
|
|
467
493
|
callback( e, null );
|
|
@@ -595,6 +621,11 @@ module.exports = Class.create({
|
|
|
595
621
|
if (err.message.match(/ENOENT/)) {
|
|
596
622
|
err.message = "File not found";
|
|
597
623
|
err.code = "NoSuchKey";
|
|
624
|
+
|
|
625
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
626
|
+
// store 'empty' stub in cache
|
|
627
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
628
|
+
}
|
|
598
629
|
}
|
|
599
630
|
|
|
600
631
|
self.logError('file', "Failed to delete file: " + key + ": " + file + ": " + err.message);
|
|
@@ -605,9 +636,10 @@ module.exports = Class.create({
|
|
|
605
636
|
else {
|
|
606
637
|
self.logDebug(9, "Delete complete: " + key);
|
|
607
638
|
|
|
608
|
-
// possibly delete from LRU cache as well
|
|
609
|
-
if (self.cache && self.
|
|
610
|
-
|
|
639
|
+
// possibly "delete" from LRU cache as well
|
|
640
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
641
|
+
// store 'empty' stub in cache
|
|
642
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
611
643
|
}
|
|
612
644
|
|
|
613
645
|
// cleanup parent dirs if empty
|
package/engines/Postgres.js
CHANGED
|
@@ -204,13 +204,22 @@ module.exports = Class.create({
|
|
|
204
204
|
if (this.cache && this.cache.has(key)) {
|
|
205
205
|
var item = this.cache.getMeta(key);
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
207
|
+
if (item.value.length == 0) {
|
|
208
|
+
process.nextTick( function() {
|
|
209
|
+
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
210
|
+
err.code = "NoSuchKey";
|
|
211
|
+
callback( err );
|
|
212
212
|
} );
|
|
213
|
-
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
process.nextTick( function() {
|
|
216
|
+
self.logDebug(9, "Cached head complete: " + key);
|
|
217
|
+
callback( null, {
|
|
218
|
+
mod: item.date,
|
|
219
|
+
len: item.value.length
|
|
220
|
+
} );
|
|
221
|
+
} );
|
|
222
|
+
}
|
|
214
223
|
return;
|
|
215
224
|
} // cache
|
|
216
225
|
|
|
@@ -227,6 +236,12 @@ module.exports = Class.create({
|
|
|
227
236
|
if (!row) {
|
|
228
237
|
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
229
238
|
err.code = "NoSuchKey";
|
|
239
|
+
|
|
240
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
241
|
+
// store 'empty' stub in cache
|
|
242
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
243
|
+
}
|
|
244
|
+
|
|
230
245
|
return callback(err, null);
|
|
231
246
|
}
|
|
232
247
|
callback(null, { mod: parseInt(row.modified, 10), len: row.len });
|
|
@@ -242,19 +257,29 @@ module.exports = Class.create({
|
|
|
242
257
|
|
|
243
258
|
// check cache first
|
|
244
259
|
if (this.cache && !is_binary && this.cache.has(key)) {
|
|
245
|
-
var
|
|
260
|
+
var item = this.cache.getMeta(key);
|
|
246
261
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
callback(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
if (item.value.length == 0) {
|
|
263
|
+
process.nextTick( function() {
|
|
264
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
265
|
+
err.code = "NoSuchKey";
|
|
266
|
+
callback( err );
|
|
267
|
+
} );
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
process.nextTick( function() {
|
|
271
|
+
var data = null;
|
|
272
|
+
try { data = JSON.parse( item.value.toString() ); }
|
|
273
|
+
catch (e) {
|
|
274
|
+
self.logError('pg', "Failed to parse JSON record: " + key + ": " + e);
|
|
275
|
+
callback( e, null );
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
|
|
279
|
+
|
|
280
|
+
callback( null, data );
|
|
281
|
+
} );
|
|
282
|
+
}
|
|
258
283
|
return;
|
|
259
284
|
} // cache
|
|
260
285
|
|
|
@@ -273,6 +298,12 @@ module.exports = Class.create({
|
|
|
273
298
|
if (!row) {
|
|
274
299
|
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
275
300
|
err.code = "NoSuchKey";
|
|
301
|
+
|
|
302
|
+
if (self.cache && !is_binary) {
|
|
303
|
+
// store 'empty' stub in cache
|
|
304
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
305
|
+
}
|
|
306
|
+
|
|
276
307
|
return callback(err, null);
|
|
277
308
|
}
|
|
278
309
|
if (is_binary) {
|
|
@@ -412,11 +443,20 @@ module.exports = Class.create({
|
|
|
412
443
|
if (!res.rowCount) {
|
|
413
444
|
var err = new Error("Failed to delete object: " + key + ": Not found");
|
|
414
445
|
err.code = "NoSuchKey";
|
|
446
|
+
|
|
447
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
448
|
+
// store 'empty' stub in cache
|
|
449
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
450
|
+
}
|
|
451
|
+
|
|
415
452
|
return callback(err);
|
|
416
453
|
}
|
|
417
454
|
self.logDebug(9, "Delete complete: " + key);
|
|
418
|
-
|
|
419
|
-
|
|
455
|
+
|
|
456
|
+
// possibly "delete" from LRU cache as well
|
|
457
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
458
|
+
// store 'empty' stub in cache
|
|
459
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
420
460
|
}
|
|
421
461
|
callback();
|
|
422
462
|
} ); // query
|
package/engines/Redis.js
CHANGED
|
@@ -9,6 +9,7 @@ const Class = require("pixl-class");
|
|
|
9
9
|
const Component = require("pixl-server/component");
|
|
10
10
|
const Redis = require('ioredis');
|
|
11
11
|
const Tools = require("pixl-tools");
|
|
12
|
+
const Cache = require("pixl-cache");
|
|
12
13
|
|
|
13
14
|
module.exports = Class.create({
|
|
14
15
|
|
|
@@ -47,6 +48,23 @@ module.exports = Class.create({
|
|
|
47
48
|
this.keyTemplate = (r_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
|
|
48
49
|
delete r_config.keyTemplate;
|
|
49
50
|
|
|
51
|
+
// optional LRU cache
|
|
52
|
+
this.cache = null;
|
|
53
|
+
var cache_opts = r_config.cache;
|
|
54
|
+
if (cache_opts && cache_opts.enabled) {
|
|
55
|
+
this.logDebug(3, "Setting up LRU cache", cache_opts);
|
|
56
|
+
this.cache = new Cache( Tools.copyHashRemoveKeys(cache_opts, { enabled: 1 }) );
|
|
57
|
+
this.cache.on('expire', function(item, reason) {
|
|
58
|
+
self.logDebug(9, "Expiring LRU cache object: " + item.key + " due to: " + reason, {
|
|
59
|
+
key: item.key,
|
|
60
|
+
reason: reason,
|
|
61
|
+
totalCount: self.cache.count,
|
|
62
|
+
totalBytes: self.cache.bytes
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
delete r_config.cache;
|
|
67
|
+
|
|
50
68
|
if (!r_config.username.length) delete r_config.username;
|
|
51
69
|
if (!r_config.password.length) delete r_config.password;
|
|
52
70
|
|
|
@@ -93,13 +111,14 @@ module.exports = Class.create({
|
|
|
93
111
|
// store key+value in Redis
|
|
94
112
|
var self = this;
|
|
95
113
|
key = this.prepKey(key);
|
|
114
|
+
var is_binary = this.storage.isBinaryKey(key);
|
|
96
115
|
|
|
97
|
-
if (
|
|
116
|
+
if (is_binary) {
|
|
98
117
|
this.logDebug(9, "Storing Redis Binary Object: " + key, '' + value.length + ' bytes');
|
|
99
118
|
}
|
|
100
119
|
else {
|
|
101
120
|
this.logDebug(9, "Storing Redis JSON Object: " + key, this.debugLevel(10) ? value : null);
|
|
102
|
-
value = JSON.stringify( value );
|
|
121
|
+
value = Buffer.from( JSON.stringify( value ) );
|
|
103
122
|
}
|
|
104
123
|
|
|
105
124
|
this.redis.set( key, value, function(err) {
|
|
@@ -109,6 +128,11 @@ module.exports = Class.create({
|
|
|
109
128
|
}
|
|
110
129
|
else self.logDebug(9, "Store complete: " + key);
|
|
111
130
|
|
|
131
|
+
// possibly cache in LRU
|
|
132
|
+
if (!err && self.cache && !is_binary) {
|
|
133
|
+
self.cache.set( key, value, { date: Tools.timeNow(true) } );
|
|
134
|
+
}
|
|
135
|
+
|
|
112
136
|
if (callback) callback(err);
|
|
113
137
|
} );
|
|
114
138
|
},
|
|
@@ -138,7 +162,30 @@ module.exports = Class.create({
|
|
|
138
162
|
// The Redis API has no way to head / ping an object.
|
|
139
163
|
// So, we have to do this the RAM-hard way...
|
|
140
164
|
|
|
141
|
-
|
|
165
|
+
// check cache first
|
|
166
|
+
if (this.cache && this.cache.has(key)) {
|
|
167
|
+
var item = this.cache.getMeta(key);
|
|
168
|
+
|
|
169
|
+
if (item.value.length == 0) {
|
|
170
|
+
process.nextTick( function() {
|
|
171
|
+
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
172
|
+
err.code = "NoSuchKey";
|
|
173
|
+
callback( err );
|
|
174
|
+
} );
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
process.nextTick( function() {
|
|
178
|
+
self.logDebug(9, "Cached head complete: " + key);
|
|
179
|
+
callback( null, {
|
|
180
|
+
mod: item.date,
|
|
181
|
+
len: item.value.length
|
|
182
|
+
} );
|
|
183
|
+
} );
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
} // cache
|
|
187
|
+
|
|
188
|
+
this.redis.getBuffer( key, function(err, data) {
|
|
142
189
|
if (err) {
|
|
143
190
|
// an actual error
|
|
144
191
|
err.message = "Failed to head key: " + key + ": " + err;
|
|
@@ -150,7 +197,12 @@ module.exports = Class.create({
|
|
|
150
197
|
// always use "NoSuchKey" in error code
|
|
151
198
|
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
152
199
|
err.code = "NoSuchKey";
|
|
153
|
-
|
|
200
|
+
|
|
201
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
202
|
+
// store 'empty' stub in cache
|
|
203
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
204
|
+
}
|
|
205
|
+
|
|
154
206
|
callback( err, null );
|
|
155
207
|
}
|
|
156
208
|
else {
|
|
@@ -163,44 +215,79 @@ module.exports = Class.create({
|
|
|
163
215
|
// fetch Redis value given key
|
|
164
216
|
var self = this;
|
|
165
217
|
key = this.prepKey(key);
|
|
218
|
+
var is_binary = this.storage.isBinaryKey(key);
|
|
166
219
|
|
|
167
220
|
this.logDebug(9, "Fetching Redis Object: " + key);
|
|
168
221
|
|
|
169
|
-
|
|
170
|
-
this.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
self.logError('redis', ''+err);
|
|
176
|
-
callback( err, null );
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
// record not found
|
|
180
|
-
// always use "NoSuchKey" in error code
|
|
222
|
+
// check cache first
|
|
223
|
+
if (this.cache && !is_binary && this.cache.has(key)) {
|
|
224
|
+
var item = this.cache.getMeta(key);
|
|
225
|
+
|
|
226
|
+
if (item.value.length == 0) {
|
|
227
|
+
process.nextTick( function() {
|
|
181
228
|
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
182
229
|
err.code = "NoSuchKey";
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
230
|
+
callback( err );
|
|
231
|
+
} );
|
|
186
232
|
}
|
|
187
233
|
else {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
else {
|
|
192
|
-
try { result = JSON.parse( result.toString() ); }
|
|
234
|
+
process.nextTick( function() {
|
|
235
|
+
var data = null;
|
|
236
|
+
try { data = JSON.parse( item.value.toString() ); }
|
|
193
237
|
catch (err) {
|
|
194
238
|
self.logError('redis', "Failed to parse JSON record: " + key + ": " + err);
|
|
195
239
|
callback( err, null );
|
|
196
240
|
return;
|
|
197
241
|
}
|
|
198
|
-
self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ?
|
|
242
|
+
self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
|
|
243
|
+
|
|
244
|
+
callback( null, data );
|
|
245
|
+
} );
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
} // cache
|
|
249
|
+
|
|
250
|
+
this.redis.getBuffer( key, function(err, result) {
|
|
251
|
+
if (err) {
|
|
252
|
+
// an actual error
|
|
253
|
+
err.message = "Failed to fetch key: " + key + ": " + err;
|
|
254
|
+
self.logError('redis', ''+err);
|
|
255
|
+
return callback( err, null );
|
|
256
|
+
}
|
|
257
|
+
else if (!result || !result.length) {
|
|
258
|
+
// record not found
|
|
259
|
+
// always use "NoSuchKey" in error code
|
|
260
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
261
|
+
err.code = "NoSuchKey";
|
|
262
|
+
|
|
263
|
+
if (self.cache && !is_binary) {
|
|
264
|
+
// store 'empty' stub in cache
|
|
265
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
199
266
|
}
|
|
200
267
|
|
|
201
|
-
callback(
|
|
268
|
+
return callback( err, null );
|
|
202
269
|
}
|
|
203
|
-
|
|
270
|
+
|
|
271
|
+
if (is_binary) {
|
|
272
|
+
self.logDebug(9, "Binary fetch complete: " + key, '' + result.length + ' bytes');
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// possibly cache in LRU
|
|
276
|
+
if (self.cache) {
|
|
277
|
+
self.cache.set( key, result, { date: Tools.timeNow(true) } );
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
try { result = JSON.parse( result.toString() ); }
|
|
281
|
+
catch (err) {
|
|
282
|
+
self.logError('redis', "Failed to parse JSON record: " + key + ": " + err);
|
|
283
|
+
callback( err, null );
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? result : null);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
callback( null, result );
|
|
290
|
+
} ); // redis.getBuffer
|
|
204
291
|
},
|
|
205
292
|
|
|
206
293
|
getBuffer: function(key, callback) {
|
|
@@ -303,6 +390,7 @@ module.exports = Class.create({
|
|
|
303
390
|
// delete Redis key given key
|
|
304
391
|
var self = this;
|
|
305
392
|
key = this.prepKey(key);
|
|
393
|
+
var is_binary = this.storage.isBinaryKey(key);
|
|
306
394
|
|
|
307
395
|
this.logDebug(9, "Deleting Redis Object: " + key);
|
|
308
396
|
|
|
@@ -310,11 +398,24 @@ module.exports = Class.create({
|
|
|
310
398
|
if (!err && !deleted) {
|
|
311
399
|
err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
312
400
|
err.code = "NoSuchKey";
|
|
401
|
+
|
|
402
|
+
if (self.cache && !is_binary) {
|
|
403
|
+
// store 'empty' stub in cache
|
|
404
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
405
|
+
}
|
|
313
406
|
}
|
|
314
407
|
if (err) {
|
|
315
408
|
self.logError('redis', "Failed to delete object: " + key + ": " + err);
|
|
316
409
|
}
|
|
317
|
-
else
|
|
410
|
+
else {
|
|
411
|
+
self.logDebug(9, "Delete complete: " + key);
|
|
412
|
+
|
|
413
|
+
// possibly "delete" from LRU cache as well
|
|
414
|
+
if (self.cache && !is_binary) {
|
|
415
|
+
// store 'empty' stub in cache
|
|
416
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
417
|
+
}
|
|
418
|
+
}
|
|
318
419
|
|
|
319
420
|
callback(err);
|
|
320
421
|
} );
|
package/engines/RedisCluster.js
CHANGED
|
@@ -9,6 +9,7 @@ const Class = require("pixl-class");
|
|
|
9
9
|
const Component = require("pixl-server/component");
|
|
10
10
|
const Redis = require('ioredis');
|
|
11
11
|
const Tools = require("pixl-tools");
|
|
12
|
+
const Cache = require("pixl-cache");
|
|
12
13
|
|
|
13
14
|
module.exports = Class.create({
|
|
14
15
|
|
|
@@ -49,6 +50,22 @@ module.exports = Class.create({
|
|
|
49
50
|
|
|
50
51
|
this.keyTemplate = (r_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
|
|
51
52
|
|
|
53
|
+
// optional LRU cache
|
|
54
|
+
this.cache = null;
|
|
55
|
+
var cache_opts = r_config.cache;
|
|
56
|
+
if (cache_opts && cache_opts.enabled) {
|
|
57
|
+
this.logDebug(3, "Setting up LRU cache", cache_opts);
|
|
58
|
+
this.cache = new Cache( Tools.copyHashRemoveKeys(cache_opts, { enabled: 1 }) );
|
|
59
|
+
this.cache.on('expire', function(item, reason) {
|
|
60
|
+
self.logDebug(9, "Expiring LRU cache object: " + item.key + " due to: " + reason, {
|
|
61
|
+
key: item.key,
|
|
62
|
+
reason: reason,
|
|
63
|
+
totalCount: self.cache.count,
|
|
64
|
+
totalBytes: self.cache.bytes
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
r_config.clusterOpts.clusterRetryStrategy = function(attempts) {
|
|
53
70
|
if (attempts > r_config.connectRetries) return false;
|
|
54
71
|
return attempts;
|
|
@@ -107,13 +124,14 @@ module.exports = Class.create({
|
|
|
107
124
|
// store key+value in Redis
|
|
108
125
|
var self = this;
|
|
109
126
|
key = this.prepKey(key);
|
|
127
|
+
var is_binary = this.storage.isBinaryKey(key);
|
|
110
128
|
|
|
111
|
-
if (
|
|
129
|
+
if (is_binary) {
|
|
112
130
|
this.logDebug(9, "Storing Redis Binary Object: " + key, '' + value.length + ' bytes');
|
|
113
131
|
}
|
|
114
132
|
else {
|
|
115
133
|
this.logDebug(9, "Storing Redis JSON Object: " + key, this.debugLevel(10) ? value : null);
|
|
116
|
-
value = JSON.stringify( value );
|
|
134
|
+
value = Buffer.from( JSON.stringify( value ) );
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
this.redis.set( key, value, function(err) {
|
|
@@ -123,6 +141,11 @@ module.exports = Class.create({
|
|
|
123
141
|
}
|
|
124
142
|
else self.logDebug(9, "Store complete: " + key);
|
|
125
143
|
|
|
144
|
+
// possibly cache in LRU
|
|
145
|
+
if (!err && self.cache && !is_binary) {
|
|
146
|
+
self.cache.set( key, value, { date: Tools.timeNow(true) } );
|
|
147
|
+
}
|
|
148
|
+
|
|
126
149
|
if (callback) callback(err);
|
|
127
150
|
} );
|
|
128
151
|
},
|
|
@@ -152,7 +175,30 @@ module.exports = Class.create({
|
|
|
152
175
|
// The Redis API has no way to head / ping an object.
|
|
153
176
|
// So, we have to do this the RAM-hard way...
|
|
154
177
|
|
|
155
|
-
|
|
178
|
+
// check cache first
|
|
179
|
+
if (this.cache && this.cache.has(key)) {
|
|
180
|
+
var item = this.cache.getMeta(key);
|
|
181
|
+
|
|
182
|
+
if (item.value.length == 0) {
|
|
183
|
+
process.nextTick( function() {
|
|
184
|
+
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
185
|
+
err.code = "NoSuchKey";
|
|
186
|
+
callback( err );
|
|
187
|
+
} );
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
process.nextTick( function() {
|
|
191
|
+
self.logDebug(9, "Cached head complete: " + key);
|
|
192
|
+
callback( null, {
|
|
193
|
+
mod: item.date,
|
|
194
|
+
len: item.value.length
|
|
195
|
+
} );
|
|
196
|
+
} );
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
} // cache
|
|
200
|
+
|
|
201
|
+
this.redis.getBuffer( key, function(err, data) {
|
|
156
202
|
if (err) {
|
|
157
203
|
// an actual error
|
|
158
204
|
err.message = "Failed to head key: " + key + ": " + err;
|
|
@@ -164,7 +210,12 @@ module.exports = Class.create({
|
|
|
164
210
|
// always use "NoSuchKey" in error code
|
|
165
211
|
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
166
212
|
err.code = "NoSuchKey";
|
|
167
|
-
|
|
213
|
+
|
|
214
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
215
|
+
// store 'empty' stub in cache
|
|
216
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
217
|
+
}
|
|
218
|
+
|
|
168
219
|
callback( err, null );
|
|
169
220
|
}
|
|
170
221
|
else {
|
|
@@ -177,44 +228,79 @@ module.exports = Class.create({
|
|
|
177
228
|
// fetch Redis value given key
|
|
178
229
|
var self = this;
|
|
179
230
|
key = this.prepKey(key);
|
|
231
|
+
var is_binary = this.storage.isBinaryKey(key);
|
|
180
232
|
|
|
181
233
|
this.logDebug(9, "Fetching Redis Object: " + key);
|
|
182
234
|
|
|
183
|
-
|
|
184
|
-
this.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
self.logError('redis', ''+err);
|
|
190
|
-
callback( err, null );
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
// record not found
|
|
194
|
-
// always use "NoSuchKey" in error code
|
|
235
|
+
// check cache first
|
|
236
|
+
if (this.cache && !is_binary && this.cache.has(key)) {
|
|
237
|
+
var item = this.cache.getMeta(key);
|
|
238
|
+
|
|
239
|
+
if (item.value.length == 0) {
|
|
240
|
+
process.nextTick( function() {
|
|
195
241
|
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
196
242
|
err.code = "NoSuchKey";
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
243
|
+
callback( err );
|
|
244
|
+
} );
|
|
200
245
|
}
|
|
201
246
|
else {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
else {
|
|
206
|
-
try { result = JSON.parse( result.toString() ); }
|
|
247
|
+
process.nextTick( function() {
|
|
248
|
+
var data = null;
|
|
249
|
+
try { data = JSON.parse( item.value.toString() ); }
|
|
207
250
|
catch (err) {
|
|
208
251
|
self.logError('redis', "Failed to parse JSON record: " + key + ": " + err);
|
|
209
252
|
callback( err, null );
|
|
210
253
|
return;
|
|
211
254
|
}
|
|
212
|
-
self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ?
|
|
255
|
+
self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
|
|
256
|
+
|
|
257
|
+
callback( null, data );
|
|
258
|
+
} );
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
} // cache
|
|
262
|
+
|
|
263
|
+
this.redis.getBuffer( key, function(err, result) {
|
|
264
|
+
if (err) {
|
|
265
|
+
// an actual error
|
|
266
|
+
err.message = "Failed to fetch key: " + key + ": " + err;
|
|
267
|
+
self.logError('redis', ''+err);
|
|
268
|
+
return callback( err, null );
|
|
269
|
+
}
|
|
270
|
+
else if (!result || !result.length) {
|
|
271
|
+
// record not found
|
|
272
|
+
// always use "NoSuchKey" in error code
|
|
273
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
274
|
+
err.code = "NoSuchKey";
|
|
275
|
+
|
|
276
|
+
if (self.cache && !is_binary) {
|
|
277
|
+
// store 'empty' stub in cache
|
|
278
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
213
279
|
}
|
|
214
280
|
|
|
215
|
-
callback(
|
|
281
|
+
return callback( err, null );
|
|
216
282
|
}
|
|
217
|
-
|
|
283
|
+
|
|
284
|
+
if (is_binary) {
|
|
285
|
+
self.logDebug(9, "Binary fetch complete: " + key, '' + result.length + ' bytes');
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
// possibly cache in LRU
|
|
289
|
+
if (self.cache) {
|
|
290
|
+
self.cache.set( key, result, { date: Tools.timeNow(true) } );
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try { result = JSON.parse( result.toString() ); }
|
|
294
|
+
catch (err) {
|
|
295
|
+
self.logError('redis', "Failed to parse JSON record: " + key + ": " + err);
|
|
296
|
+
callback( err, null );
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? result : null);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
callback( null, result );
|
|
303
|
+
} ); // redis.getBuffer
|
|
218
304
|
},
|
|
219
305
|
|
|
220
306
|
getBuffer: function(key, callback) {
|
|
@@ -317,6 +403,7 @@ module.exports = Class.create({
|
|
|
317
403
|
// delete Redis key given key
|
|
318
404
|
var self = this;
|
|
319
405
|
key = this.prepKey(key);
|
|
406
|
+
var is_binary = this.storage.isBinaryKey(key);
|
|
320
407
|
|
|
321
408
|
this.logDebug(9, "Deleting Redis Object: " + key);
|
|
322
409
|
|
|
@@ -324,11 +411,24 @@ module.exports = Class.create({
|
|
|
324
411
|
if (!err && !deleted) {
|
|
325
412
|
err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
326
413
|
err.code = "NoSuchKey";
|
|
414
|
+
|
|
415
|
+
if (self.cache && !is_binary) {
|
|
416
|
+
// store 'empty' stub in cache
|
|
417
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
418
|
+
}
|
|
327
419
|
}
|
|
328
420
|
if (err) {
|
|
329
421
|
self.logError('redis', "Failed to delete object: " + key + ": " + err);
|
|
330
422
|
}
|
|
331
|
-
else
|
|
423
|
+
else {
|
|
424
|
+
self.logDebug(9, "Delete complete: " + key);
|
|
425
|
+
|
|
426
|
+
// possibly "delete" from LRU cache as well
|
|
427
|
+
if (self.cache && !is_binary) {
|
|
428
|
+
// store 'empty' stub in cache
|
|
429
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
430
|
+
}
|
|
431
|
+
}
|
|
332
432
|
|
|
333
433
|
callback(err);
|
|
334
434
|
} );
|
package/engines/S3.js
CHANGED
|
@@ -149,7 +149,7 @@ module.exports = Class.create({
|
|
|
149
149
|
}
|
|
150
150
|
else {
|
|
151
151
|
this.logDebug(9, "Storing S3 JSON Object: " + key, this.debugLevel(10) ? params.Body : null);
|
|
152
|
-
params.Body = this.pretty ? JSON.stringify( params.Body, null, "\t" ) : JSON.stringify( params.Body );
|
|
152
|
+
params.Body = Buffer.from( this.pretty ? JSON.stringify( params.Body, null, "\t" ) : JSON.stringify( params.Body ) );
|
|
153
153
|
params.ContentType = 'application/json';
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -257,13 +257,22 @@ module.exports = Class.create({
|
|
|
257
257
|
if (this.cache && this.cache.has(orig_key)) {
|
|
258
258
|
var item = this.cache.getMeta(orig_key);
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
if (item.value.length == 0) {
|
|
261
|
+
process.nextTick( function() {
|
|
262
|
+
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
263
|
+
err.code = "NoSuchKey";
|
|
264
|
+
callback( err );
|
|
265
265
|
} );
|
|
266
|
-
}
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
process.nextTick( function() {
|
|
269
|
+
self.logDebug(9, "Cached head complete: " + orig_key);
|
|
270
|
+
callback( null, {
|
|
271
|
+
mod: item.date,
|
|
272
|
+
len: item.value.length
|
|
273
|
+
} );
|
|
274
|
+
} );
|
|
275
|
+
}
|
|
267
276
|
return;
|
|
268
277
|
} // cache
|
|
269
278
|
|
|
@@ -288,6 +297,11 @@ module.exports = Class.create({
|
|
|
288
297
|
// always include "Not found" in error message
|
|
289
298
|
err = new Error("Failed to head key: " + key + ": Not found");
|
|
290
299
|
err.code = "NoSuchKey";
|
|
300
|
+
|
|
301
|
+
if (self.cache && !self.storage.isBinaryKey(orig_key)) {
|
|
302
|
+
// store 'empty' stub in cache
|
|
303
|
+
self.cache.set( orig_key, Buffer.alloc(0), { date: 0 } );
|
|
304
|
+
}
|
|
291
305
|
}
|
|
292
306
|
else if (err.name == 'SlowDown') {
|
|
293
307
|
// special behavior for SlowDown errors
|
|
@@ -315,19 +329,29 @@ module.exports = Class.create({
|
|
|
315
329
|
|
|
316
330
|
// check cache first
|
|
317
331
|
if (this.cache && !is_binary && this.cache.has(orig_key)) {
|
|
318
|
-
var
|
|
332
|
+
var item = this.cache.getMeta(orig_key);
|
|
319
333
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
callback(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
334
|
+
if (item.value.length == 0) {
|
|
335
|
+
process.nextTick( function() {
|
|
336
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
337
|
+
err.code = "NoSuchKey";
|
|
338
|
+
callback( err );
|
|
339
|
+
} );
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
process.nextTick( function() {
|
|
343
|
+
var data = null;
|
|
344
|
+
try { data = JSON.parse( item.value.toString() ); }
|
|
345
|
+
catch (e) {
|
|
346
|
+
self.logError('file', "Failed to parse JSON record: " + orig_key + ": " + e);
|
|
347
|
+
callback( e, null );
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
self.logDebug(9, "Cached JSON fetch complete: " + orig_key, self.debugLevel(10) ? data : null);
|
|
351
|
+
|
|
352
|
+
callback( null, data );
|
|
353
|
+
} );
|
|
354
|
+
}
|
|
331
355
|
return;
|
|
332
356
|
} // cache
|
|
333
357
|
|
|
@@ -350,14 +374,12 @@ module.exports = Class.create({
|
|
|
350
374
|
self.logDebug(9, "Binary fetch complete: " + key, '' + body.length + ' bytes');
|
|
351
375
|
}
|
|
352
376
|
else {
|
|
353
|
-
body = body.toString();
|
|
354
|
-
|
|
355
377
|
// possibly cache in LRU
|
|
356
378
|
if (self.cache) {
|
|
357
379
|
self.cache.set( orig_key, body, { date: Tools.timeNow(true) } );
|
|
358
380
|
}
|
|
359
381
|
|
|
360
|
-
try { body = JSON.parse( body ); }
|
|
382
|
+
try { body = JSON.parse( body.toString() ); }
|
|
361
383
|
catch (e) {
|
|
362
384
|
self.logError('s3', "Failed to parse JSON record: " + key + ": " + e);
|
|
363
385
|
callback( e, null );
|
|
@@ -378,6 +400,11 @@ module.exports = Class.create({
|
|
|
378
400
|
// always include "Not found" in error message
|
|
379
401
|
err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
380
402
|
err.code = "NoSuchKey";
|
|
403
|
+
|
|
404
|
+
if (self.cache && !is_binary) {
|
|
405
|
+
// store 'empty' stub in cache
|
|
406
|
+
self.cache.set( orig_key, Buffer.alloc(0), { date: 0 } );
|
|
407
|
+
}
|
|
381
408
|
}
|
|
382
409
|
else if (err.name == 'SlowDown') {
|
|
383
410
|
// special behavior for SlowDown errors
|
|
@@ -577,9 +604,10 @@ module.exports = Class.create({
|
|
|
577
604
|
self.logDebug(9, "Delete complete: " + key);
|
|
578
605
|
self.storage.emit('billing', 's3_delete', 1);
|
|
579
606
|
|
|
580
|
-
// possibly delete from LRU cache as well
|
|
581
|
-
if (self.cache && self.
|
|
582
|
-
|
|
607
|
+
// possibly "delete" from LRU cache as well
|
|
608
|
+
if (self.cache && !self.storage.isBinaryKey(orig_key)) {
|
|
609
|
+
// store 'empty' stub in cache
|
|
610
|
+
self.cache.set( orig_key, Buffer.alloc(0), { date: 0 } );
|
|
583
611
|
}
|
|
584
612
|
|
|
585
613
|
if (callback) process.nextTick( function() { callback(null, data); } );
|
package/engines/SQLite.js
CHANGED
|
@@ -191,13 +191,22 @@ module.exports = Class.create({
|
|
|
191
191
|
if (this.cache && this.cache.has(key)) {
|
|
192
192
|
var item = this.cache.getMeta(key);
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
if (item.value.length == 0) {
|
|
195
|
+
process.nextTick( function() {
|
|
196
|
+
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
197
|
+
err.code = "NoSuchKey";
|
|
198
|
+
callback( err );
|
|
199
199
|
} );
|
|
200
|
-
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
process.nextTick( function() {
|
|
203
|
+
self.logDebug(9, "Cached head complete: " + key);
|
|
204
|
+
callback( null, {
|
|
205
|
+
mod: item.date,
|
|
206
|
+
len: item.value.length
|
|
207
|
+
} );
|
|
208
|
+
} );
|
|
209
|
+
}
|
|
201
210
|
return;
|
|
202
211
|
} // cache
|
|
203
212
|
|
|
@@ -215,6 +224,12 @@ module.exports = Class.create({
|
|
|
215
224
|
if (!row) {
|
|
216
225
|
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
217
226
|
err.code = "NoSuchKey";
|
|
227
|
+
|
|
228
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
229
|
+
// store 'empty' stub in cache
|
|
230
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
231
|
+
}
|
|
232
|
+
|
|
218
233
|
return callback(err, null);
|
|
219
234
|
}
|
|
220
235
|
callback(null, { mod: row.modified, len: row['length(value)'] });
|
|
@@ -230,19 +245,29 @@ module.exports = Class.create({
|
|
|
230
245
|
|
|
231
246
|
// check cache first
|
|
232
247
|
if (this.cache && !is_binary && this.cache.has(key)) {
|
|
233
|
-
var
|
|
248
|
+
var item = this.cache.getMeta(key);
|
|
234
249
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
callback(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
250
|
+
if (item.value.length == 0) {
|
|
251
|
+
process.nextTick( function() {
|
|
252
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
253
|
+
err.code = "NoSuchKey";
|
|
254
|
+
callback( err );
|
|
255
|
+
} );
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
process.nextTick( function() {
|
|
259
|
+
var data = null;
|
|
260
|
+
try { data = JSON.parse( item.value.toString() ); }
|
|
261
|
+
catch (e) {
|
|
262
|
+
self.logError('file', "Failed to parse JSON record: " + key + ": " + e);
|
|
263
|
+
callback( e, null );
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
|
|
267
|
+
|
|
268
|
+
callback( null, data );
|
|
269
|
+
} );
|
|
270
|
+
}
|
|
246
271
|
return;
|
|
247
272
|
} // cache
|
|
248
273
|
|
|
@@ -263,6 +288,12 @@ module.exports = Class.create({
|
|
|
263
288
|
if (!row) {
|
|
264
289
|
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
265
290
|
err.code = "NoSuchKey";
|
|
291
|
+
|
|
292
|
+
if (self.cache && !is_binary) {
|
|
293
|
+
// store 'empty' stub in cache
|
|
294
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
295
|
+
}
|
|
296
|
+
|
|
266
297
|
return callback(err, null);
|
|
267
298
|
}
|
|
268
299
|
if (is_binary) {
|
|
@@ -399,11 +430,20 @@ module.exports = Class.create({
|
|
|
399
430
|
if (!info.changes) {
|
|
400
431
|
var err = new Error("Failed to delete object: " + key + ": Not found");
|
|
401
432
|
err.code = "NoSuchKey";
|
|
433
|
+
|
|
434
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
435
|
+
// store 'empty' stub in cache
|
|
436
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
437
|
+
}
|
|
438
|
+
|
|
402
439
|
return callback(err);
|
|
403
440
|
}
|
|
404
441
|
self.logDebug(9, "Delete complete: " + key);
|
|
405
|
-
|
|
406
|
-
|
|
442
|
+
|
|
443
|
+
// possibly "delete" from LRU cache as well
|
|
444
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
445
|
+
// store 'empty' stub in cache
|
|
446
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
407
447
|
}
|
|
408
448
|
callback();
|
|
409
449
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixl-server-storage",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "A key/value/list storage component for the pixl-server framework.",
|
|
5
5
|
"author": "Joseph Huckaby <jhuckaby@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/jhuckaby/pixl-server-storage",
|
|
Binary file
|
|
Binary file
|