Jump to content
  • Advertisement
Sign in to follow this  

Proper way to sync data to disk?

This topic is 1748 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello.

 

I'm new to game development, and I'm trying to make a multi-player game server. One design issue I've run into is trying to find a fast but safe way to keep server data (account info, game states, etc) and file data synchronized. I thought about just saving directly to the file whenever any internal data structure is changed, but I don't know how much that would impact performance to constantly write to the disk. I also want to minimize the chance of putting the files in an inconsistent state in the event of a crash. Which the current server gleefully makes a hobby of doing.

 

 

So what's a good disk I/O strategy to keep the performance decent, the data consistent, and code complexity/size down?

Share this post


Link to post
Share on other sites
Advertisement

For persistent data (account info. character info., etc.) you might use a sql interface to store data in a database.

 

Generally, you don't store transient data (game state, etc.) on disk.

Share this post


Link to post
Share on other sites

Right, there's certain things that you want persisted immediately for a good user experience, like items acquired, levels gained, or trades made. For that kind of thing, you want the persistence transaction (be it do disk or database) to be completely atomic. Other stuff involving the state of the player's general interaction with the world are not crucial to maintain, like their exact position every time they move. In a perfect world, maybe that'd be nice, but here in reality-land, there are more important things to worry about.

Share this post


Link to post
Share on other sites

SQlite sounds like a good idea; I'm using a lot of relational data anyways. The game I'm developing is a turn-based strategy game with tiled maps, so there is a lot of data to be stored per player/map, and a game could last longer than the lifetime of the server process.

Share this post


Link to post
Share on other sites

If its not real-time, then yes, pretty much everything is persistent -- the only things that aren't persistent themselves are things that can be derived from the persistent data, and even then, depending on how resource intensive re-deriving it is, you may want to cache it persistently anyhow.

 

Since its turn-based, you might want to just store the actions (that is, the commands vetted by the server) together with the starting state (starting state of the world, random-number-generator seeds, etc), and ensure your simulation (server-side) is deterministic. This architecture would easily allow your players to see the last action (or last-few-actions) when they log in, and you can use something like push notifications to move the game along when both players are connected at the same time. As a bonus, you'd be able to replay the entire game after its through -- in fact, this is exactly how Halo Reach, and Halo 4 provide their replay functionality (except that it all happens per-client, so some gameplay might diverge in small ways; technically the game-serving-peer's video is the "official version" of the replay).

 

This could grow to a lot of data, and a lot of queries, so you'll want to bone up on designing performance-minded databases and writing fast SQL. If the size of the database on-disk becomes a problem, you can purge games that have completed (or been inactive) after a time.

Edited by Ravyne

Share this post


Link to post
Share on other sites

The standard approach for atomic updates of a whole file is:

 

1. Save data to a temporary file. Check for errors and report failure if you get an error during this process (e.g. disk full).

2. Swap the two files over. This is the tricky bit, and will need O/S specific code to get right because you need disk cache flushes. The other options to the process in the linked article are the MoveFileTransacted() or ReplaceFIle() APIs.

Share this post


Link to post
Share on other sites

The standard approach for atomic updates of a whole file is:

 

1. Save data to a temporary file. Check for errors and report failure if you get an error during this process (e.g. disk full).

2. Swap the two files over. This is the tricky bit, and will need O/S specific code to get right because you need disk cache flushes. The other options to the process in the linked article are the MoveFileTransacted() or ReplaceFIle() APIs.

 

Adam,

 

Yes, but, it's not very efficient or desirable when making a small change to a large data. Really, using sqlite will make problems with durability / rewriting go away, and everything just works. Internally sqlite is creating some kind of journal, but that's ok, you don't need to worry about it.

 

For something really simple like a high-score-table, I might be tempted to use the "atomically replace file" model though.

Share this post


Link to post
Share on other sites

A better approach to consistency is journalling and snapshotting, in my opinion. This is the approach that for example redis takes, too.

 

You have an initial snapshot file that is read when the server starts. Optionally, in case there was a crash or power failure, there may be a journal file. If there is one, the server works off all entries in the journal until it reaches the end, before creating a socket and listening to requests.

 

For every request, after parsing and error checking, some serialized data is appended to the journal (if you want, you can just write out the client's request 1-to-1 too, but that's less efficient). Then, success is reported back to the client (i.e. the transaction is "finished" as far as the client can tell). Now you modify your actual database record in-memory. Every 1-2 minutes, you fork and the child process writes a snapshot to a new snapshot file, then either swaps or deletes the old one.

 

The same works with datasets that are too huge to keep in memory too, although it gets a little bit more involved (MVCC needed). In case of a failure, you can always restore the consistent state of the whole database at the time of the last successful transaction (or, in fact, any previous transaction that is in the journal, so you even have a kind of "time travel" capability if you want) from the latest snapshop plus the journal of the last minute. This usually takes no more than at most a few seconds after a crash.

 

That approach does not offer perfect consistency since the server might still crash before the journal is synced to disk, but this is something that you can usually live with, and it is a very unlikely condition with usually very little impact, too. If it is absolutely not tolerable, you can sync all journal writes before reporting success (but even then, you have no 100% guarantee because the harddisk or the RAID controller might tell you "OK" and in reality only have the data in cache...).

Share this post


Link to post
Share on other sites

There are some really great suggestions here, and I'm very thankful . . . but I don't feel confident about my skills as a programmer to implement a complex and advanced system like effective journalling and recovery. I mean, I probably could find a way to do it poorly, but I'm already pushing my novice skills as is. My focus is more on getting the game finished while having it useable and stable. I've seen how easy it is for a game project to fall into the void of never-getting-finished due to feature creep, and there's only one of me after all. smile.png

 

Now, SQL is something I am a little bit familiar with. Though I'm not too knowledgeable about performance for it, is that something I need to worry about? So far the load I'm planning on supporting is roughly up to 24 concurrent clients/players (hard limit at 64 due to select(), but the more the merrier), and some players can have the server take over for them while they're gone. Also, I'm expecting that there can be many small updates to game data as players plan their actions out.

 

So my current plan is this. I have two rough sets of data: data specific to a game instance (game data), and everything else (system data). I've already programmed in a good chunk of the system data, and it's all read/written as tab-separated text files. A lot of that data is simple, irregularly changed (ie password hashes), and if corrupted, easy for server operators to spot and fix by hand. Game data on the other hand, is much more transient, sizable, homogenous, and the context may not be as obvious (whereas "name=john_smith" has very obvious context in a client account file). Hence the dual focus on efficient saving, and where data corruption becomes a much bigger concern in diagnosing and fixing. So I may go with a database there, and save the data every turn. If I want to push it, I could copy the game data to new structures after every turn, and then in a separate thread write the old data to disk. But I don't know if writing to the disk will cause the entire process to block, it's also not a subject I'm terribly familiar with. For me, getting it done takes precedence over being fancy.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!