0

I'm developing a multiplayer word game for mobile in Unity. I've been using GameSparks as a backend, but felt that it didn't give me the control I wanted over the project. After looking around a bit, I decided to go with Firebase. It felt like a good decision, but now I'm experiencing some serious slowness with the database + Cloud Functions

The game has a turn based mode where you play a round and get a notification when your opponent has finished. A round is uploaded via a cloudscript call, with only the necessary data. The new game state is then pieced together in the cloudscript and the game database entry (activeCasualGames/{gameId}) updated. After this is done, I update a gameInfo (activeCasualGamesInfo/{gameId}) entry with some basic info about the game, then send a cloud message to notify the opponent that it's their turn.

The data sent is only 32 kb, and no complex changes are made to the code (see cloud function code below). The complete database entry for the game would be around 100kb max at this point, yet the time from sending the round to the game being updated on server and opponent notified ranges from 50 seconds to more than a minute. On GameSparks, the same operation takes maybe a few seconds.

Does anyone else experience this slowness with Firebase? Have I made some mistake in the cloud script function, or is this the performance I should expect? The game also has a live mode, which I haven't started implementing in Firebase yet, and with this kind of lag, I'm not feeling that it's gonna work out too well

Many thanks in advance!

exports.uploadCasualGameRound = functions.https.onCall(
  (roundUploadData, response) => {
    const roundData = JSON.parse(roundUploadData);

    const updatedGameData = JSON.parse(roundData.gameData);
    const updatedGameInfo = JSON.parse(roundData.gameInfo);

    console.log("game data object:");
    console.log(updatedGameData);

    console.log("game info:");
    console.log(updatedGameInfo);

    const gameId = updatedGameData.gameId;

    console.log("updating game with id: " + gameId);

    admin
      .database()
      .ref("/activeCasualGames/" + gameId)
      .once("value")
      .then(function(snapshot: { val: any }) {
        let game = snapshot.val();
        console.log("old game:");
        console.log(game);

        const isNewGame = (game as boolean) === true;

        if (isNewGame === false) {
          console.log("THIS IS AN EXISTING GAME!");
        } else {
          console.log("THIS IS A NEW GAME!");
        }

        // make the end state of the currently stored TurnBasedGameData the beginning state of the uploaded one (limits the upload of data and prevents timeout errors)
        if (isNewGame === true) {
          updatedGameData.gameStateRoundStart.playerOneRounds =
            updatedGameData.gameStateRoundEnd.playerOneRounds;
        } else {
          // Player one rounds are not uploaded by player two, and vice versa. Compensate for this here:
          if (updatedGameData.whoseTurn === updatedGameData.playerTwoId) {
            updatedGameData.gameStateRoundEnd.playerTwoRounds =
              game.gameStateRoundEnd.playerTwoRounds;
          } else {
            updatedGameData.gameStateRoundEnd.playerOneRounds =
              game.gameStateRoundEnd.playerOneRounds;
          }

          updatedGameData.gameStateRoundStart = game.gameStateRoundEnd;
        }

        game = updatedGameData;

        console.log("Game after update:");
        console.log(game);

        // game.lastRoundFinishedDate = Date.now();
        game.finalRoundWatchedByOtherPlayer = false;

        admin
          .database()
          .ref("/activeCasualGames/" + gameId)
          .set(game)
          .then(() => {
            updatedGameInfo.roundUploaded = true;
            return admin
              .database()
              .ref("/activeCasualGamesInfo/" + gameId)
              .set(updatedGameInfo)
              .then(() => {
               exports.sendOpponentFinishedCasualGameRoundMessage(gameId, updatedGameInfo.lastPlayer, updatedGameInfo.whoseTurn);
                return true;
              });
          });
      });
  }
);
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • while designing Firebase Database try to maintain flat hierarchy don't use nested structure , even though Firebase supports nested structure it will become slow – Shanmugam Apr 02 '19 at 10:32
  • The complete code for this function is a bit hard to follow. Can you reproduce the problem with less code, and with hard-coded data? That will allow others to see all that is going on, and possibly try to reproduce the problem. Also look at [how to create a minimal, complete, verifiable example](http://stackoverflow.com/help/mcve). – Frank van Puffelen Apr 02 '19 at 13:59
  • Many thanks for the input. I got in touch with Firebase Support and received a surprisingly quick response from them. I'll write about the conclusions here when I know more. @Shanmugam: You're probably onto something here. The game data object is quite complex and deeply nested, which is unfortunately hard to do much about. – Henrik Larsson Apr 03 '19 at 04:42
  • @FrankvanPuffelen: Thanks for your input. I'll l have a look at the link you posted! – Henrik Larsson Apr 03 '19 at 04:44

1 Answers1

0

There is some things you must to consider when using a Firebase Cloud Function + Realtime Database. This question may help about the serverless performance issues, such cold start. Also, I think the code itself may be refactored to make less calls to the realtime database, as every external request, it may take time. Some tips are to use more local-stored resources (in memory, browser cache, etc) and use some global variables.

  • 1
    Thanks Luiz. I did read about the cold start thing before, and made sure to use the function a few times. 50 seconds is about the average time from beginning to end. I'm in touch with Firebase support now and will update here I know more about the problem! – Henrik Larsson Apr 03 '19 at 04:47