The tricky part with real time saving though is ensuring the data is always atomic. I have some various provisions for that which are handled in the first queue up part that is done in real time. One thing you don't want to happen is something like a player buying something from a vendor, so a new object is created and put in their pack, and their bank account is deducted. If the server crashes for any reason at the wrong time and the bank account change made it to the DB, but the item did not, you end up with a non atomic save and a player that is not too happy.
I agree with your sentiment. You'd ideally want your servers to be running physically close to the DB to avoid latency issues.
As for the atomicity problem, the easiest way to handle that (IMO), is basically doing what DBs do under the covers anyways. That is, write the intent then the commit.
So, for a bank/item scenario, rather than updating a field that is the "player money" field, you'd write out to an intention log "I intend to deduct X money from player money" and in the same log you can write "I intend to create a new item xyz" and "I intend to give created item to player" and finally "commit".
from there, you can pull up all transactions with corresponding commits and process them at your leisure. If the server crashes while the log is being written, (before commit), you throw that log away. If the server crashes while you are processing the log, you just pick up where you left off. The only trick is making sure the processing and removal of each log item is done as an atomic operation.
Of course, using SQL transactions if you are using a SQL server anyways would generally be better. Though, it might not be desirable if you have to do a lot of processing while the transaction is open (that could slow things down). Ideally, your transaction are relatively short lived.
Another way to accomplish the log/transaction stuff is versioned data. You reserve a version, do all your operations with that version as the version number, and finally write out that "Active version for player x is 123 if the previous version is the same as what I started with". If the version changes while you are doing all this processing, then you just reprocess with a new version.
With that approach, you'll want something to regularly go back and clean up old versions, but it's semi easy to implement and relatively quick (especially if contention is low).
The version approach has the added benefit of working well with nosql solutions. That because the only thing that needs to be atomic is the update to the new version if the new version is what you started with. Most nosql dbs support that concept.