Skip to content

Monitoring generating insane timestamp #5320

Description

@chaen

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:

  1. An entry is added to the MonitoringReporter
  2. At some point, the records are commited with
  3. 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)
  4. What is eventually called is generateDocs with this slice of records
    def generateDocs(data, withTimeStamp=True):
  5. 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
  6. 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
  7. 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

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions