@@ -7,11 +7,17 @@ const config = require('config')
7
7
const elasticsearch = require ( 'elasticsearch' )
8
8
const _ = require ( 'lodash' )
9
9
const Joi = require ( '@hapi/joi' )
10
+ const { Mutex } = require ( 'async-mutex' )
11
+ const logger = require ( './logger' )
10
12
11
13
AWS . config . region = config . ES . AWS_REGION
12
14
13
15
// Elasticsearch client
14
16
let esClient
17
+ let transactionId
18
+ // Mutex to ensure that only one elasticsearch action is carried out at any given time
19
+ const esClientMutex = new Mutex ( )
20
+ const mutexReleaseMap = { }
15
21
16
22
/**
17
23
* Get Kafka options
@@ -51,6 +57,31 @@ async function getESClient () {
51
57
host
52
58
} )
53
59
}
60
+
61
+ // Patch the transport to enable mutex
62
+ esClient . transport . originalRequest = esClient . transport . request
63
+ esClient . transport . request = async ( params ) => {
64
+ const tId = _ . get ( params . query , 'transactionId' )
65
+ params . query = _ . omit ( params . query , 'transactionId' )
66
+ if ( ! tId || tId !== transactionId ) {
67
+ const release = await esClientMutex . acquire ( )
68
+ mutexReleaseMap [ tId || 'noTransaction' ] = release
69
+ transactionId = tId
70
+ }
71
+ try {
72
+ return await esClient . transport . originalRequest ( params )
73
+ } finally {
74
+ if ( params . method !== 'GET' || ! tId ) {
75
+ const release = mutexReleaseMap [ tId || 'noTransaction' ]
76
+ delete mutexReleaseMap [ tId || 'noTransaction' ]
77
+ transactionId = undefined
78
+ if ( release ) {
79
+ release ( )
80
+ }
81
+ }
82
+ }
83
+ }
84
+
54
85
return esClient
55
86
}
56
87
@@ -71,50 +102,66 @@ function validProperties (payload, keys) {
71
102
/**
72
103
* Function to get user from es
73
104
* @param {String } userId
105
+ * @param {String } transactionId
74
106
* @returns {Object } user
75
107
*/
76
- async function getUser ( userId ) {
108
+ async function getUser ( userId , transactionId ) {
77
109
const client = await getESClient ( )
78
- return client . getSource ( { index : config . get ( 'ES.USER_INDEX' ) , type : config . get ( 'ES.USER_TYPE' ) , id : userId } )
110
+ const user = await client . get ( { index : config . get ( 'ES.USER_INDEX' ) , type : config . get ( 'ES.USER_TYPE' ) , id : userId , transactionId } )
111
+ return { seqNo : user . _seq_no , primaryTerm : user . _primary_term , user : user . _source }
79
112
}
80
113
81
114
/**
82
115
* Function to update es user
83
116
* @param {String } userId
117
+ * @param {Number } seqNo
118
+ * @param {Number } primaryTerm
119
+ * @param {String } transactionId
84
120
* @param {Object } body
85
121
*/
86
- async function updateUser ( userId , body ) {
122
+ async function updateUser ( userId , body , seqNo , primaryTerm , transactionId ) {
87
123
const client = await getESClient ( )
88
124
await client . update ( {
89
125
index : config . get ( 'ES.USER_INDEX' ) ,
90
126
type : config . get ( 'ES.USER_TYPE' ) ,
91
127
id : userId ,
92
- body : { doc : body }
128
+ transactionId,
129
+ body : { doc : body } ,
130
+ if_seq_no : seqNo ,
131
+ if_primary_term : primaryTerm
93
132
} )
94
133
}
95
134
96
135
/**
97
136
* Function to get org from es
98
137
* @param {String } organizationId
138
+ * @param {String } transactionId
99
139
* @returns {Object } organization
100
140
*/
101
- async function getOrg ( organizationId ) {
141
+ async function getOrg ( organizationId , transactionId ) {
102
142
const client = await getESClient ( )
103
- return client . getSource ( { index : config . get ( 'ES.ORGANIZATION_INDEX' ) , type : config . get ( 'ES.ORGANIZATION_TYPE' ) , id : organizationId } )
143
+ const org = await client . get ( { index : config . get ( 'ES.ORGANIZATION_INDEX' ) , type : config . get ( 'ES.ORGANIZATION_TYPE' ) , id : organizationId , transactionId } )
144
+ return { seqNo : org . _seq_no , primaryTerm : org . _primary_term , org : org . _source }
104
145
}
105
146
106
147
/**
107
148
* Function to update es organization
108
149
* @param {String } organizationId
150
+ * @param {Number } seqNo
151
+ * @param {Number } primaryTerm
152
+ * @param {String } transactionId
109
153
* @param {Object } body
110
154
*/
111
- async function updateOrg ( organizationId , body ) {
155
+ async function updateOrg ( organizationId , body , seqNo , primaryTerm , transactionId ) {
112
156
const client = await getESClient ( )
113
157
await client . update ( {
114
158
index : config . get ( 'ES.ORGANIZATION_INDEX' ) ,
115
159
type : config . get ( 'ES.ORGANIZATION_TYPE' ) ,
116
160
id : organizationId ,
117
- body : { doc : body }
161
+ transactionId,
162
+ body : { doc : body } ,
163
+ if_seq_no : seqNo ,
164
+ if_primary_term : primaryTerm
118
165
} )
119
166
}
120
167
@@ -130,6 +177,21 @@ function getErrorWithStatus (message, statusCode) {
130
177
return error
131
178
}
132
179
180
+ /**
181
+ * Ensure the esClient mutex is released
182
+ * @param {String } tId transactionId
183
+ */
184
+ function checkEsMutexRelease ( tId ) {
185
+ if ( tId === transactionId ) {
186
+ const release = mutexReleaseMap [ tId ]
187
+ delete mutexReleaseMap [ tId ]
188
+ transactionId = undefined
189
+ if ( release ) {
190
+ release ( )
191
+ }
192
+ }
193
+ }
194
+
133
195
module . exports = {
134
196
getKafkaOptions,
135
197
getESClient,
@@ -138,5 +200,6 @@ module.exports = {
138
200
updateUser,
139
201
getOrg,
140
202
updateOrg,
141
- getErrorWithStatus
203
+ getErrorWithStatus,
204
+ checkEsMutexRelease
142
205
}
0 commit comments