pixl-server-storage 3.2.4 → 3.2.6

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/docs/API.md CHANGED
@@ -280,7 +280,7 @@ storage.head( 'test1', function(err, data) {
280
280
  } );
281
281
  ```
282
282
 
283
- Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native head API, so the `head()` method has to load the entire record. It does return the record size in to the `len` property, but there is no way to retrieve the last modified date.
283
+ Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native head API, so the `head()` method has to load the entire record. It does return the record size in to the `len` property, but the `mod` property will always be set to `1`.
284
284
 
285
285
  ## headMulti
286
286
 
@@ -689,6 +689,7 @@ module.exports = Class.create({
689
689
  // run daily maintenance - delete old temp files
690
690
  var self = this;
691
691
  var now = Tools.timeNow(true);
692
+ this.logDebug(3, "Running filesystem maintenance");
692
693
 
693
694
  fs.readdir( this.tempDir, function(err, files) {
694
695
  if (err) return callback();
package/engines/Hybrid.js CHANGED
@@ -107,6 +107,8 @@ module.exports = Class.create({
107
107
 
108
108
  runMaintenance: function(callback) {
109
109
  // run daily maintenance
110
+ this.logDebug(3, "Running hybrid maintenance");
111
+
110
112
  async.series([
111
113
  this.binaryEngine.runMaintenance.bind(this.binaryEngine),
112
114
  this.docEngine.runMaintenance.bind(this.docEngine)
package/engines/SQLite.js CHANGED
@@ -13,6 +13,7 @@ const Component = require("pixl-server/component");
13
13
  const SQLite3 = require('sqlite3');
14
14
  const Tools = require("pixl-tools");
15
15
  const Perf = require("pixl-perf");
16
+ const Cache = require("pixl-cache");
16
17
  const async = require('async');
17
18
  const noop = function() {};
18
19
 
@@ -46,7 +47,12 @@ module.exports = Class.create({
46
47
  this.keyTemplate = (sql_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
47
48
 
48
49
  this.baseDir = sql_config.base_dir || process.cwd();
49
- this.commands = {};
50
+ this.commands = {
51
+ get: 'SELECT value FROM items WHERE key = $key LIMIT 1',
52
+ head: 'SELECT modified, length(value) FROM items WHERE key = $key LIMIT 1',
53
+ put: 'INSERT INTO items VALUES($key, $value, $now) ON CONFLICT (key) DO UPDATE SET value = $value, modified = $now WHERE key = $key',
54
+ delete: 'DELETE FROM items WHERE key = $key'
55
+ };
50
56
 
51
57
  // create initial data dir if necessary
52
58
  try {
@@ -58,6 +64,22 @@ module.exports = Class.create({
58
64
  throw new Error(msg);
59
65
  }
60
66
 
67
+ // optional LRU cache
68
+ this.cache = null;
69
+ var cache_opts = this.config.get('cache');
70
+ if (cache_opts && cache_opts.enabled) {
71
+ this.logDebug(3, "Setting up LRU cache", cache_opts);
72
+ this.cache = new Cache( Tools.copyHashRemoveKeys(cache_opts, { enabled: 1 }) );
73
+ this.cache.on('expire', function(item, reason) {
74
+ self.logDebug(9, "Expiring LRU cache object: " + item.key + " due to: " + reason, {
75
+ key: item.key,
76
+ reason: reason,
77
+ totalCount: self.cache.count,
78
+ totalBytes: self.cache.bytes
79
+ });
80
+ });
81
+ }
82
+
61
83
  var db_file = Path.join( this.baseDir, sql_config.filename );
62
84
  this.logDebug(3, "Opening database file: " + db_file);
63
85
 
@@ -80,18 +102,6 @@ module.exports = Class.create({
80
102
  // create our table if necessary
81
103
  self.db.run( 'CREATE TABLE IF NOT EXISTS items( key TEXT PRIMARY KEY, value BLOB, modified INTEGER )', callback );
82
104
  },
83
- function(callback) {
84
- self.commands.get = self.db.prepare( 'SELECT value FROM items WHERE key = $key LIMIT 1', callback );
85
- },
86
- function(callback) {
87
- self.commands.head = self.db.prepare( 'SELECT modified, length(value) FROM items WHERE key = $key LIMIT 1', callback );
88
- },
89
- function(callback) {
90
- self.commands.put = self.db.prepare( 'INSERT INTO items VALUES($key, $value, $now) ON CONFLICT (key) DO UPDATE SET value = $value, modified = $now WHERE key = $key', callback );
91
- },
92
- function(callback) {
93
- self.commands.delete = self.db.prepare( 'DELETE FROM items WHERE key = $key', callback );
94
- }
95
105
  ],
96
106
  function(err) {
97
107
  if (err) {
@@ -128,7 +138,9 @@ module.exports = Class.create({
128
138
  var now = Tools.timeNow(true);
129
139
  key = this.prepKey(key);
130
140
 
131
- if (this.storage.isBinaryKey(key)) {
141
+ var is_binary = this.storage.isBinaryKey(key);
142
+
143
+ if (is_binary) {
132
144
  this.logDebug(9, "Storing SQLite Binary Object: " + key, '' + value.length + ' bytes');
133
145
  }
134
146
  else {
@@ -136,13 +148,18 @@ module.exports = Class.create({
136
148
  value = Buffer.from( JSON.stringify( value ) );
137
149
  }
138
150
 
139
- this.commands.put.run({ $key: key, $value: value, $now: now }, function(err) {
151
+ this.db.run( this.commands.put, { $key: key, $value: value, $now: now }, function(err) {
140
152
  if (err) {
141
153
  err.message = "Failed to store object: " + key + ": " + err;
142
154
  self.logError('sqlite', '' + err);
143
155
  }
144
156
  else self.logDebug(9, "Store complete: " + key);
145
157
 
158
+ // possibly cache in LRU
159
+ if (self.cache && !is_binary) {
160
+ self.cache.set( key, value, { date: Tools.timeNow(true) } );
161
+ }
162
+
146
163
  if (callback) callback(err);
147
164
  } ); // put
148
165
  },
@@ -169,7 +186,21 @@ module.exports = Class.create({
169
186
  var self = this;
170
187
  key = this.prepKey(key);
171
188
 
172
- this.commands.head.get({ $key: key }, function(err, row) {
189
+ // check cache first
190
+ if (this.cache && this.cache.has(key)) {
191
+ var item = this.cache.getMeta(key);
192
+
193
+ process.nextTick( function() {
194
+ self.logDebug(9, "Cached head complete: " + key);
195
+ callback( null, {
196
+ mod: item.date,
197
+ len: item.value.length
198
+ } );
199
+ } );
200
+ return;
201
+ } // cache
202
+
203
+ this.db.get( this.commands.head, { $key: key }, function(err, row) {
173
204
  if (err) {
174
205
  // an actual error
175
206
  err.message = "Failed to head key: " + key + ": " + err;
@@ -194,9 +225,29 @@ module.exports = Class.create({
194
225
  var self = this;
195
226
  key = this.prepKey(key);
196
227
 
228
+ var is_binary = this.storage.isBinaryKey(key);
229
+
230
+ // check cache first
231
+ if (this.cache && !is_binary && this.cache.has(key)) {
232
+ var data = this.cache.get(key);
233
+
234
+ process.nextTick( function() {
235
+ try { data = JSON.parse( data.toString() ); }
236
+ catch (e) {
237
+ self.logError('file', "Failed to parse JSON record: " + key + ": " + e);
238
+ callback( e, null );
239
+ return;
240
+ }
241
+ self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
242
+
243
+ callback( null, data );
244
+ } );
245
+ return;
246
+ } // cache
247
+
197
248
  this.logDebug(9, "Fetching SQLite Object: " + key);
198
249
 
199
- this.commands.get.get({ $key: key }, function(err, row) {
250
+ this.db.get( this.commands.get, { $key: key }, function(err, row) {
200
251
  if (err) {
201
252
  // an actual error
202
253
  err.message = "Failed to fetch key: " + key + ": " + err;
@@ -212,11 +263,16 @@ module.exports = Class.create({
212
263
  }
213
264
  else {
214
265
  // success
215
- if (self.storage.isBinaryKey(key)) {
266
+ if (is_binary) {
216
267
  self.logDebug(9, "Binary fetch complete: " + key, '' + row.value.length + ' bytes');
217
268
  callback( null, row.value );
218
269
  }
219
270
  else {
271
+ // possibly cache in LRU
272
+ if (self.cache) {
273
+ self.cache.set( key, row.value, { date: Tools.timeNow(true) } );
274
+ }
275
+
220
276
  var json = null;
221
277
  try { json = JSON.parse( row.value.toString() ); }
222
278
  catch (err) {
@@ -238,7 +294,7 @@ module.exports = Class.create({
238
294
 
239
295
  this.logDebug(9, "Fetching SQLite Object: " + key);
240
296
 
241
- this.commands.get.get({ $key: key }, function(err, row) {
297
+ this.db.get( this.commands.get, { $key: key }, function(err, row) {
242
298
  if (err) {
243
299
  // an actual error
244
300
  err.message = "Failed to fetch key: " + key + ": " + err;
@@ -334,7 +390,7 @@ module.exports = Class.create({
334
390
 
335
391
  this.logDebug(9, "Deleting SQLite Object: " + key);
336
392
 
337
- this.commands.delete.run({ $key: key }, function(err) {
393
+ this.db.run( this.commands.delete, { $key: key }, function(err) {
338
394
  // In sqlite3 callbacks `this` is special, and contains `changes`
339
395
  if (!err && !this.changes) {
340
396
  err = new Error("Not found");
@@ -346,6 +402,12 @@ module.exports = Class.create({
346
402
  return callback(err);
347
403
  }
348
404
  self.logDebug(9, "Delete complete: " + key);
405
+
406
+ // possibly delete from LRU cache as well
407
+ if (self.cache && self.cache.has(key)) {
408
+ self.cache.delete(key);
409
+ }
410
+
349
411
  callback();
350
412
  }); // delete
351
413
  },
@@ -354,6 +416,8 @@ module.exports = Class.create({
354
416
  // run daily maintenance
355
417
  // config.backups: { enabled, dir, filename, compress?, keep? }
356
418
  var self = this;
419
+ this.logDebug(3, "Running SQLite maintenance");
420
+
357
421
  var backups = this.config.get('backups') || null;
358
422
  if (!backups || !backups.enabled || !backups.dir || !backups.filename) return callback();
359
423
 
@@ -506,21 +570,14 @@ module.exports = Class.create({
506
570
  this.logDebug(2, "Shutting down SQLite");
507
571
 
508
572
  if (this.db) {
509
- // finalize all statements and close db
510
- async.eachSeries( Object.keys(this.commands),
511
- function(key, callback) {
512
- self.commands[key].finalize(callback);
513
- },
514
- function() {
515
- self.db.close( function(err) {
516
- if (err) self.logError('sqlite', "Failed to shutdown database cleanly: " + err);
517
- else self.logDebug(3, "Shutdown complete");
518
- callback();
519
- } );
520
- self.db = null;
521
- self.commands = null;
522
- }
523
- ); // eachSeries
573
+ // close db
574
+ self.db.close( function(err) {
575
+ if (err) self.logError('sqlite', "Failed to shutdown database cleanly: " + err);
576
+ else self.logDebug(3, "Shutdown complete");
577
+ callback();
578
+ } );
579
+ self.db = null;
580
+ self.commands = null;
524
581
  }
525
582
  }
526
583
 
package/indexer.js CHANGED
@@ -129,7 +129,7 @@ module.exports = Class.create({
129
129
  _indexRecord: function(id, record, config, callback) {
130
130
  // index record (internal)
131
131
  var self = this;
132
- this.logDebug(8, "Indexing record: " + id, record);
132
+ this.logDebug(8, "Indexing record: " + id);
133
133
 
134
134
  var state = {
135
135
  id: id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-storage",
3
- "version": "3.2.4",
3
+ "version": "3.2.6",
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",
package/storage.js CHANGED
@@ -169,7 +169,7 @@ module.exports = Class.create({
169
169
  // sanity checks
170
170
  if (!value) return callback( new Error("Record value cannot be false.") );
171
171
 
172
- var isBuffer = !!value.fill;
172
+ var isBuffer = Buffer.isBuffer(value);
173
173
  if (isBuffer && !this.isBinaryKey(key)) {
174
174
  return callback( new Error("Buffer values are only allowed with keys containing file extensions, e.g. " + key + ".bin") );
175
175
  }
package/test/config.json CHANGED
@@ -69,7 +69,13 @@
69
69
  "filename": "sqlite.db",
70
70
  "pragmas": {
71
71
  "auto_vacuum": 0,
72
- "cache_size": -100000
72
+ "cache_size": -100000,
73
+ "journal_mode": "WAL"
74
+ },
75
+ "cache": {
76
+ "enabled": true,
77
+ "maxItems": 1000,
78
+ "maxBytes": 10485760
73
79
  }
74
80
  }
75
81
  }
Binary file
package/transaction.js CHANGED
@@ -40,7 +40,7 @@ var TransStorageFunctions = {
40
40
  // binary keys not part of transaction system
41
41
  if (this.isBinaryKey(key)) return this.rawStorage.put(key, value, callback);
42
42
  if (!value) return callback( new Error("Value cannot be false.") );
43
- if (value.fill) return callback( new Error("Buffers not allowed in transactions.") );
43
+ if (Buffer.isBuffer(value)) return callback( new Error("Buffers not allowed in transactions.") );
44
44
 
45
45
  this.logDebug(9, "Storing JSON Object in transaction: " + key, this.debugLevel(10) ? value : null);
46
46
  value = JSON.stringify( value );