Schema and Device Sync advice

Hi,
I’m working on a Flutter app that tracks matches for an amateur, nationwide sports league. I’m using flexible sync and the main tables I have are:

User : Read/write own role.
Player (Read all role. Stores public info such as name, image url, etc.)
Match (Read/write own roles. Stores both player IDs and match details)

I have all of this working except I’m wondering if my approach is right when it comes to syncing players.

Right now each User is subscribed to all matches they have played in, with player 1 being the “owner” in terms of write permissions. At startup I update subscriptions. I then build an opponent list based on the current user’s matches and manually subscribe to each opponent so they are synced locally. This allows me to provide a list view with all of their matches and opponent’s info…even when offline. With that setup, I’ll have to update this subscription list every time a match is created or deleted to keep things in sync. However, from what I’ve read, you shouldn’t update subscriptions often due to a performance hit. I’m not sure what that “hit” will be.

A couple of other ways I’ve considered:

  1. Have each user maintain a list of players that gets updated when a new match is created. Then under read roles, call a server function to allow/disallow reading of only the players a user has played against. Drawback: That list is essentially unbounded and could get quite large over time. Otherwise, I think this would work but I haven’t tried it yet.

  2. Embed (duplicate) player data in each match. Drawback: duplication of data that won’t update when the player changes their name, image, etc.

  3. Syncing with all players. Drawback: That could be thousands of players over time so bandwidth, space and performance concerns.

Is there a 4th option?

The bottom line is that I want each user to have their matches and opponents they played in those matches subscribed to in the most efficient way so that those documents are available to them even if they are offline.

Any tips would be appreciated!

My existing code is below if it’s useful to see:

Future _updateSubscriptions(User user) async {

    // subscribe to the current user's Player document
    await realm!.query<models.Player>("_id == oid(${user.id})").subscribe(
        name: 'player',
        waitForSyncMode: WaitForSyncMode.firstTime,
        update: true);

    // subscribe to all matches that the current user has played in
    await realm!.all<models.Match>().subscribe(name: 'matches', update: true);

    // build a list of players the current user has had matches with
    List<ObjectId> matchplayers = [];
    for (models.Match match in realm!.all<models.Match>()) {
      if (!matchplayers.contains(match.p1_id;
)) {
        matchplayers.add(match.p1_id;
);
      }
      if (!matchplayers.contains(match.p2_id)) {
        match.players.add(match.p2_id);
      }
    }
    matchplayers.remove(ObjectId.fromHexString(
        user.id)); // remove the current user's player from the list since we've already subscribed to it above

    // remove subscriptions for players the current user no longer has matches with
    realm!.subscriptions.update((subscriptions) {
      var playersubs =
          subscriptions.where((element) => element.name!.startsWith('p='));

      for (Subscription sub in playersubs) {
        String userid = sub.name!.substring(2);
        if (!matchplayers.contains(ObjectId.fromHexString(userid))) {
          subscriptions.remove(sub);
        }
      }
    });

    // add subscriptions for players we have matches with but aren't subscribed to
    for (ObjectId id in matchplayers) {
      await realm!.query<models.Player>("_id == oid($id)").subscribe(
          name: "p=$id",
          waitForSyncMode: WaitForSyncMode.firstTime,
          update: true);
    }
)

I would probably go with option 3 - unless space is a really big concern. Thousands of objects doesn’t seem that much and should sync down fairly fast. You could reduce data transfer/storage needs by syncing the player images on demand - e.g. when a match is created for your user, iterate over all the players involved and download their images. That way you’ll have every player metadata offline, but the large files for only relevant players. You could also implement a cleanup mechanism that removes player images for matches older than x months.

Technically speaking, updating the subscription every time a match is created is not what would be considered “often”, so your current approach would still be good enough. The main challenge there is that the subscription can grow substantially over time meaning that at some point it will be more expensive to synchronize the new subscription than to download the player data that matches it (imagine after 1000 matches, you’d have 22000 or clauses in the query - realm.query<Player>('id == match1oponent1 || id == match1oponent2 || ...)).

1 Like

Thanks, that was very helpful. The player docs should be pretty small and the image field is only a url so maybe syncing them all is just easiest like you said. I think I may be prematurely trying to optimize something that will likely never be an isssue.

Thanks again!