We regularly have entries for all types of monitoring that fails in being inserted in ElasticSearch with errors like
BulkIndexError: ('259 document(s) failed to index.', [{u'index': {u'status': 400, u'_type': u'_doc', u'_index': u'lhcb-certification_wmshistory_index-2021-08-10', u'error': {u'caused_by': {u'caused_by': {u'reason': u'date_time_parse_exception: Failed to parse with all enclosed parsers', u'type': u'date_time_parse_exception'}, u'reason': u'failed to parse date field [1627194654000000000000000000000000000000000000000000000000000000000000000000000000000000] with format [strict_date_optional_time||epoch_millis]',
What happens is the following:
- An entry is added to the
MonitoringReporter
|
def addRecord(self, rec): |
- At some point, the records are commited with
- In this method, we take a slice of all the records to be committed, and pass it to
MonitoringDB for treatment
|
recordsToSend = documents[:self.__maxRecordsInABundle] |
|
retVal = monitoringDB.put(recordsToSend, self.__monitoringType) |
- What is eventually called is
generateDocs with this slice of records
|
def generateDocs(data, withTimeStamp=True): |
- In this method, we manipulate the
timestamp. Either create it, or convert it, or, if it is Epoch, multiply it by 1000
|
try: |
|
if isinstance(timestamp, datetime): |
|
doc['timestamp'] = int(timestamp.strftime('%s')) * 1000 |
|
elif isinstance(timestamp, six.string_types): |
|
timeobj = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S.%f') |
|
doc['timestamp'] = int(timeobj.strftime('%s')) * 1000 |
|
else: # we assume the timestamp is an unix epoch time (integer). |
|
doc['timestamp'] = timestamp * 1000 |
|
except (TypeError, ValueError) as e: |
|
# in case we are not able to convert the timestamp to epoch time.... |
|
sLog.error("Wrong timestamp", e) |
|
doc['timestamp'] = int(Time.toEpoch()) * 1000 |
- If ever, the DB insertion fails, for a reason or another, we try to send them to the MessageQueue, and if that fails, keep them for the next attempt
|
if retVal['OK']: |
|
recordSent += len(recordsToSend) |
|
del documents[:self.__maxRecordsInABundle] |
|
gLogger.verbose("%d records inserted to MonitoringDB" % (recordSent)) |
|
else: |
|
if mqProducer is not None: |
|
res = self.publishRecords(recordsToSend, mqProducer) |
|
# if we managed to publish the records we can delete from the list |
|
if res['OK']: |
|
recordSent += len(recordsToSend) |
|
del documents[:self.__maxRecordsInABundle] |
|
else: |
|
return res # in case of MQ problem |
- But at the next attempt, you will go through the same logic, in particular step 5. So you end up multiplying the
timestamp again, and that's how you end up with crazy records like the one above
The reason this happens is that the original dictionary is updated. It is illustrated by the following example
record1 = {'timestamp':1}
record2 = {'timestamp':2}
records = [record1, record2]
# prints Before commit [{'timestamp': 1}, {'timestamp': 2}]
print("Before commit ", records)
# equivalent of MonitoringReporter.commit
recordsToSend = records[:1]
# equivalent of generateDocs
for doc in recordsToSend:
doc['timestamp'] = doc['timestamp']*1000
# After commit [{'timestamp': 1000}, {'timestamp': 2}]
print("After commit ", records)
While there is maybe a good reason for multiplying epoch by 1000 (is there really ?), we should:
- Not update the document in place
- Not systematically multiply by 1000, but only if it is needed
I did not yet investigate which solution is the best, nor which are the consequences of doing so. Not sure I am the best person to do it though.
But frankly, this monitoring is again a piece of art when it comes to over designing and complexifying things
We regularly have entries for all types of monitoring that fails in being inserted in ElasticSearch with errors like
What happens is the following:
MonitoringReporterDIRAC/src/DIRAC/MonitoringSystem/Client/MonitoringReporter.py
Line 99 in cbce4f0
DIRAC/src/DIRAC/MonitoringSystem/Client/MonitoringReporter.py
Line 127 in cbce4f0
MonitoringDBfor treatmentDIRAC/src/DIRAC/MonitoringSystem/Client/MonitoringReporter.py
Lines 147 to 148 in cbce4f0
generateDocswith this slice of recordsDIRAC/src/DIRAC/Core/Utilities/ElasticSearchDB.py
Line 47 in cbce4f0
timestamp. Either create it, or convert it, or, if it is Epoch, multiply it by 1000DIRAC/src/DIRAC/Core/Utilities/ElasticSearchDB.py
Lines 64 to 75 in cbce4f0
DIRAC/src/DIRAC/MonitoringSystem/Client/MonitoringReporter.py
Lines 149 to 161 in cbce4f0
timestampagain, and that's how you end up with crazy records like the one aboveThe reason this happens is that the original dictionary is updated. It is illustrated by the following example
While there is maybe a good reason for multiplying epoch by 1000 (is there really ?), we should:
I did not yet investigate which solution is the best, nor which are the consequences of doing so. Not sure I am the best person to do it though.
But frankly, this monitoring is again a piece of art when it comes to over designing and complexifying things