Skip to content

[ML] Refactor model snapshot writing to use concurrent writer #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 14, 2018
Merged

[ML] Refactor model snapshot writing to use concurrent writer #12

merged 4 commits into from
Mar 14, 2018

Conversation

dimitris-athanasiou
Copy link
Contributor

This refactors the writing of the model snapshot document and
model size stats document out of CJsonOutputWriter.

It achieves the following improvements:

  • model snapshot documents do not have to be queued up. They
    can be written directly leveraging the safe concurrent writer
    that was implemented for forecasts. This minimizes the delay
    of a model snapshot being written which in failure cases may
    result into having a more recent snapshot available.
  • better separation of concerns and a smaller CJsonOutputWriter class.
  • simplifies adding new fields in the model snapshot document as
    needed for writing the minimum compatible version.

This refactors the writing of the model snapshot document and
model size stats document out of CJsonOutputWriter.

It achieves the following improvements:

- model snapshot documents do not have to be queued up. They
can be written directly leveraging the safe concurrent writer
that was implemented for forecasts. This minimizes the delay
of a model snapshot being written which in failure cases may
result into having a more recent snapshot available.
- better separation of concerns and a smaller CJsonOutputWriter class.
- simplifies adding new fields in the model snapshot document as
needed for writing the minimum compatible version.
Copy link
Contributor

@tveasey tveasey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good idea. Left some additional suggestions and corrections for typos.

const model::CResourceMonitor::SResults &modelSizeStats,
const std::string &normalizerState,
core_t::TTime latestRecordTime,
core_t::TTime latestFinalResultTime);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought, but you don't actually need to provide a constructor for this pod. You can just use a list initialiser and provide the arguments in the order in which they are declared in the object, i.e. SModelSnapshotReport report{time, description, ...}. The only downside is if someone reorders the fields in the class definition, but this should generally fail to compile since the types must be convertible to one another. That would be an odd thing to do as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

static const std::string BUCKET_ALLOCATION_FAILURES_COUNT;
static const std::string MEMORY_STATUS;
static const std::string TIMESTAMP;
static const std::string LOG_TIME;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These don't appear to be needed outside of this object. In which case, I generally prefer to just define them in an unnamed namespace in the cc file. It means if we want to write new fields we don't have to touch the header and also tidies up the class definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

core::CRapidJsonConcurrentLineWriter &writer);
};


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra new line.

static const std::string LATEST_RECORD_TIME;
static const std::string LATEST_RESULT_TIME;
static const std::string QUANTILES;
static const std::string QUANTILE_STATE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

writer.EndObject();
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra new line.

writer.String(print(results.s_MemoryStatus));

writer.String(TIMESTAMP);
writer.Int64(results.s_BucketStartTime * 1000);
Copy link
Contributor

@tveasey tveasey Mar 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are quite a few places where we magically multiply times by 1000 sometimes (correctly) casting to int64_t and sometimes not. Might be nice to add a utility function which does this; maybe in core::CPersistUtils.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe static int64_t core::CTimeUtils::toJavaTimestamp(core_t::TTime)?

Or alternatively static int64_t core::CTimeUtils::toEpochMs(core_t::TTime).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to do this but I'll open another PR for it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a better suggestion. I up vote static int64_t core::CTimeUtils::toEpochMs(core_t::TTime).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, makes sense to defer to separate PR. There may well be other cases of this as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only drawback of the add{String/Int/Uint/..}FieldToObj methods is that you have to construct an extra layer of C++ objects to represent the JSON document in memory before writing it as a text JSON document. We do this in cases where we need to return to the documents later to add extra fields. But in cases where a single document can be written start-to-end in one method it's an unnecessary overhead. For documents written as infrequently as model snapshots I don't think the performance difference will be measurable, but I wouldn't want to set a precedent that we only write JSON by constructing documents in memory.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! (So I could simplify writing forecast results, too.)

What about a writer.Time(int64_t time) (to be implemented in CRapidJsonWriterBase.h). I still like the idea of moving the logic into the writer as we have addTimeFieldToObj already. IMHO that is more consistent to that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sure we could have writer.Time(core_t::TTime time) and then all the methods that multiply by 1000 could encapsulate that logic by deferring to int64_t core::CTimeUtils::toEpochMs(core_t::TTime). That will make it easier if we ever change the time units we use in the C++ code.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 exactly

(I was only unsure if core_t::TTime or int64_t as the writer class is somewhat already in rapidjson space, but its hard to draw the line, so core_t::TTime is fine)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raised #16

/*
* ELASTICSEARCH CONFIDENTIAL
*
* Copyright (c) 2016 Elasticsearch BV. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2018, although not sure how this will interact with changes to license header.

/*
* ELASTICSEARCH CONFIDENTIAL
*
* Copyright (c) 2016 Elasticsearch BV. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2018, although not sure how this will interact with changes to license header.

/*
* ELASTICSEARCH CONFIDENTIAL
*
* Copyright (c) 2016 Elasticsearch BV. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2018, although not sure how this will interact with changes to license header.


#include <core/CRapidJsonConcurrentLineWriter.h>

#include <core/CNonInstantiatable.h>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

combine with 2 lines above and sort?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, these should be in lexicographical order.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

LOG_DEBUG("Persist complete with description: " << description);
snapshotIdOut = snapshotIdIn;
numDocsOut = numDocsIn;
LOG_INFO("Persist complete with description: " << modelSnapshotReport.s_Description);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you raised the log level, by intent or debug leftover?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

LOG_DEBUG("Persist complete with description: " << description);
snapshotIdOut = snapshotIdIn;
numDocsOut = numDocsIn;
LOG_INFO("Persist complete with description: " << modelSnapshotReport.s_Description);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEBUG->INFO ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@dimitris-athanasiou
Copy link
Contributor Author

All comments have been addressed. Could you guys have another look?

Copy link

@hendrikmuhs hendrikmuhs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@tveasey tveasey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dimitris-athanasiou dimitris-athanasiou merged commit af650fd into elastic:master Mar 14, 2018
@dimitris-athanasiou dimitris-athanasiou deleted the add-model-snapshot-min-version branch March 14, 2018 12:21
dimitris-athanasiou added a commit that referenced this pull request Mar 14, 2018
This refactors the writing of the model snapshot document and
model size stats document out of CJsonOutputWriter.

It achieves the following improvements:

- model snapshot documents do not have to be queued up. They
can be written directly leveraging the safe concurrent writer
that was implemented for forecasts. This minimizes the delay
of a model snapshot being written which in failure cases may
result into having a more recent snapshot available.
- better separation of concerns and a smaller CJsonOutputWriter class.
- simplifies adding new fields in the model snapshot document as
needed for writing the minimum compatible version.
droberts195 pushed a commit that referenced this pull request Apr 23, 2018
This refactors the writing of the model snapshot document and
model size stats document out of CJsonOutputWriter.

It achieves the following improvements:

- model snapshot documents do not have to be queued up. They
can be written directly leveraging the safe concurrent writer
that was implemented for forecasts. This minimizes the delay
of a model snapshot being written which in failure cases may
result into having a more recent snapshot available.
- better separation of concerns and a smaller CJsonOutputWriter class.
- simplifies adding new fields in the model snapshot document as
needed for writing the minimum compatible version.
droberts195 pushed a commit that referenced this pull request Apr 23, 2018
This refactors the writing of the model snapshot document and
model size stats document out of CJsonOutputWriter.

It achieves the following improvements:

- model snapshot documents do not have to be queued up. They
can be written directly leveraging the safe concurrent writer
that was implemented for forecasts. This minimizes the delay
of a model snapshot being written which in failure cases may
result into having a more recent snapshot available.
- better separation of concerns and a smaller CJsonOutputWriter class.
- simplifies adding new fields in the model snapshot document as
needed for writing the minimum compatible version.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants