pixl-server-storage 4.1.0 → 4.2.0
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 +35 -4
- package/docs/API.md +58 -0
- package/docs/Indexer.md +62 -0
- package/docs/Transactions.md +102 -60
- package/engines/Filesystem.js +59 -27
- package/engines/Hybrid.js +104 -2
- package/engines/Postgres.js +291 -23
- package/engines/Redis.js +262 -28
- package/engines/RedisCluster.js +152 -28
- package/engines/S3.js +53 -25
- package/engines/SQLite.js +253 -21
- package/package.json +1 -1
- package/storage.js +63 -1
- package/test/config.json +4 -0
- package/test/data/backups/backup-2026-06-14-15-49-56.db.gz +0 -0
- package/test/data/backups/backup-2026-06-14-15-58-00.db.gz +0 -0
- package/test/data/backups/backup-2026-06-14-16-29-30.db.gz +0 -0
- package/test/data/backups/backup-2026-06-14-17-29-36.db.gz +0 -0
- package/test/data/backups/backup-2026-06-14-17-31-49.db.gz +0 -0
- package/test/data/backups/backup-2026-06-14-18-02-10.db.gz +0 -0
- package/test/data/backups/backup-2026-06-14-18-02-55.db.gz +0 -0
- package/test/test-main.js +17 -0
- package/test/test.js +59 -38
- package/transaction.js +105 -3
- package/test/data/backups/backup-2026-02-15-18-10-33.db.gz +0 -0
- package/test/data/backups/backup-2026-02-15-18-10-45.db.gz +0 -0
- package/test/data/backups/backup-2026-02-15-18-10-58.db.gz +0 -0
- package/test/data/backups/backup-2026-02-15-18-11-10.db.gz +0 -0
- package/test/data/backups/backup-2026-02-15-18-11-23.db.gz +0 -0
- package/test/data/backups/backup-2026-02-15-18-11-35.db.gz +0 -0
- package/test/data/backups/backup-2026-04-05-20-23-56.db.gz +0 -0
package/test/test.js
CHANGED
|
@@ -67,44 +67,54 @@ module.exports = {
|
|
|
67
67
|
// save ref to storage
|
|
68
68
|
self.storage = server.Storage;
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
var finishStartup = function() {
|
|
71
|
+
// build our test array dynamically
|
|
72
|
+
// we have some repeating tests with different configuration options
|
|
73
|
+
self.tests = self.tests.concat(
|
|
74
|
+
listTests.tests,
|
|
75
|
+
|
|
76
|
+
(self.args.splice || self.args.all || self.args.comprehensive) ?
|
|
77
|
+
listTests.generateSpliceTests() : [],
|
|
78
|
+
|
|
79
|
+
hashTests.tests
|
|
80
|
+
);
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
// now add transaction tests, which enable and init transactions
|
|
83
|
+
self.tests = self.tests.concat( transactionTests.tests );
|
|
77
84
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} );
|
|
85
|
+
// now repeat list and hash tests, with transactions enabled
|
|
86
|
+
// augment test names for clarity
|
|
87
|
+
[].concat( listTests.tests, hashTests.tests ).forEach( function(func) {
|
|
88
|
+
var wrapper = function(test) { func.apply(this, [test]); };
|
|
89
|
+
wrapper.testName = 'Transaction_' + func.name;
|
|
90
|
+
self.tests.push( wrapper );
|
|
91
|
+
} );
|
|
92
|
+
|
|
93
|
+
// finally add indexer tests
|
|
94
|
+
self.tests = self.tests.concat( indexerTests.tests );
|
|
95
|
+
|
|
96
|
+
// log memory usage at end
|
|
97
|
+
self.tests.push( function(test) {
|
|
98
|
+
if (global.gc) { global.gc(); global.gc(); }
|
|
99
|
+
test.ok( true );
|
|
100
|
+
setTimeout( function() {
|
|
101
|
+
self.storage.logDebug(9, "MEMORY USAGE: " + Tools.getTextFromBytes( process.memoryUsage.rss() ), process.memoryUsage() );
|
|
102
|
+
test.done();
|
|
103
|
+
}, 1 );
|
|
104
|
+
} );
|
|
105
|
+
|
|
106
|
+
// startup complete
|
|
107
|
+
// delay this by 1ms so the log is in the correct order (pre-start is async)
|
|
108
|
+
setTimeout( function() { callback(); }, 1 );
|
|
109
|
+
};
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
if (self.storage.engine.unitTestCleanup) {
|
|
112
|
+
self.storage.engine.unitTestCleanup( function(err) {
|
|
113
|
+
if (err) return callback(err);
|
|
114
|
+
finishStartup();
|
|
115
|
+
} );
|
|
116
|
+
}
|
|
117
|
+
else finishStartup();
|
|
108
118
|
} ); // startup
|
|
109
119
|
} ); // delete
|
|
110
120
|
},
|
|
@@ -127,9 +137,20 @@ module.exports = {
|
|
|
127
137
|
|
|
128
138
|
tearDown: function (callback) {
|
|
129
139
|
// clean up
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
var self = this;
|
|
141
|
+
|
|
142
|
+
var finishShutdown = function(cleanup_err) {
|
|
143
|
+
self.server.shutdown( function(shutdown_err) {
|
|
144
|
+
cp.exec("rm -rf transactions " + base_data_dir, function(rm_err) {
|
|
145
|
+
callback( cleanup_err || shutdown_err || rm_err );
|
|
146
|
+
} );
|
|
147
|
+
} );
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (this.storage && this.storage.engine && this.storage.engine.unitTestCleanup) {
|
|
151
|
+
this.storage.engine.unitTestCleanup( finishShutdown );
|
|
152
|
+
}
|
|
153
|
+
else finishShutdown();
|
|
133
154
|
},
|
|
134
155
|
|
|
135
156
|
multiCheck: function(map, storage, callback) {
|
package/transaction.js
CHANGED
|
@@ -80,10 +80,10 @@ var TransStorageFunctions = {
|
|
|
80
80
|
if (trans.keys[key] == 'W') {
|
|
81
81
|
// we've written the key, so fetch our version
|
|
82
82
|
this.logDebug(9, "Pinging Object from transaction: " + key);
|
|
83
|
+
var value = trans.values[key];
|
|
83
84
|
|
|
84
85
|
setImmediate( function() {
|
|
85
86
|
self.logDebug(9, "Head complete: " + key);
|
|
86
|
-
var value = trans.values[key];
|
|
87
87
|
callback( null, {
|
|
88
88
|
mod: value.mod,
|
|
89
89
|
len: value.len
|
|
@@ -121,11 +121,12 @@ var TransStorageFunctions = {
|
|
|
121
121
|
if (trans.keys[key] == 'W') {
|
|
122
122
|
// we've written the key, so fetch our version
|
|
123
123
|
this.logDebug(9, "Fetching Object in transaction: " + key);
|
|
124
|
+
var value = trans.values[key];
|
|
124
125
|
|
|
125
126
|
setImmediate( function() {
|
|
126
|
-
var data =
|
|
127
|
+
var data = value.data;
|
|
127
128
|
self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
|
|
128
|
-
callback(
|
|
129
|
+
callback( null, Tools.copyHash(data, true) );
|
|
129
130
|
} );
|
|
130
131
|
}
|
|
131
132
|
else if (trans.keys[key] == 'D') {
|
|
@@ -700,6 +701,107 @@ module.exports = Class.create({
|
|
|
700
701
|
var num_bytes = 0;
|
|
701
702
|
var pf = this.perf.begin('commit');
|
|
702
703
|
|
|
704
|
+
if (this.engine.commitTransaction) {
|
|
705
|
+
// native engine transaction support replaces our entire rollback-log commit system.
|
|
706
|
+
// The engine is responsible for applying all record changes atomically, using the
|
|
707
|
+
// full transaction object. We still own the outer locks, events, logs and cleanup.
|
|
708
|
+
async.waterfall(
|
|
709
|
+
[
|
|
710
|
+
function(callback) {
|
|
711
|
+
// acquire commit lock
|
|
712
|
+
self.lock( 'C|'+path, true, function() { callback(); } );
|
|
713
|
+
},
|
|
714
|
+
function(callback) {
|
|
715
|
+
// notify listeners that the commit is starting
|
|
716
|
+
self.emit('commitStart', trans);
|
|
717
|
+
|
|
718
|
+
// ask the engine to atomically commit all actions
|
|
719
|
+
self.engine.commitTransaction( trans, callback );
|
|
720
|
+
}
|
|
721
|
+
],
|
|
722
|
+
function(err) {
|
|
723
|
+
// commit complete
|
|
724
|
+
var elapsed = pf.end();
|
|
725
|
+
|
|
726
|
+
if (err) {
|
|
727
|
+
var msg = "Failed to commit transaction: " + path + ": " + err.message;
|
|
728
|
+
self.logError('commit', msg, { id: trans.id });
|
|
729
|
+
|
|
730
|
+
if (err.fatal) {
|
|
731
|
+
// Native engine detected an unsafe transaction outcome. Do not fall
|
|
732
|
+
// through to abortTransaction(), as there is no rollback log to replay.
|
|
733
|
+
var fatal_err = new Error(msg);
|
|
734
|
+
fatal_err.fatal = true;
|
|
735
|
+
fatal_err.originalError = err;
|
|
736
|
+
return self.transFatalError(fatal_err);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Note: in this case unlocking happens in abortTransaction
|
|
740
|
+
return callback( new Error(msg) );
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// emulate the normal per-record put/delete side effects, since native
|
|
744
|
+
// engine commits bypass self.put() and self.delete() for atomicity.
|
|
745
|
+
Object.keys(trans.keys).forEach( function(key) {
|
|
746
|
+
var record_state = trans.keys[key];
|
|
747
|
+
var norm_key = self.normalizeKey(key);
|
|
748
|
+
|
|
749
|
+
if (record_state == 'W') {
|
|
750
|
+
var value = trans.values[key];
|
|
751
|
+
num_bytes += value.len;
|
|
752
|
+
|
|
753
|
+
// update the Storage-level RAM cache, if enabled for this key
|
|
754
|
+
if (self.cacheKeyRegex && norm_key.match(self.cacheKeyRegex)) {
|
|
755
|
+
self.cache[norm_key] = value.data;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
self.logTransaction('put', norm_key, {
|
|
759
|
+
elapsed_ms: 0
|
|
760
|
+
});
|
|
761
|
+
self.emit('put', norm_key, value.data);
|
|
762
|
+
}
|
|
763
|
+
else if (record_state == 'D') {
|
|
764
|
+
// update the Storage-level RAM cache, if enabled for this key
|
|
765
|
+
if (self.cacheKeyRegex && norm_key.match(self.cacheKeyRegex) && (norm_key in self.cache)) {
|
|
766
|
+
delete self.cache[norm_key];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
self.logTransaction('delete', norm_key, {
|
|
770
|
+
elapsed_ms: 0
|
|
771
|
+
});
|
|
772
|
+
self.emit('delete', norm_key);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
self.logDebug(5, "Transaction committed successfully: " + trans.id, { path: path, actions: num_actions });
|
|
777
|
+
self.logTransaction('commit', path, {
|
|
778
|
+
id: trans.id,
|
|
779
|
+
elapsed_ms: elapsed,
|
|
780
|
+
actions: num_actions,
|
|
781
|
+
bytes_written: num_bytes
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// transaction is complete
|
|
785
|
+
delete trans.values; // release memory
|
|
786
|
+
delete self.transactions[path];
|
|
787
|
+
|
|
788
|
+
// enqueue any pending tasks that got added during the transaction
|
|
789
|
+
if (trans.queue.length) {
|
|
790
|
+
trans.queue.forEach( self.enqueue.bind(self) );
|
|
791
|
+
trans.queue = []; // release memory
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
delete trans.keys; // release memory
|
|
795
|
+
|
|
796
|
+
self.emit('commitEnd', trans);
|
|
797
|
+
self.unlock( 'C|'+path );
|
|
798
|
+
self._transUnlock(path);
|
|
799
|
+
callback();
|
|
800
|
+
}
|
|
801
|
+
); // native engine commit
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
703
805
|
async.waterfall(
|
|
704
806
|
[
|
|
705
807
|
function(callback) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|