Spring Data MongoRepository save(T) not working... sometimes Spring Data MongoRepository save(T) not working... sometimes mongodb mongodb

Spring Data MongoRepository save(T) not working... sometimes


MongoDB is inherently a cache store, by which I mean, the contents are not guaranteed to be latest or necessarily correct. I haven't been able to find the configuration options for flush time (but they would be configured in the DB itself), but MongoDB has added features so that you can choose fast+dirty, or slow+clean. This "freshness" factor is most likely your issue if you are seeing this kind of problem. (Even if you aren't running distributed, there is a timing difference between request acknowledge, and request committed)

Here is a link to post regarding "clean reading" (Key point in following quote)

http://www.dagolden.com/index.php/2633/no-more-dirty-reads-with-mongodb/

I encourage MongoDB users to place themselves (or at least, their application activities) into one of the following groups:

"I want low latency" – Dirty reads are OK as long as things are fast. Use w=1 and read concern 'local'. (These are the default settings.) "I want consistency" – Dirty reads are not OK, even at the cost of latency or slightly out of date data. Use w='majority' and read concern 'majority. use MongoDB v1.2.0;

my $mc = MongoDB->connect(    $uri,    {        read_concern_level => 'majority',        w => 'majority',    });

further reading that may or may not be useful

Update

If running in a multi-thread environment, make sure your threads aren't trampling over another's updates. You can verify if this is happening by configuring the system or query logging level to 5.https://docs.mongodb.com/manual/reference/log-messages/#log-messages-configure-verbosity


Problem solved.
A different async call from the JS client, to a different endpoint in the Java backend, was overwriting my updated document in a different thread with the original values.

Both update operations were calling findById before saving. Problem was that they did so at the same time, so they were getting the same original values.
Each then carried on with updating their relevant fields and calling save at the end, resulting in the other thread effectively overriding my changes.
Each call was logged with just the relevant modified fields, so I didn't realize that one of them was overwriting the changes of the other.

Once I added systemLog.verbosity: 3 to MongoDB's config.cfg so it logged all operations, it was clear that 2 different WRITE operations were happening at the same time (~500 ms apart) but using different values.
Then it was just a matter of moving the findById closer to the save and ensuring that the JS calls were done in order (by making one of the promises depend on the other).

In hindsight, this probably wouldn't have happened if I used MongoOperations or MongoTemplate, which offer single update and findAndModify methods that also allow single-field operations, instead of MongoRepository where I'm forced to do it in 3 steps (find, modify returned entity, save) and to work with the full document.


EDIT: I didn't really like my first "move findById closer to save" approach, so in the end I did what I felt was right and implemented custom save methods that used MongoTemplate's finer-grained update API. Final code:

/* MongoRepository provides entity-based default Spring Data methods *//* BookDataRepositoryCustom provides field-level update methods */public interface BookDataRepository extends MongoRepository<BookData, String>, BookDataRepositoryCustom {    BookData findById(String id);}/* Interface for the custom methods */public interface BookDataRepositoryCustom {    int saveCurrentPage(String id, Integer currentPage);}/* Custom implementation using MongoTemplate. */@SuppressWarnings("unused")public class BookDataRepositoryImpl implements BookDataRepositoryCustom {    @Inject    MongoTemplate mongoTemplate;    @Override    public int saveCurrentPage(String id, Integer currentPage) {        Query query = new Query(Criteria.where("_id").is(id));        Update update = new Update();        update.set("currentPage", currentPage);        WriteResult result = mongoTemplate.updateFirst(query, update, BookData.class);        return result == null ? 0 : result.getN();    }}// Old code: get entity from DB, update, save. 3 steps with plenty of room for interferences.//        BookData bookData = bookDataRepository.findById(bookDataId);//        bookData.setCurrentPage(currentPage);//        bookDataRepository.save(bookData);// New code: update single field. 1 step, 0 problems.        bookDataRepository.saveCurrentPage(bookDataId, currentPage);

By doing so, each endpoint can update as frequently as needed via MongoTemplate without ever worrying about overwriting unrelated fields, and I still keep the entity-based MongoRepository methods for things like new entity creation, findBy methods, annotated @Querys etc.