-
Notifications
You must be signed in to change notification settings - Fork 301
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
HPCC-30085 Batch and write dali transactions asynchronously #17671
HPCC-30085 Batch and write dali transactions asynchronously #17671
Conversation
c12cc11
to
bc5c527
Compare
ec2f450
to
32d924a
Compare
32d924a
to
5ce8c5e
Compare
@ghalliday - please give this a 1st review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jakesmith a few minor comments from an initial scan. I will revisit again and review properly.
hpcc:displayName="Seconds between transactions being committed to disk" hpcc:presetValue="0" | ||
hpcc:tooltip="The maximum time between commit pending transactions to disk (default=0, meaning commit immediately)"/> | ||
<xs:attribute name="deltaTransactionQueueLimit" type="xs:nonNegativeInteger" | ||
hpcc:displayName="Maximum number of pending uncommited transaction" hpcc:presetValue="10000" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trivial: uncommitted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will fix.
type = f_delta; | ||
name = strdup(path); | ||
} | ||
CTransactionItem(char *_name, size32_t _dataLength, void *_data) : name(_name), dataLength(_dataLength), data(_data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unusual that one constructor clones the name, and the other takes ownership.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, I'll make it consistently take ownership, and clone the string outside.
dali/base/dasds.cpp
Outdated
deltaFilename.clear().append(backupPath); | ||
iStoreHelper->getCurrentDeltaFilename(deltaFilename); | ||
OwnedIFile iFileDeltaBackup = createIFile(deltaFilename.str()); | ||
if (!compareFiles(iFileDeltaBackup, iFileDelta, false)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: A better function name would be filesMatch/filesAreIdentical - more specific, and reads better in the if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not new, but agreed, I'll change it.
dali/base/dasds.cpp
Outdated
StringBuffer fname(dataPath); | ||
OwnedIFile deltaIPIFile = createIFile(fname.append(DELTAINPROGRESS).str()); | ||
OwnedIFileIO deltaIPIFileIO = deltaIPIFile->open(IFOcreate); | ||
deltaIPIFileIO.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the close? Worth a comment at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not new code, but relocated. This code is effectively a touch()
Just looked, and there is a jfile touchFile function, but not for an IFile, nor a removeFile for a filename - I may add, to clarify what this is doing.
dali/base/dasds.cpp
Outdated
size_t items = pending.size(); | ||
if ((pendingSz < transactionMaxMem) && (items < transactionQueueLimit)) | ||
{ | ||
if (nextTimeThreshold && (get_cycles_now() < nextTimeThreshold)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is the potential for wrapping - if nextTimeThreshold gets to (cycles_type)-1 then this is unlikely to every be true. Always safer to test (get_cycles_now() - prevWriteTime) > thresholdDuration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, will change.
item->flags = BackupQueueItem::f_addext; | ||
add(item); | ||
CriticalBlock b(pendingCrit); | ||
CTransactionItem *item = new CTransactionItem(name, length, content); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
efficiency: create the item outside of the critical section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I'll move the creation of these.
dali/base/dasds.cpp
Outdated
item->flags = BackupQueueItem::f_delext; | ||
add(item); | ||
CriticalBlock b(pendingCrit); | ||
addToQueue(new CTransactionItem(path, delta)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
efficiency: create the item outside of the critical section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will move
dali/base/dasds.cpp
Outdated
e->Release(); | ||
aborted = true; | ||
sem.signal(); | ||
pendingCrit.enter(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth a comment why you are doing this.
dali/base/dasds.cpp
Outdated
class CBinaryFileExternal : public CExternalFile, implements IExternalHandler | ||
{ | ||
public: | ||
IMPLEMENT_IINTERFACE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should really be
IMPLEMENT_IINTERFACE_USING(CExternalFile)
dali/base/dasds.cpp
Outdated
++throttleCounter; | ||
if (timeThrottled >= queryOneSecCycles()) | ||
{ | ||
IWARNLOG("Transactions throttled - current items = %u, since last message throttled-time/tracactions = { %u ms, %u }, total hard limit hits = %u", (unsigned)items, (unsigned)cycle_to_millisec(timeThrottled), throttleCounter, totalQueueLimitHits); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo?: tracactions->transactions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will correct.
@ghalliday - please see new commit |
dali/base/dasds.cpp
Outdated
Semaphore sem; | ||
cycle_t timeThrottled = 0; | ||
unsigned throttleCounter = 0; | ||
bool waiting = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be atomic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it? It's always altered or read whilst pendingCrit is held.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jakesmith a few relatively minor comments.
dali/base/dasds.cpp
Outdated
// external file | ||
|
||
// commit accumulated delta 1st (so write order is consistent) | ||
if (deltaXml.length()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you possibly do this in two phases?
a) write new files, but don't flush xml
after all written
b) remove all external.
StringBuffer deltaFilename(dataPath); | ||
iStoreHelper->getCurrentDeltaFilename(deltaFilename); | ||
OwnedIFile iFile = createIFile(deltaFilename.str()); | ||
writeDelta(deltaXml, *iFile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crc calculation in that function is a bit expensive. Might be worth considering other options/implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dali/base/dasds.cpp
Outdated
unsigned throttleCounter = 0; | ||
bool waiting = true; | ||
bool backupOutOfSync = false; | ||
bool aborted = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be atomic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, will change.
dali/base/dasds.cpp
Outdated
cleanChangeTree(*changeTree); | ||
|
||
// write out with header details (e.g. path) | ||
Owned<IPropertyTree> header = createPTree("Header"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
efficiency: Not as clean, but would it be more efficient to explicitly write out the text
"<Header path='%s'><Delta>"..."</Delta></Header>"
than the overhead of creating the tree nodes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes could do, cuts down on the 'Header' and 'Delta' trees.
I'll change.
item->data = data; // take ownership | ||
item->flags = BackupQueueItem::f_addext; | ||
add(item); | ||
CTransactionItem *item = new CTransactionItem(name, length, content); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be checking aborted still? (And the other similar functions)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it highlights an issue. The deltaWriter is cleared when blockingSave is called (when Dali itself saves all in memory state, the pending 'to be saved' transactions are cleared). However, when Dali shuts down that is not done.
The delta writer should both be cleared and stopped, although by the time it (Dali) is stopping and saving, there will be no more transactions added.
I will make some changes.
dali/base/dasds.cpp
Outdated
serializeVisibleAttributes(owner, mb); | ||
StringBuffer extName; | ||
getName(extName, name); | ||
if (manager.extCache.lookup(extName, mb)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should withValue be passed/tested before this? Is it something that occurs often?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should withValue be passed/tested before this?
it does look that way, I need to double check.
Is it something that occurs often?
is hitting the cache something that happens often?
Yes, in testing, the same external is often re-read in quick succession.
dali/base/dasds.cpp
Outdated
getFilename(filename, name); | ||
Owned<IFile> iFile = createIFile(filename.str()); | ||
size32_t sz = (size32_t)iFile->size(); | ||
if ((unsigned)-1 == sz) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to cast to size32_t for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, will change.
dali/base/dasds.cpp
Outdated
getFilename(filename, name); | ||
Owned<IFile> iFile = createIFile(filename.str()); | ||
size32_t sz = (size32_t)iFile->size(); | ||
if ((unsigned)-1 == sz) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to cast to size32_t for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will change.
else | ||
{ | ||
Owned<IFileIO> fileIO = iFile->open(IFOread); | ||
verifyex(sz == ::read(fileIO, 0, sz, mb)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
read function unnecessarily checks size() again. I don't know if that will cause an extra delay (on remote storage), but may be worth avoiding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like it would at least be worth modifying ::read to avoid the check:
const size32_t checkLengthLimit = 0x1000;
if (len >= checkLengthLimit)
{
//Don't allocate a stupid amount of memory....
offset_t fileLength = in->size();
if (pos > fileLength)
pos = fileLength;
if ((len == (size32_t)-1) || (pos + len > fileLength))
len = (size32_t)(fileLength - pos);
}
if pos == 0 and len != -1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense to do in separate JIRA/PR : https://track.hpccsystems.com/browse/HPCC-30193
// force a synchronous save | ||
CCycleTimer timer; | ||
CHECKEDCRITICALBLOCK(blockedSaveCrit, fakeCritTimeout); // because if Dali is saving state (::blockingSave), it will clear pending | ||
PROGLOG("Forcing synchronous save of %u transactions", (unsigned)pending.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move before the critsec? I suspect it is unlikely to be contended so not at all significant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but yes, logical to move before
thresholdDuration = queryOneSecCycles() * saveThresholdSecs; | ||
lastSaveTime = get_cycles_now(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NB: this is an incidental change. Not being set before meant it would save without checking threshold early than intended (not a big deal, but more correct to set time at start)
70d928c
to
8c2ceb0
Compare
@ghalliday - please review new commit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jakesmith looks close to ready. A few comments on naming, and I think one potential hole.
dali/base/dasds.cpp
Outdated
if (pendingSz) | ||
signalWaiting(); | ||
} | ||
if (!waiting) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trivial: else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's intentionally not an else, because signalWaiting (if called above) wil reset waiting to false
@@ -1407,17 +1427,36 @@ class CDeltaWriter : implements IThreaded | |||
CriticalBlock b(pendingCrit); | |||
addToQueue(item); | |||
} | |||
void clear() | |||
void flush() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be commented because it isn't clear what the different flags mean etc. Something like
//ensure that all pending transactions have been flushed to the file.
if (waiting)
{
// The writer thread is actively waiting - if there is data to write ensure it is written
}
else
{
//delta is currently being written. Wait until all the data is written.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will update with some comments.
dali/base/dasds.cpp
Outdated
if (waiting) | ||
{ | ||
if (pendingSz) | ||
signalWaiting(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this also wait until until signalEmpty occurs? Othewise it might still be writing data (or not yet have written the data) before the function continues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it will because sianglWaiting resets waiting to false (all within pendingCrit), and so it will fall into the if (!waiting) block below.
dali/base/dasds.cpp
Outdated
bool waitOnEmptySem = false; | ||
{ | ||
CriticalBlock b(pendingCrit); | ||
if (waiting) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waiting is a very confusing variable name - especially since it is set from thread and cleared in another. It actually seems to mean.
needToSendSignalToRequestDataIsWritten
I think it would be worth coming up with a clearer name - it would help check the logic is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've renamed it deltaWriterIdle, which I think is more accurate and clarifies the logic IMO.
dali/base/dasds.cpp
Outdated
waitOnEmptySem = true; | ||
} | ||
} | ||
if (waitOnEmptySem) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"allWritten" would be a bit clearer than "empty"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, will rename this and some others to clarify meaning.
dali/base/dasds.cpp
Outdated
@@ -1445,6 +1484,11 @@ class CDeltaWriter : implements IThreaded | |||
if (semTimedout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be
if (semTimedout && !needToSendSignalToRequestDataIsWritten)
{
sem.wait(); // Consume the signal that was sent
needToSendSignalToRequestDataIsWritten = true;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's true, I'll modify.
dali/base/dasds.cpp
Outdated
@@ -1445,6 +1484,11 @@ class CDeltaWriter : implements IThreaded | |||
if (semTimedout) | |||
sem.wait(0); | |||
waiting = true; | |||
if (signalEmpty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
signalWhen...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will rename.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jakesmith please squash. A couple of comments to clarify the code, but I think it is functional. Will scan once more when squashed.
dali/base/dasds.cpp
Outdated
bool waitOnEmptySem = false; | ||
// ensure all pending transactions are written out | ||
// if necessary wait until CDeltaWriter thread has finished | ||
bool allWritten = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
picky: really waitUntilAllWritten
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will change
dali/base/dasds.cpp
Outdated
signalWaiting(); | ||
} | ||
if (!waiting) | ||
if (!writerIdle) // i.e. if not idle, it is busy writing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth having a comment that this includes if signalWaiting() was called above. Otherwise it is very tempting to turn this if into an else, because it strange that writerIdle would be set by the requester.
writerRequested would be clearer than writerIdle, but would invert the condition, so safer not to make that change now.
signalWaiting() would similarly be better renamed as request[Async]Write()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have made these changes, but kept writerIdle -> writeRequested change in its own commit to allow easier review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jakesmith please squash
Also add an external file caching mechanism to reduce the expense of frequently reloading recent externals. Signed-off-by: Jake Smith <[email protected]>
b656757
to
0a10a8a
Compare
@ghalliday - squashed |
Also add an external file caching mechanism to reduce the
expense of frequently reloading recent externals.
Signed-off-by: Jake Smith [email protected]
Type of change:
Checklist:
Smoketest:
Testing: