Alex Alex - 1 month ago 5
Node.js Question

node.js lost update writing JSON file

I got a Problem with fs; in the SQL universe you would call it lost update.

Background Story:

I got a tiny application, that should store some data provided by the user to a JSON file:

[
{
"givenname": "Joe",
"surname": "Doe",
"dob": "1990-04-01"
},
{
"givenname": "Tim",
"surname": "Cake",
"dob": "2000-01-01"
}
]


And I got some JS to update the file:



// sync
var data = JSON.parse(fs.readFileSync(data_path, 'utf8'));
data.push(user);
var str = JSON.stringify(data);
setTimeout(function () {
fs.writeFileSync(data_path, str);
res.send({'status': 'success'});
}, 5000);

// same story but async
fs.readFile(data_path, 'utf8', function (err, _data) {
if (err) throw err;
var data = JSON.parse(_data);
data.push(user);
setTimeout(function () {
fs.writeFile(data_path, JSON.stringify(data), function (err) {
if (err) {
console.log(err);
res.send({'status': 'error'})
}
res.send({'status': 'success'})
})
}, 5000);
});


I inserted the
SetTimeout(..., 5000)
to have some more time to test the application.

No matter if I do it sync or async, If I post some data to the app, for example
{"givenname": "g01", "surname": "s01", "dob": "2016-01-01"}
and post some other data within the 5 seconds, for example
{"givenname": "g02", "surname": "s02", "dob": "2016-02-02"}
, the first record (g01, s01) will be lost.

I know that in production the timespan between reading and writing the file will be much shorter, but obviously it does exist and it can corrupt the data.

So my question is:

Is there a possibility to avoid that loss of data?

Answer

What's happening is the file is being read by execution A which then sleeps for 5 seconds. Meanwhile, execution B reads the file and waits for 5 seconds. Then A writes it's output, then B writes it's output overwriting the previous output from A.

For a single process accessing the file, the synchronous version you have without the sleep will work:

var data = JSON.parse(fs.readFileSync(data_path, 'utf8'));
data.push(user);
var str = JSON.stringify(data);
fs.writeFileSync(data_path, str);
res.send({'status': 'success'});

It's not particularly scalable (locking up your process for a read and a write), but it will work.

Comments