Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing atomic file writes in a nontransactional filesystem

Many common filesystems do not offer atomic operations, yet writing files in an atomic manner is very important in certain scenarios. I tried to come up with a solution for this problem.

I made the following assumptions:

  • The filesystem in use supports atomic operations at inode level (for instance, NTFS). This means that move and delete are atomic.
  • Only the program itself accesses the files.
  • There is only 1 instance of the program at a time and it acts in a single-threaded manner.
  • For simplicity, the whole file content is written each time (i.e. truncate-write).

This leaves the following problem: While writing a file, the program could be interrupted and the file would be left with only a part of the content to write.

I propose the following process:

  1. Write new content to a temporary file New
  2. Move the original file Original to a temporary location Backup
  3. Move New to Original
  4. Delete Backup

New and Backup files are distinguishable from Original files (for instance, they could be prefixed differently, or could be in a separate directory on the same volume). At the same time, their name should map directly to the corresponding Original (for instance by simply using the same file name).

This, however, does not make the operation atomic yet. The process could be interrupted steps 1, 2, 3 or 4:

  1. Leaves a potentially incomplete New.
  2. Move is atomic, but the target file is now missing. Both New and Backup exist and are complete.
  3. Move is atomic, but there is an unused Backup. The Original was replaced by the New content
  4. Deletion is atomic.

Using assumptions 2 and 3 from earlier, the program has to be restarted after a crash. During the startup process, it should perform these recovery checks:

  • If New exists but Backup does not, we crashed in or after step 1. Delete New since it could be incomplete.
  • If New exists and Backup does too, we crashed after step 2. Continue with step 3.
  • If Backup exists but New does not, too, we crashed after step 3. Continue with step 4.

The recovery process itself, only using atomic operations, will simply continue where it left off after being interrupted.

I believe this idea ensures atomic writes for a single program. These issues exist still:

  • When using multiple instances of the same program, there is an interference of the recovery process with currently ongoing file writes in the other program.
  • Outside programs that only read but never write will usually get the correct result, but if there is a write operation on the requested entry at the same time, they may incorrectly find no entry.

Those issues (which are excluded by the assumptions earlier) could be solved via usage policy (for instance, check for other instances, and deny directory access to other users).

Finally, my question: Did that make sense, or is there a flaw in the process? Are there any issues that prevent this approach from being used in practice?

like image 632
mafu Avatar asked Jan 18 '23 14:01

mafu


2 Answers

There is only one thing you should assume, renaming a file is atomic operation

So following steps will ensure correction (at least on unix like OS)

  1. Write new content to a temporary file New
  2. rename the temporary file to original name

This way if application crashed when restarted it either get the old content or new content without need of extra code.

like image 189
maor Avatar answered Jan 29 '23 11:01

maor


Your steps can be simplified further:

  1. Write new content to a temporary file New
  2. Delete Original file
  3. Move New to Original

On startup:

  1. If Original does not exist but New does, the process was interrupted before step 3, move New to Original.
  2. If Original and New both exist, the process was interrupted before step 2, delete New.

I have used this in managing configuration files and have never encountered a problem from this process.

like image 43
Dark Falcon Avatar answered Jan 29 '23 10:01

Dark Falcon