kuzzle 2.14.14 → 2.15.2

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.
@@ -37,6 +37,7 @@ class AdminController extends NativeController {
37
37
  'loadFixtures',
38
38
  'loadMappings',
39
39
  'loadSecurities',
40
+ 'refreshIndexCache',
40
41
  'resetCache',
41
42
  'resetDatabase',
42
43
  'resetSecurity',
@@ -46,6 +47,10 @@ class AdminController extends NativeController {
46
47
  this.shuttingDown = false;
47
48
  }
48
49
 
50
+ async refreshIndexCache () {
51
+ await global.kuzzle.ask('core:storage:public:cache:refresh');
52
+ }
53
+
49
54
  /**
50
55
  * Reset Redis cache
51
56
  */
@@ -515,11 +515,7 @@ class DocumentController extends NativeController {
515
515
  document);
516
516
  }
517
517
 
518
- if (!source) {
519
- return { _id: document._id };
520
- }
521
-
522
- return document;
518
+ return source ? document : { _id: document._id };
523
519
  }
524
520
 
525
521
  /**
@@ -0,0 +1,140 @@
1
+ import '../types';
2
+ export declare type SerializedIdCard = {
3
+ id: string;
4
+ ip: string;
5
+ birthdate: number;
6
+ topology: string[];
7
+ };
8
+ export declare class IdCard {
9
+ /**
10
+ * Node unique identifier
11
+ *
12
+ * @example
13
+ *
14
+ * knode-pensive-einstein-844221
15
+ */
16
+ private id;
17
+ /**
18
+ * Node IP address
19
+ */
20
+ private ip;
21
+ /**
22
+ * Node creation timestamp
23
+ */
24
+ private birthdate;
25
+ /**
26
+ * Node known topology composed of node IDs
27
+ *
28
+ * Set<node-id>
29
+ */
30
+ topology: Set<string>;
31
+ constructor({ id, ip, birthdate, topology }: SerializedIdCard);
32
+ serialize(): SerializedIdCard;
33
+ static unserialize(serialized: SerializedIdCard): IdCard;
34
+ }
35
+ /**
36
+ * Handles the ID Key stored in Redis, holding node information
37
+ */
38
+ export declare class ClusterIdCardHandler {
39
+ /**
40
+ * Node instance. Represents the local node.
41
+ */
42
+ private node;
43
+ /**
44
+ * Local node ID Card
45
+ */
46
+ private idCard;
47
+ /**
48
+ * Local node IP address
49
+ */
50
+ private ip;
51
+ /**
52
+ * Delay for refreshing the ID Card. The heartbeat timer is run on this delay
53
+ * and the node ID Card should be available on Redis otherwise it will be evicted.
54
+ */
55
+ private refreshDelay;
56
+ /**
57
+ * Multiplier used to ensure the node has enough time to refresh it's ID Card
58
+ * before the ID Card refresh delay
59
+ */
60
+ private refreshMultiplier;
61
+ /**
62
+ * Worker thread in charge of refreshing the ID Card once the node has started
63
+ */
64
+ private refreshWorker;
65
+ /**
66
+ * Hold the timer in charge of refreshing the ID Card before the worker starts
67
+ */
68
+ private refreshTimer;
69
+ /**
70
+ * Local node ID
71
+ */
72
+ private nodeId;
73
+ /**
74
+ * Local node Redis key
75
+ */
76
+ private nodeIdKey;
77
+ /**
78
+ * Flag to prevent updating the id card if it has been disposed.
79
+ * Prevents race condition if a topology update occurs at the same time as
80
+ * the id card is been disposed because the node is evicting itself from the
81
+ * cluster
82
+ */
83
+ private disposed;
84
+ constructor(node: any);
85
+ /**
86
+ * Generates and reserves a unique ID for this node instance.
87
+ * Makes sure that the ID is not already taken by another node instance.
88
+ */
89
+ createIdCard(): Promise<void>;
90
+ /**
91
+ * Helper method to mock worker instantiation in unit tests
92
+ */
93
+ private constructWorker;
94
+ /**
95
+ * Start refreshing the ID Card before the worker starts to ensure the ID Card
96
+ * is refreshed.
97
+ *
98
+ * Once the worker starts, this timer will be stopped.
99
+ */
100
+ private startTemporaryRefresh;
101
+ dispose(): Promise<void>;
102
+ /**
103
+ * Retrieves the ID cards from other nodes
104
+ *
105
+ * Each node store it's ID Card under a specific key name. When Redis database
106
+ * is growing, searching for those keys can be quite expensive and can slow down
107
+ * the handshake process.
108
+ *
109
+ * We are storing a set containing ID Card's keys under a set so when a new node
110
+ * is started, it can directly get the other nodes ID Cards with SMEMBERS and
111
+ * then MGET instead of using SCAN.
112
+ *
113
+ * When a new node retrieve the ID Card's keys from the set, it try to get them
114
+ * with MGET, those who cannot be retrieved are expired ID Cards so the node update
115
+ * the set accordingly.
116
+ *
117
+ * @return {Array.<IdCard>}
118
+ */
119
+ getRemoteIdCards(): Promise<IdCard[]>;
120
+ /**
121
+ * Adds a remote node IdCard to the node known topology
122
+ */
123
+ addNode(id: string): Promise<void>;
124
+ /**
125
+ * Removes a remote node IdCard from the node known topology
126
+ */
127
+ removeNode(id: string): Promise<void>;
128
+ /**
129
+ * Store the key under which this node ID Card is stored inside the set.
130
+ *
131
+ * This set is an index to retrieve ID Cards faster.
132
+ */
133
+ addIdCardToIndex(): Promise<void>;
134
+ /**
135
+ * Saves the local node IdCard into Redis
136
+ *
137
+ * @returns True if the key was set
138
+ */
139
+ private save;
140
+ }
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /*
2
3
  * Kuzzle, a backend software, self-hostable and ready to use
3
4
  * to power modern apps
@@ -18,188 +19,234 @@
18
19
  * See the License for the specific language governing permissions and
19
20
  * limitations under the License.
20
21
  */
21
-
22
- 'use strict';
23
-
24
- const { generateRandomName } = require('../util/name-generator');
25
- const { Worker } = require('worker_threads');
26
-
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ClusterIdCardHandler = exports.IdCard = void 0;
27
+ const name_generator_1 = require("../util/name-generator");
28
+ const worker_threads_1 = require("worker_threads");
29
+ const bluebird_1 = __importDefault(require("bluebird"));
30
+ require("../types");
27
31
  const REDIS_PREFIX = '{cluster/node}/';
28
-
29
- /**
30
- * @typedef {IdCard}
31
- */
32
+ const REDIS_ID_CARDS_INDEX = REDIS_PREFIX + 'id-cards-index';
32
33
  class IdCard {
33
- /**
34
- * @param {Object} obj - contains the ID card description
35
- * @param {string} obj.id - node identifier
36
- * @param {string} obj.ip - node IP address
37
- * @param {Number} obj.birthdate - node creation timestamp
38
- * @param {Array.<string>} obj.topology - node's known topology
39
- */
40
- constructor (obj) {
41
- this.id = obj.id;
42
- this.ip = obj.ip;
43
- this.birthdate = obj.birthdate;
44
- this.topology = new Set(obj.topology);
45
- }
46
-
47
- serialize () {
48
- return JSON.stringify({
49
- birthdate: this.birthdate,
50
- id: this.id,
51
- ip: this.ip,
52
- topology: Array.from(this.topology),
53
- });
54
- }
55
-
56
- static unserialize (value) {
57
- return new IdCard(JSON.parse(value));
58
- }
34
+ constructor({ id, ip, birthdate, topology }) {
35
+ this.id = id;
36
+ this.ip = ip;
37
+ this.birthdate = birthdate;
38
+ this.topology = new Set(topology);
39
+ }
40
+ serialize() {
41
+ return {
42
+ birthdate: this.birthdate,
43
+ id: this.id,
44
+ ip: this.ip,
45
+ topology: Array.from(this.topology),
46
+ };
47
+ }
48
+ static unserialize(serialized) {
49
+ return new IdCard(serialized);
50
+ }
59
51
  }
60
-
52
+ exports.IdCard = IdCard;
61
53
  /**
62
54
  * Handles the ID Key stored in Redis, holding node information
63
55
  */
64
56
  class ClusterIdCardHandler {
65
- constructor (node) {
66
- this.node = node;
67
- this.idCard = null;
68
- this.ip = node.ip;
69
- this.refreshDelay = node.heartbeatDelay;
70
- this.refreshWorker = null;
71
- this.nodeId = null;
72
- this.nodeIdKey = null;
73
-
74
- // Flag to prevent updating the id card if it has been disposed.
75
- // Prevents race condition if a topology update occurs at the same time as
76
- // the id card is been disposed because the node is evicting itself from the
77
- // cluster
78
- this.disposed = false;
79
- }
80
-
81
- /**
82
- * Generates and reserves a unique ID for this node instance.
83
- * Makes sure that the ID is not already taken by another node instance.
84
- *
85
- * @return {void}
86
- */
87
- async createIdCard () {
88
- let reserved;
89
-
90
- do {
91
- this.nodeId = generateRandomName('knode');
92
- this.nodeIdKey = `${REDIS_PREFIX}${this.nodeId}`;
93
- this.idCard = new IdCard({
94
- birthdate: Date.now(),
95
- id: this.nodeId,
96
- ip: this.ip,
97
- topology: [],
98
- });
99
-
100
- reserved = await this._save({ creation: true });
101
- } while (!reserved);
102
-
103
- this.refreshWorker = this._constructWorker(`${__dirname}/workers/IDCardRenewer.js`);
104
- this.refreshWorker.unref();
105
-
106
- this.refreshWorker.on('message', async message => {
107
- if (message.error) {
108
- await this.node.evictSelf(message.error);
109
- }
110
- });
111
-
112
- // Transfer informations to the worker
113
- this.refreshWorker.postMessage({
114
- action: 'start', // start the worker
115
- kuzzle: {
116
- config: global.kuzzle.config,
117
- id: global.kuzzle.id,
118
- },
119
- nodeIdKey: this.nodeIdKey,
120
- // Used to configure a redis the same way as the Cache Engine does
121
- redis: {
122
- config: global.kuzzle.config.services.internalCache,
123
- name: 'internal_adapter',
124
- },
125
- refreshDelay: this.refreshDelay,
126
- });
127
- }
128
-
129
- // Used to Mock the creation of a worker for the tests
130
- _constructWorker(path) {
131
- return new Worker(path);
132
- }
133
-
134
- async dispose () {
135
- this.disposed = true;
136
- if (this.refreshWorker) {
137
- this.refreshWorker.postMessage({action: 'dispose'});
57
+ constructor(node) {
58
+ /**
59
+ * Local node ID Card
60
+ */
61
+ this.idCard = null;
62
+ /**
63
+ * Multiplier used to ensure the node has enough time to refresh it's ID Card
64
+ * before the ID Card refresh delay
65
+ */
66
+ this.refreshMultiplier = 2;
67
+ /**
68
+ * Worker thread in charge of refreshing the ID Card once the node has started
69
+ */
70
+ this.refreshWorker = null;
71
+ /**
72
+ * Hold the timer in charge of refreshing the ID Card before the worker starts
73
+ */
74
+ this.refreshTimer = null;
75
+ /**
76
+ * Local node ID
77
+ */
78
+ this.nodeId = null;
79
+ /**
80
+ * Local node Redis key
81
+ */
82
+ this.nodeIdKey = null;
83
+ /**
84
+ * Flag to prevent updating the id card if it has been disposed.
85
+ * Prevents race condition if a topology update occurs at the same time as
86
+ * the id card is been disposed because the node is evicting itself from the
87
+ * cluster
88
+ */
89
+ this.disposed = false;
90
+ this.node = node;
91
+ this.ip = node.ip;
92
+ this.refreshDelay = node.heartbeatDelay;
93
+ }
94
+ /**
95
+ * Generates and reserves a unique ID for this node instance.
96
+ * Makes sure that the ID is not already taken by another node instance.
97
+ */
98
+ async createIdCard() {
99
+ let reserved = false;
100
+ do {
101
+ this.nodeId = (0, name_generator_1.generateRandomName)('knode');
102
+ this.nodeIdKey = `${REDIS_PREFIX}${this.nodeId}`;
103
+ this.idCard = new IdCard({
104
+ birthdate: Date.now(),
105
+ id: this.nodeId,
106
+ ip: this.ip,
107
+ topology: [],
108
+ });
109
+ reserved = await this.save({ creation: true });
110
+ } while (!reserved);
111
+ await this.addIdCardToIndex();
112
+ this.refreshWorker = this.constructWorker(`${__dirname}/workers/IDCardRenewer.js`);
113
+ this.refreshWorker.unref();
114
+ this.refreshWorker.on('message', async (message) => {
115
+ if (message.error) {
116
+ await this.node.evictSelf(message.error);
117
+ }
118
+ });
119
+ // Transfer informations to the worker
120
+ this.refreshWorker.postMessage({
121
+ action: 'start',
122
+ kuzzle: {
123
+ config: global.kuzzle.config,
124
+ id: global.kuzzle.id,
125
+ },
126
+ nodeIdKey: this.nodeIdKey,
127
+ // Used to configure a redis the same way as the Cache Engine does
128
+ redis: {
129
+ config: global.kuzzle.config.services.internalCache,
130
+ name: 'internal_adapter',
131
+ },
132
+ refreshDelay: this.refreshDelay,
133
+ refreshMultiplier: this.refreshMultiplier,
134
+ });
135
+ this.startTemporaryRefresh();
136
+ }
137
+ /**
138
+ * Helper method to mock worker instantiation in unit tests
139
+ */
140
+ constructWorker(path) {
141
+ return new worker_threads_1.Worker(path);
142
+ }
143
+ /**
144
+ * Start refreshing the ID Card before the worker starts to ensure the ID Card
145
+ * is refreshed.
146
+ *
147
+ * Once the worker starts, this timer will be stopped.
148
+ */
149
+ startTemporaryRefresh() {
150
+ this.refreshTimer = setInterval(async () => {
151
+ try {
152
+ await this.save();
153
+ }
154
+ catch (error) {
155
+ global.kuzzle.log.error(`An error occurred while refreshing the ID card during WorkerThread startup: ${error}`);
156
+ }
157
+ }, this.refreshDelay * this.refreshMultiplier);
158
+ this.refreshWorker.on('message', ({ initialized }) => {
159
+ if (initialized) {
160
+ clearInterval(this.refreshTimer);
161
+ this.refreshTimer = null;
162
+ }
163
+ });
164
+ }
165
+ async dispose() {
166
+ this.disposed = true;
167
+ if (this.refreshWorker) {
168
+ this.refreshWorker.postMessage({ action: 'dispose' });
169
+ }
170
+ }
171
+ /**
172
+ * Retrieves the ID cards from other nodes
173
+ *
174
+ * Each node store it's ID Card under a specific key name. When Redis database
175
+ * is growing, searching for those keys can be quite expensive and can slow down
176
+ * the handshake process.
177
+ *
178
+ * We are storing a set containing ID Card's keys under a set so when a new node
179
+ * is started, it can directly get the other nodes ID Cards with SMEMBERS and
180
+ * then MGET instead of using SCAN.
181
+ *
182
+ * When a new node retrieve the ID Card's keys from the set, it try to get them
183
+ * with MGET, those who cannot be retrieved are expired ID Cards so the node update
184
+ * the set accordingly.
185
+ *
186
+ * @return {Array.<IdCard>}
187
+ */
188
+ async getRemoteIdCards() {
189
+ const idCards = [];
190
+ let keys = await global.kuzzle.ask('core:cache:internal:execute', 'smembers', REDIS_ID_CARDS_INDEX);
191
+ keys = keys.filter(nodeIdKey => nodeIdKey !== this.nodeIdKey);
192
+ if (keys.length === 0) {
193
+ return idCards;
194
+ }
195
+ const rawIdCards = await global.kuzzle.ask('core:cache:internal:mget', keys);
196
+ const expiredIdCards = [];
197
+ for (let i = 0; i < keys.length; i++) {
198
+ // filter keys that might have expired between the key search and their
199
+ // values retrieval
200
+ if (rawIdCards[i] !== null) {
201
+ idCards.push(IdCard.unserialize(JSON.parse(rawIdCards[i])));
202
+ }
203
+ else {
204
+ expiredIdCards.push(keys[i]);
205
+ }
206
+ }
207
+ // Clean expired ID Card's keys in the index
208
+ await bluebird_1.default.map(expiredIdCards, idCardKey => {
209
+ return global.kuzzle.ask('core:cache:internal:execute', 'srem', REDIS_ID_CARDS_INDEX, idCardKey);
210
+ });
211
+ return idCards;
138
212
  }
139
- }
140
-
141
- /**
142
- * Retrieves the ID cards from other nodes
143
- * @return {Array.<IdCard>}
144
- */
145
- async getRemoteIdCards () {
146
- const result = [];
147
-
148
- let keys = await global.kuzzle.ask(
149
- 'core:cache:internal:searchKeys',
150
- `${REDIS_PREFIX}*`);
151
-
152
- keys = keys.filter(nodeIdKey => nodeIdKey !== this.nodeIdKey);
153
-
154
- if (keys.length === 0) {
155
- return result;
213
+ /**
214
+ * Adds a remote node IdCard to the node known topology
215
+ */
216
+ async addNode(id) {
217
+ if (this.disposed || this.idCard.topology.has(id)) {
218
+ return;
219
+ }
220
+ this.idCard.topology.add(id);
221
+ await this.save();
156
222
  }
157
-
158
- const values = await global.kuzzle.ask('core:cache:internal:mget', keys);
159
-
160
- for (let i = 0; i < keys.length; i++) {
161
- // filter keys that might have expired between the key search and their
162
- // values retrieval
163
- if (values[i] !== null) {
164
- result.push(IdCard.unserialize(values[i]));
165
- }
223
+ /**
224
+ * Removes a remote node IdCard from the node known topology
225
+ */
226
+ async removeNode(id) {
227
+ if (!this.disposed && this.idCard.topology.delete(id)) {
228
+ await this.save();
229
+ }
166
230
  }
167
-
168
- return result;
169
- }
170
-
171
- /**
172
- * Adds a remote node IdCard to the node known topology
173
- */
174
- async addNode (id) {
175
- if (this.disposed || this.idCard.topology.has(id)) {
176
- return;
231
+ /**
232
+ * Store the key under which this node ID Card is stored inside the set.
233
+ *
234
+ * This set is an index to retrieve ID Cards faster.
235
+ */
236
+ async addIdCardToIndex() {
237
+ await global.kuzzle.ask('core:cache:internal:execute', 'sadd', REDIS_ID_CARDS_INDEX, this.nodeIdKey);
177
238
  }
178
-
179
- this.idCard.topology.add(id);
180
-
181
- await this._save();
182
- }
183
-
184
- /**
185
- * Removes a remote node IdCard from the node known topology
186
- */
187
- async removeNode (id) {
188
- if (!this.disposed && this.idCard.topology.delete(id)) {
189
- await this._save();
239
+ /**
240
+ * Saves the local node IdCard into Redis
241
+ *
242
+ * @returns True if the key was set
243
+ */
244
+ async save({ creation } = { creation: false }) {
245
+ if (!this.idCard) {
246
+ return false;
247
+ }
248
+ return await global.kuzzle.ask('core:cache:internal:store', this.nodeIdKey, JSON.stringify(this.idCard.serialize()), { onlyIfNew: creation, ttl: this.refreshDelay * this.refreshMultiplier });
190
249
  }
191
- }
192
-
193
- /**
194
- * Saves the local node IdCard into Redis
195
- */
196
- _save ({ creation } = { creation: false }) {
197
- return global.kuzzle.ask(
198
- 'core:cache:internal:store',
199
- this.nodeIdKey,
200
- this.idCard.serialize(),
201
- { onlyIfNew: creation, ttl: this.refreshDelay * 3 });
202
- }
203
250
  }
204
-
205
- module.exports = { ClusterIdCardHandler, IdCard };
251
+ exports.ClusterIdCardHandler = ClusterIdCardHandler;
252
+ //# sourceMappingURL=idCardHandler.js.map
@@ -474,21 +474,21 @@ class ClusterNode {
474
474
  // to prevent race conditions (other nodes attempting to connect to this
475
475
  // node while it's still initializing)
476
476
  await this.idCardHandler.createIdCard();
477
- global.kuzzle.log.debug('[CLUSTER] ID Card created');
477
+ debug('[CLUSTER] ID Card created');
478
478
 
479
479
  this.nodeId = this.idCardHandler.nodeId;
480
480
 
481
481
  await this.startHeartbeat();
482
- global.kuzzle.log.debug('[CLUSTER] Start heartbeat');
482
+ debug('[CLUSTER] Start heartbeat');
483
483
 
484
484
  let retried = false;
485
485
  let fullState = null;
486
486
  let nodes;
487
487
 
488
- global.kuzzle.log.debug('[CLUSTER] Start retrieving full state..');
488
+ debug('[CLUSTER] Start retrieving full state..');
489
489
  do {
490
490
  nodes = await this.idCardHandler.getRemoteIdCards();
491
- global.kuzzle.log.debug(`[CLUSTER] ${nodes.length} remote nodes discovered`);
491
+ debug('[CLUSTER] %s remote nodes discovered', nodes.length);
492
492
 
493
493
  // No other nodes detected = no handshake required
494
494
  if (nodes.length === 0) {
@@ -511,7 +511,7 @@ class ClusterNode {
511
511
  this.remoteNodes.set(id, subscriber);
512
512
  return subscriber.init();
513
513
  });
514
- global.kuzzle.log.debug('[CLUSTER] Successfully subscribed to nodes');
514
+ debug('[CLUSTER] Successfully subscribed to nodes');
515
515
 
516
516
  fullState = await this.command.getFullState(nodes);
517
517
 
@@ -541,18 +541,18 @@ class ClusterNode {
541
541
  }
542
542
  }
543
543
  while (fullState === null);
544
- global.kuzzle.log.debug('[CLUSTER] Fullstate retrieved, loading into node..');
544
+ debug('[CLUSTER] Fullstate retrieved, loading into node..');
545
545
 
546
546
  await this.fullState.loadFullState(fullState);
547
547
  this.activity = fullState.activity
548
548
  ? fullState.activity
549
549
  : this.activity;
550
550
 
551
- global.kuzzle.log.debug('[CLUSTER] Fullstate loaded.');
551
+ debug('[CLUSTER] Fullstate loaded.');
552
552
 
553
553
  const handshakeResponses = await this.command.broadcastHandshake(nodes);
554
554
 
555
- global.kuzzle.log.debug('[CLUSTER] Successful handshakes with other nodes.');
555
+ debug('[CLUSTER] Successful handshakes with other nodes.');
556
556
 
557
557
  // Update subscribers: start synchronizing, or unsubscribes from nodes who
558
558
  // didn't respond
@@ -753,6 +753,10 @@ class ClusterNode {
753
753
  * @return {void}
754
754
  */
755
755
  registerEvents () {
756
+ global.kuzzle.on(
757
+ 'admin:afterRefreshIndexCache',
758
+ () => this.onIndexCacheRefreshed());
759
+
756
760
  global.kuzzle.on(
757
761
  'core:realtime:room:create:after',
758
762
  payload => this.onNewRealtimeRoom(payload));
@@ -1123,6 +1127,13 @@ class ClusterNode {
1123
1127
  this.publisher.send('Shutdown', {});
1124
1128
  }
1125
1129
 
1130
+ /**
1131
+ * Triggered when the index cache has been manually refreshed
1132
+ */
1133
+ onIndexCacheRefreshed () {
1134
+ this.publisher.send('RefreshIndexCache', {});
1135
+ }
1136
+
1126
1137
  /**
1127
1138
  * Returns the total number of subscribers on the cluster for the provided
1128
1139
  * room
@@ -177,3 +177,7 @@ message ClusterWideEvent {
177
177
  string event = 2;
178
178
  string payload = 3;
179
179
  }
180
+
181
+ message RefreshIndexCache {
182
+ uint64 messageId = 1;
183
+ }