0 Members and 4 Guests are viewing this topic.
What information exactly do you get in every update from the server?If you get the list of online players, you could check against the table to remove whoever isn't in that list from the tracking table, and also add whoever isn't on that list. A timestamp doesn't sound very reliable, as a lot of people enter servers just to leave them.
As mentioned in this thread, the lobby will have a shiny new feature in the near future. There's one aspect of the player tracking that I haven't thought of: how to know when a player leaves the server.There's only one way that I thought to solve this: every time I get an update from the server that has player names (every minute), I do the following steps:- Remove all entries in the player tracking table that are in this server (aka that match the server IP and port)- Insert the player name, server IP and server port into the player-tracking table (for each player in the server)The problem with this is that it creates a very small amount of time where there's no information on who is in that server. Technically this just isn't good/is a race condition of sorts... and I don't like that. I want a better way to do itI need help to think of better ways to do this. Maybe it involves keeping a timestamp on all of the entries in the player tracking table, and just removing stale entries from there. I don't know, and need some other people to help me think outside the box on this one. Thoughts?
mysql> LOCK TABLES trans READ, customer WRITE;mysql> SELECT SUM(value) FROM trans WHERE customer_id=some_id;mysql> UPDATE customer SET total_value=sum_from_previous_statement  ->    WHERE customer_id=some_id;mysql> UNLOCK TABLES;
<?php//Try to insert the player into the database. This will fail if he'salready in it.$query="INSERT INTO players (PlayerIP, Updated, ...) VALUES ('ip', 1, ...);";if(!mysql_query($query)) { Â Â Â //The query failed, so the player's aready in the database: Â Â Â $query="UPDATE players SET PlayerIP='ip', Updated=1, ... "; Â Â Â mysql_query($query);}// Remove the players from that server that have Updated set to 0mysql_query("DELETE * FROM players WHERE ...");//Set all Updated values of that server to 0:mysql_query("UPDATE players SET Updated=0 WHERE ...");?>
Is the locking really necessary chris?I remember reading the MySQL manual and it said that UPDATE queries by default have a higher priority than the SELECT ones. This means that if an UPDATE has been issued, the following SELECT queries will be delayed until the update is complete.There's even a SELECT HIGH_PRIORITY statement that's used to force a SELECT even when an UPDATE has been issued before it.
Well, I'm not a fan of the locking:There are around 300 servers, let's say 100 with players, updating every minute.Let's say each update locks the ENTIRE table for 0.05 seconds, which is already pretty fast.Then I'd come up with some possible problems:- That means the lobby is locked for 5 seconds per minute, or around 10% of the time.- The locking slows down the other updates quite alot.You need to take into account that there's no hartbeat, every server updates itself randomly, so you have multiple instances of the code running.The locking will have the same problem, only on another area. Imagine the update being run by a server with 32 players and a server with only 1 player, almost at the same time. Update process 1 is the 32 player server, process 2 is the 1 player server.Update process 1 locks the tableUpdate process 1 starts deleting the entries.Update process 2 locks the table, but it's already locked so nothing happens.Update process 2 starts deleting the entries.Update process 2 finishes deleting the entries earlier than process 1 (only has to delete 1 entry instead of 32)Update process 2 starts inserting the new entry.Update process 1 finishes deleting the entries.Update process 1 starts inserting the 32 queries, which will take significantly longer than inserting only one entry.Update process 2 finishes.Update process 2 UNLOCKS THE TABLE.Update process 1 finishes inserting.Update process 1 unlocks the table, but it is already unlocked so nothing happens.The underlined part is yet another part where there is no data returned for that particular server. The only difference with the setup in the first post is that this occurs less often and for a shorter time. But the chance for a dead moment still lies around 0.1-1% which means one in thousand requests for players will return no players at all.The trick is to delete only those players that are not needed anymore.I've made some guesses but I always rounded down to the lowest possible value. If the server's busy, the locking will consume more than 10% of the time for example.PS: I still haven't found a better way to do this as my suggestion or chrisgbk's suggestion. I'll keep thinking.Grtz, DePhille
About the locking:Hmm, if the other process has to wait until the table is unlocked again, the 'lock-time' of the table will be a bit more than 10% of the total time then. If you're also willing to know how long the player has been playing in that server then I'm almost pretty sure the easiest way is replacing the Update value with a timestamp and comparing it with the current time instead of with 0 and 1.
+-----------------------+---------+| Variable_name | Value |+-----------------------+---------+| Table_locks_immediate | 1151552 || Table_locks_waited | 15324 |+-----------------------+---------+
edit: Even more reason not to delete everyone and add them back (and instead just update them) is so that I can know when they first came to the server, so that when queried I can say "this player has been in server foo for X minutes/hours" or "... since <date>"A clear alternative is to simply loop through the players I just got from the server, see if they're already in the server. If not, insert them. If they are, update... but this is just as inefficient (still 2N queries). This is probably the better option than deleting all and inserting all, but I'd still like to hear any thoughts on better ways to do it.
DELETE FROM track_players AS tp WHERE tp.server = $SERVER AND tp.name NOT IN($NAMES)
INSERT IGNORE INTO track_players (server, name, ip, join_time, whatever) VALUES ($SERVER, $NAME, $IP, $TIME, $ETC)
ALTER TABLE `track_players` ADD UNIQUE `server_player` ( `server` , `name` )
INSERT INTO track_players (server, name, ip, join_time, whatever) VALUES ($SERVER, $NAME, $IP, $TIME, $ETC) ON DUPLICATE KEY UPDATE kills=$KILLS, deaths=$DEATHS
Now for this to work you just need one thing; An unique index. You can't simply create an unique index on name, since it's possible that different people with same name are on different servers at same time (eg. "Major"). So you create a multi-column UNIQUE index:Code: [Select]ALTER TABLE `track_players` ADD UNIQUE `server_player` ( `server` , `name` )I don't know how the lobby system works, to be more precise I don't know how it tracks servers. If it assigns each server that registers in lobby an unique ID upon registering itself for first time, and then using that unique ID for consequent updates, there shouldn't be no problem at all. But if it tracks the servers by its name as well, then this approach will have an issue with servers with duplicate names (if the lobby allows it). To be more precise, it wouldn't be possible to track two players with same name on different servers, if the servers have the exact same name. That should be a pretty rare case, but still it can happen. But if the servers are tracked by a numeric ID that is unique, then this won't be a problem at all.
Quote from: ramirez on May 30, 2007, 09:44:08 pmNow for this to work you just need one thing; An unique index. You can't simply create an unique index on name, since it's possible that different people with same name are on different servers at same time (eg. "Major"). So you create a multi-column UNIQUE index:Code: [Select]ALTER TABLE `track_players` ADD UNIQUE `server_player` ( `server` , `name` )I don't know how the lobby system works, to be more precise I don't know how it tracks servers. If it assigns each server that registers in lobby an unique ID upon registering itself for first time, and then using that unique ID for consequent updates, there shouldn't be no problem at all. But if it tracks the servers by its name as well, then this approach will have an issue with servers with duplicate names (if the lobby allows it). To be more precise, it wouldn't be possible to track two players with same name on different servers, if the servers have the exact same name. That should be a pretty rare case, but still it can happen. But if the servers are tracked by a numeric ID that is unique, then this won't be a problem at all.Theres one way to guarantee that each server is unique; use the server IP and port. It's not possible for any 2 servers to have the same IP and port. That would solve that.
Quote from: chrisgbk on May 30, 2007, 09:50:25 pmQuote from: ramirez on May 30, 2007, 09:44:08 pmNow for this to work you just need one thing; An unique index. You can't simply create an unique index on name, since it's possible that different people with same name are on different servers at same time (eg. "Major"). So you create a multi-column UNIQUE index:Code: [Select]ALTER TABLE `track_players` ADD UNIQUE `server_player` ( `server` , `name` )I don't know how the lobby system works, to be more precise I don't know how it tracks servers. If it assigns each server that registers in lobby an unique ID upon registering itself for first time, and then using that unique ID for consequent updates, there shouldn't be no problem at all. But if it tracks the servers by its name as well, then this approach will have an issue with servers with duplicate names (if the lobby allows it). To be more precise, it wouldn't be possible to track two players with same name on different servers, if the servers have the exact same name. That should be a pretty rare case, but still it can happen. But if the servers are tracked by a numeric ID that is unique, then this won't be a problem at all.Theres one way to guarantee that each server is unique; use the server IP and port. It's not possible for any 2 servers to have the same IP and port. That would solve that.Oh yeah, that's true and would work just well. The real benefit of using a numeric ID over a server name or ip:port is that since the column is indexed, it's faster to look up if it's an integer.
<ramirez> i was just curious that do you use MyISAM or InnoDB engine for lobby tables<FliesLikeABrick> ah just sent you an e-mail<FliesLikeABrick> because of that lobby api thread?<ramirez> yeah<ramirez> because with InnoDB you could do row-level locking<ramirez> instead of locking entire tables<FliesLikeABrick> row-level locking wouldn't be enough<ramirez> which can speed up the lobby in case there's a lot of activity<FliesLikeABrick> because I don't store player info in the server table<FliesLikeABrick> I'm going to store player info in a player table separately, so that it is easier to query for stuff<FliesLikeABrick> one row per player<ramirez> well what i meant was that* Cube` has quit (Read error: Connection reset by peer)<FliesLikeABrick> if it was one row per server (which it is in the server table), and I stored player names as a serialized string, then it could be useful<ramirez> you can lock multiple rows with InnoDB<FliesLikeABrick> its MyISAM anyway<FliesLikeABrick> right, but what would that gain me? <FliesLikeABrick> is that only useful for updates?<FliesLikeABrick> or can I lock a bunch of rows, remove them, then unlock them to commit the removal?<ramirez> yeah, exactly<ramirez> InnoDB does reading bit differently than MyISAM, it doesn't lock the tables for reading<FliesLikeABrick> but then I could have duplicates, as far as any second process is concerned<ramirez> how so?<FliesLikeABrick> because say that one person leaves a server and goes to another one<FliesLikeABrick> unless I could read-lock the new rows as they're inserted<ramirez> you can<ramirez> basically, this is what you'd do<FliesLikeABrick> and can I unlock the old rows and the new ones at the same time?<FliesLikeABrick> (I'm just hunting for race conditions)<ramirez> well<ramirez> what you could do is something like this:<ramirez> BEGIN; SELECT 1 FROM track_players WHERE server=$SERVER FOR UPDATE; /* Here you do all the updates on that specific server */ COMMIT;<ramirez> even if other processes try to read that table at same time as it's updating<ramirez> the data will be correct<FliesLikeABrick> so pretty much it commits all of the things at once<FliesLikeABrick> gotcha<ramirez> because it'll use a "shadow" of the data<FliesLikeABrick> so it "handles" which rows to lock on its own<ramirez> yep<ramirez> it works by indexes<FliesLikeABrick> it is pretty much just ensuring that my transaction occurs all at once<ramirez> server would need to be an indexed column for it to lock<ramirez> but well<ramirez> I'd first do it with MyISAM because MyISAM is faster generally.<FliesLikeABrick> yeah, and I am<ramirez> InnoDB only gets really beneficial when there's a lot of activity<FliesLikeABrick> table locking is sufficient and should be fine<FliesLikeABrick> if not, I can convert this one table to InnoDB easily<ramirez> yeah