pixl-server-storage 3.0.21 → 3.0.24

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/engines/S3.js CHANGED
@@ -275,7 +275,7 @@ module.exports = Class.create({
275
275
  if (done) return; else done = true;
276
276
 
277
277
  if (err) {
278
- if (err.code == 'NoSuchKey') {
278
+ if ((err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
279
279
  // key not found, special case, don't log an error
280
280
  // always include "Not found" in error message
281
281
  err = new Error("Failed to fetch key: " + key + ": Not found");
@@ -346,9 +346,17 @@ module.exports = Class.create({
346
346
  if (done) return; else done = true;
347
347
 
348
348
  if (err) {
349
- if ((err.code != 'NoSuchKey') && (err.code != 'NotFound')) {
350
- self.logError('s3', "Failed to head key: " + key + ": " + (err.message || err), err);
349
+ if ((err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
350
+ // key not found, special case, don't log an error
351
+ // always include "Not found" in error message
352
+ err = new Error("Failed to fetch key: " + key + ": Not found");
353
+ err.code = "NoSuchKey";
354
+ }
355
+ else {
356
+ // some other error
357
+ self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
351
358
  }
359
+
352
360
  download.destroy();
353
361
  callback( err, null );
354
362
  return;
@@ -397,9 +405,17 @@ module.exports = Class.create({
397
405
  if (done) return; else done = true;
398
406
 
399
407
  if (err) {
400
- if ((err.code != 'NoSuchKey') && (err.code != 'NotFound')) {
401
- self.logError('s3', "Failed to head key: " + key + ": " + (err.message || err), err);
408
+ if ((err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
409
+ // key not found, special case, don't log an error
410
+ // always include "Not found" in error message
411
+ err = new Error("Failed to fetch key: " + key + ": Not found");
412
+ err.code = "NoSuchKey";
402
413
  }
414
+ else {
415
+ // some other error
416
+ self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
417
+ }
418
+
403
419
  download.destroy();
404
420
  callback( err, null );
405
421
  return;
@@ -159,7 +159,9 @@ module.exports = Class.create({
159
159
  }
160
160
 
161
161
  // load index summary for list of all populated dates
162
+ var dspf = state.perf.begin('date_summary');
162
163
  this.get( sum_path, function(err, summary) {
164
+ dspf.end();
163
165
  if (err || !summary) {
164
166
  summary = { id: query.def.id, values: {} };
165
167
  }
@@ -190,6 +192,7 @@ module.exports = Class.create({
190
192
  }
191
193
 
192
194
  // now perform OR search for all applicable words
195
+ var drpf = state.perf.begin('date_range');
193
196
  async.eachLimit( words, self.concurrency,
194
197
  function(word, callback) {
195
198
  // for each word, iterate over record ids
@@ -203,6 +206,8 @@ module.exports = Class.create({
203
206
  },
204
207
  function(err) {
205
208
  // all done, perform final merge
209
+ drpf.end();
210
+ state.perf.count('date_buckets', words.length);
206
211
  if (err) return callback(err);
207
212
  self.mergeIndex( record_ids, temp_results, state.first ? 'or' : state.mode );
208
213
  state.first = false;
@@ -119,7 +119,9 @@ module.exports = Class.create({
119
119
  }
120
120
 
121
121
  // load index summary for list of all populated numbers
122
+ var nspf = state.perf.begin('number_summary');
122
123
  this.get( sum_path, function(err, summary) {
124
+ nspf.end();
123
125
  if (err || !summary) {
124
126
  summary = { id: query.def.id, values: {} };
125
127
  }
@@ -150,6 +152,7 @@ module.exports = Class.create({
150
152
  }
151
153
 
152
154
  // now perform OR search for all applicable words
155
+ var nrpf = state.perf.begin('number_range');
153
156
  async.eachLimit( words, self.concurrency,
154
157
  function(word, callback) {
155
158
  // for each word, iterate over record ids
@@ -163,6 +166,8 @@ module.exports = Class.create({
163
166
  },
164
167
  function(err) {
165
168
  // all done, perform final merge
169
+ nrpf.end();
170
+ state.perf.count('number_buckets', words.length);
166
171
  if (err) return callback(err);
167
172
  self.mergeIndex( record_ids, temp_results, state.first ? 'or' : state.mode );
168
173
  state.first = false;
package/indexer.js CHANGED
@@ -5,6 +5,7 @@
5
5
  var async = require('async');
6
6
  var Class = require("pixl-class");
7
7
  var Tools = require("pixl-tools");
8
+ var Perf = require("pixl-perf");
8
9
  var nearley = require("nearley");
9
10
  var pxql_grammar = require("./pxql.js");
10
11
  var stemmer = require('porter-stemmer').stemmer;
@@ -1220,15 +1221,16 @@ module.exports = Class.create({
1220
1221
  var path = config.base_path;
1221
1222
  var pf = this.perf.begin('search');
1222
1223
 
1224
+ var orig_query = query;
1225
+ if (typeof(query) == 'object') query = Tools.copyHash(query, true);
1226
+
1223
1227
  this.shareLock( 'C|'+path, true, function(err, lock) {
1224
1228
  // got shared lock
1225
1229
  self._searchRecords( query, config, function(err, results, state) {
1226
1230
  // search complete
1227
- var elapsed = pf.end();
1228
-
1229
1231
  if (!err) self.logTransaction('search', path, {
1230
- query: query,
1231
- elapsed_ms: elapsed,
1232
+ query: orig_query,
1233
+ perf: state.perf ? state.perf.metrics() : {},
1232
1234
  results: (self.logEventTypes.search || self.logEventTypes.all) ? Tools.numKeys(results) : 0
1233
1235
  });
1234
1236
 
@@ -1251,9 +1253,15 @@ module.exports = Class.create({
1251
1253
  if (query == '*') {
1252
1254
  // fetch all records
1253
1255
  this.logDebug(8, "Fetching all records: " + config.base_path);
1256
+ var apf = new Perf();
1257
+ apf.begin();
1258
+ apf.begin('all');
1259
+
1254
1260
  return this.hashGetAll( config.base_path + '/_id', function(err, results) {
1255
1261
  // ignore error, just return empty hash
1256
- callback( null, results || {}, {} );
1262
+ apf.end('all');
1263
+ apf.end();
1264
+ callback( null, results || {}, { perf: apf } );
1257
1265
  } );
1258
1266
  }
1259
1267
  else if (query.match(/^\([\s\S]+\)$/)) {
@@ -1282,6 +1290,12 @@ module.exports = Class.create({
1282
1290
  state.record_ids = Object.create(null);
1283
1291
  state.first = true;
1284
1292
 
1293
+ // track detailed perf of search operations
1294
+ if (!state.perf) {
1295
+ state.perf = new Perf();
1296
+ state.perf.begin();
1297
+ }
1298
+
1285
1299
  // first, split criteria into subs (sub-queries),
1286
1300
  // stds (standard queries) and negs (negative queries)
1287
1301
  var subs = [], stds = [], negs = [];
@@ -1302,11 +1316,14 @@ module.exports = Class.create({
1302
1316
  }
1303
1317
 
1304
1318
  // stds need to be weighed and sorted by weight ascending
1319
+ var wpf = state.perf.begin('weigh');
1305
1320
  async.eachLimit( (query.mode == 'and') ? stds : [], this.concurrency,
1306
1321
  function(crit, callback) {
1307
1322
  self.weighCriterion(crit, config, callback);
1308
1323
  },
1309
1324
  function(err) {
1325
+ wpf.end();
1326
+
1310
1327
  // sort stds by weight ascending (only needed in AND mode)
1311
1328
  if (query.mode == 'and') {
1312
1329
  stds = stds.sort( function(a, b) { return a.weight - b.weight; } );
@@ -1317,9 +1334,12 @@ module.exports = Class.create({
1317
1334
  var tasks = [].concat( subs, stds, negs );
1318
1335
  async.eachSeries( tasks,
1319
1336
  function(task, callback) {
1337
+ task.perf = state.perf;
1338
+
1320
1339
  if (task.criteria) {
1321
- // sub-query
1340
+ // sub-query
1322
1341
  self._searchRecords( task, config, function(err, records) {
1342
+ state.perf.count('subs', 1);
1323
1343
  self.mergeIndex( state.record_ids, records, state.first ? 'or' : state.mode );
1324
1344
  state.first = false;
1325
1345
  callback();
@@ -1333,13 +1353,31 @@ module.exports = Class.create({
1333
1353
  // custom index type, e.g. date, time, number
1334
1354
  var func = 'searchIndex_' + task.def.type;
1335
1355
  if (!self[func]) return callback( new Error("Unknown index type: " + task.def.type) );
1336
- self[func]( task, state, callback );
1356
+
1357
+ var cpf = state.perf.begin('search_' + task.def.id + '_' + task.def.type);
1358
+ self[func]( task, state, function(err) {
1359
+ cpf.end();
1360
+ state.perf.count(task.def.type + 's', 1);
1361
+ callback(err);
1362
+ } );
1337
1363
  }
1338
1364
  else if (task.literal) {
1339
- self.searchWordIndexLiteral(task, state, callback);
1365
+ // literal multi-word phrase
1366
+ var spf = state.perf.begin('search_' + task.def.id + '_literal');
1367
+ self.searchWordIndexLiteral(task, state, function(err) {
1368
+ spf.end();
1369
+ state.perf.count('literals', 1);
1370
+ callback(err);
1371
+ });
1340
1372
  }
1341
1373
  else {
1342
- self.searchWordIndex(task, state, callback);
1374
+ // single word search
1375
+ var spf = state.perf.begin('search_' + task.def.id + '_word');
1376
+ self.searchWordIndex(task, state, function(err) {
1377
+ spf.end();
1378
+ state.perf.count('words', 1);
1379
+ callback(err);
1380
+ });
1343
1381
  }
1344
1382
  },
1345
1383
  function(err) {
@@ -1347,6 +1385,7 @@ module.exports = Class.create({
1347
1385
  if (err) {
1348
1386
  self.logError('index', "Index search failed: " + err);
1349
1387
  state.record_ids = {};
1388
+ state.err = err;
1350
1389
  }
1351
1390
  self.logDebug(10, "Search complete", state.record_ids);
1352
1391
  callback(null, state.record_ids, Tools.copyHashRemoveKeys(state, { config:1, record_ids:1, first:1 }));
@@ -1357,7 +1396,7 @@ module.exports = Class.create({
1357
1396
  },
1358
1397
 
1359
1398
  searchWordIndex: function(query, state, callback) {
1360
- // run one search query (list of words against one index)
1399
+ // run one word query (single word against one index)
1361
1400
  var self = this;
1362
1401
  var config = state.config;
1363
1402
  var def = query.def;
@@ -1385,6 +1424,7 @@ module.exports = Class.create({
1385
1424
  // then it would probably be faster to apply the logic using _data getMulti (a.k.a row scan).
1386
1425
  // Otherwise, perform a normal hash merge (which has to read every hash page).
1387
1426
  var hash_page_size = config.hash_page_size || 1000;
1427
+ var wpf = state.perf.begin('word_' + query.word);
1388
1428
 
1389
1429
  if ((mode == 'and') && query.weight && (query.weight / hash_page_size > num_cur_items)) {
1390
1430
  this.logDebug(10, "Performing row scan on " + num_cur_items + " items", query);
@@ -1394,7 +1434,9 @@ module.exports = Class.create({
1394
1434
  return config.base_path + '/_data/' + record_id;
1395
1435
  } );
1396
1436
 
1437
+ var rspf = state.perf.begin('row_scan');
1397
1438
  this.getMulti( data_paths, function(err, datas) {
1439
+ rspf.end();
1398
1440
  if (err) return callback(err);
1399
1441
 
1400
1442
  datas.forEach( function(data, idx) {
@@ -1404,11 +1446,15 @@ module.exports = Class.create({
1404
1446
  }
1405
1447
  } );
1406
1448
 
1449
+ state.perf.count('rows_scanned', datas.length);
1450
+ wpf.end();
1407
1451
  callback();
1408
1452
  } ); // getMulti
1409
1453
  } // row scan
1410
1454
  else {
1411
1455
  this.logDebug(10, "Performing '" + mode + "' hash merge on " + num_cur_items + " items", query);
1456
+
1457
+ var hmpf = state.perf.begin('hash_merge');
1412
1458
  this.hashEachPage( path,
1413
1459
  function(items, callback) {
1414
1460
  switch (mode) {
@@ -1430,9 +1476,12 @@ module.exports = Class.create({
1430
1476
  }
1431
1477
  break;
1432
1478
  }
1479
+ state.perf.count('hash_pages', 1);
1433
1480
  callback();
1434
1481
  },
1435
1482
  function(err) {
1483
+ hmpf.end();
1484
+ wpf.end();
1436
1485
  if (mode == 'and') state.record_ids = new_items;
1437
1486
  callback(err);
1438
1487
  }
@@ -1460,6 +1509,7 @@ module.exports = Class.create({
1460
1509
  function(word, callback) {
1461
1510
  // for each word, iterate over record ids
1462
1511
  var keepers = Object.create(null);
1512
+ var wpf = state.perf.begin('literal_' + word);
1463
1513
 
1464
1514
  self.hashEachSync( path_prefix + word,
1465
1515
  function(record_id, raw_value) {
@@ -1495,6 +1545,7 @@ module.exports = Class.create({
1495
1545
  else keepers[record_id] = 1;
1496
1546
  },
1497
1547
  function(err) {
1548
+ wpf.end();
1498
1549
  // If in a subsequent word pass, make sure all temp_results
1499
1550
  // ids are still matched in the latest word
1500
1551
  if (temp_idx > 0) self.mergeIndex( temp_results, keepers, 'and' );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-storage",
3
- "version": "3.0.21",
3
+ "version": "3.0.24",
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
@@ -893,6 +893,7 @@ module.exports = Class.create({
893
893
  },
894
894
  function() {
895
895
  // all locks released
896
+ self.logDebug(9, "All locks released.");
896
897
  callback();
897
898
  }
898
899
  ); // whilst
package/transaction.js CHANGED
@@ -638,14 +638,17 @@ module.exports = Class.create({
638
638
  }
639
639
 
640
640
  // complete, unlock and remove transaction from memory
641
- self.unlock( 'C|'+path );
642
- self._transUnlock(path);
643
641
  self.transactions[path].keys = {}; // release memory
644
642
  self.transactions[path].values = {}; // release memory
645
643
  self.transactions[path].queue = []; // release memory
646
644
  delete self.transactions[path];
647
645
 
648
646
  self.logDebug(3, "Transaction rollback complete: " + trans.id, { path: path });
647
+
648
+ // unlock at the VERY end, as a new transaction may be waiting on the same path
649
+ self.unlock( 'C|'+path );
650
+ self._transUnlock(path);
651
+
649
652
  callback();
650
653
  }); // fs.unlink
651
654
  } // done with log