Appending to a file from multiple processes
nullprogram.com
nullprogram.com
Personally, I agree with tlb: it seems simpler to generate one file per thread, and combine them at the end.
EDIT: Corrected the numbers, I had a bug, previously it said 4 processes with 10 million locks per process and 750 ms. It is still pretty fast even if 250 times slower than initially claimed.
That said, I think the main problem with that is to do it cross platform, which the article goes to paint to mention quite a bit. I imagine the whole point here is to be portable, and I'm not sure what mechanisms work best with that, and what platforms they are available on. I uncovered some unsettling info about Fcntl locking[1], but generally I just used flock when I had to care about it, but i don't think that exists on windows normally(?).
Another idea was, why not just exclusively open the file, write to it and then closed it again? Why even bother having the file open in several processes at the same time? I am not sure what the overhead would be, but I guess it would not be to terrible. And if you can afford to buffer say 1000 records you want to write, then you can simply cut down the overhead by a factor of 1000 by just doing that.
[1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa3...
[2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa3...
Regarding having a buffer of records and only locking / unlocking once per batch write, I've used this technique in a program that writes logs to CSV and it works perfectly. You obviously need to tune the buffer size based on the rate and size of new records! One advantage of this is that, depending on the data you're dealing with, you can pre-sort the data in the buffer and end up with mostly sorted (less interleaved) data in the final output. If you need sorted output, then this can dramatically reduce the time taken to sort the final file.
File IO is always expensive, the trick is always in how you work around that.
E.g.
Open each file and buffer a small amount, such as 4k.
1) Read the first record of each buffer
2) Display the first record of the buffers
3) Read the next record from the buffer you just consumed from, and re-buffer another 4k from it if needed.
4) Go to 2.
Go ahead and change how much you want to buffer from each file depending on what seems to work well and to take advantage of sequential read times if using media that uses that to it's advantage (spinning disks), but I don't see that being very slow. Worst case, you are naively sorting X records each time to find the first, but since you are always consuming them in order, you can easily keep track of the current order and do a dingle comparison per loop after the first.
Yes, but that's the kind of thing everyone explicitly tells you never to do.
If you're on Windows you should use ETW instead of reinventing it when possible :)
1. https://blogs.windows.com/buildingapps/2016/06/10/using-devi...
Can we stop with the silly/trick/arbitrary interview questions? I know we cannot, but I dream for the day.
You have N number of writers, those writers can write X sized data to location A.
How would those writers go about writing to "A" in a performant manner without causing issues? Whether those issues are interleaved data, slowing down the N writers or having to keep outsized buffers on the OS level.
Even if you have no knowledge of POSIX filesystems you should be able to reason about this in an intelligent way in an interview.
Do we go for locks? That has its own set of problems, what are those? Do we go for fixed sized non-interleaved guarantees? What problems does that cause? Do we buffer arbitrarily sized output and and merge it later etc.
It seems like atomic reads are harder. You want to read a record from a shared input, but you don't know how big a record might be. Any thoughts?
But unfortunately that is not atomic. Another process could read into the data between your first and second reads.
Our strategy is to keep the history as append-only, with periodic "vacuums" that rewrite the file into a temporary and move it into place. Even the appends in principle could be split, as NFS provides no guarantees here, but in practice writing individual items seems to be atomic when done with O_APPEND.
"If the O_APPEND flag of the file status flags is set, the file offset shall be set to the end of the file prior to each write and no intervening file modification operation shall occur between changing the file offset and the write operation."
but then it goes on and says:
"This volume of IEEE Std 1003.1-2001 does not specify behavior of concurrent writes to a file from multiple processes. Applications should use some form of concurrency control."
Also, it does seems to allow a signal to interrupt file I/O.
Anyways, it seems that historical unix behaviour is that signals never interrupt file I/O and O_APPEND can be used for atomic appends. Of course, this is not documented anywhere, but you can find discussions on the topic. In particular, here [1] Linus goes in one of his rants when commenting on a patch to change this behaviour.
But that's the (implicit) point of all this, now you've added a bunch of logic for an accumulating writer, a protocol to pass messages between the processes, and state so that partially received messages can be continued when you get back to that process. Now the application has a non-trivial amount of logic and code to deal with logging which might be a source of problems itself. As another commenter noted, individual files per process with an aggregation step (or specialized reader) is much simpler and easier to reason about and mirrors actual case in the article where it was eventually inserted into SQLite, which essentially enforces this read ordering as needed (if there is an ordering field).
While there are various hard-won implementations that work doing this, it's also one of the reasons maildir was invented.
It's much more portable, comprehensible, and reliable to have a single process writing to the file and all the other producers sending records to it using sockets or named pipes. The consolidator needs to be aware of record boundaries so you don't have to worry about pipe atomicity.
This was designed about 16 years ago, so well before the big-data tools, ssds, or powerful machines we have today.
mkfifo foo
tee -a log.txt < foo &
and then multiple processes write to pipe foo.TCP Sockets to a server over localhost might also work ( although the flush IOCTL makes no guarantees w/ sockets), then have the server serialize out. One of the nice things about Tcl is that such a server is relatively easy to do. But you may need to mind newlines - don't write until an entire "line" of text has been read.