Uploaded image for project: 'Realm .NET SDK'
  1. Realm .NET SDK
  2. RNET-767

Overhead of subscriptions makes them unusable in real-world scenarios

      What happened?

      This issue escalates concerns raised in https://github.com/realm/realm-dotnet/discussions/2775. We have since had users reporting slowdowns in our game as a result of subscription usage. I've been able to reproduce the performance issues, and have isolated to a test project.

      In the previous discussion, it was diligently explained that some overhead is expected from having subscriptions, but as we scale up to actual user workloads it's looking more and more like we cannot use subscriptions altogether.

      In providing this test project and data, I hope there can be some kind of optimisations / improvement at realm's end to make this work better. If not, I am interested in any suggestions on how we can reduce the overhead, or work around it altogether.

      Repro steps

      Test project: test project.zip

      Note that for the realm models this test project has our game package included via nuget. If required i can pull the models out.

      Output of test project shows the overhead of two example subscriptions:

      ```md

      Running with 0 subscriptions active

      Refresh started...
      Refresh completed! (0ms)

      Performing arbitrary write...
      Write completed.

      Refresh started...
      Refresh completed! (0ms)


      Running with 1 subscriptions active

      Refresh started...
      Got callback (1986 beatmaps)
      Refresh completed! (5ms)

      Performing arbitrary write...
      Write completed.

      Refresh started...
      Got callback (1986 beatmaps)
      Refresh completed! (4387ms)


      Running with 2 subscriptions active

      Refresh started...
      Got callback (18231 beatmaps)
      Refresh completed! (2ms)

      Performing arbitrary write...
      Write completed.

      Refresh started...
      Got callback (1986 beatmaps)
      Got callback (18231 beatmaps)
      Refresh completed! (74392ms)

      The data structure of interest here is that one `BeatmapSetInfo` can have multiple `BeatmapInfo` [children](https://github.com/ppy/osu/blob/deb108816cd17e92f5700391e65bd03e34aa7e70/osu.Game/Beatmaps/BeatmapSetInfo.cs#L34).
      
      The run with one subscription has the subscription on the top-level collection (`BeatmapSetInfo`) and the overhead is around 4 seconds for a collection of 1982 realm objects.
      
      The run with two subscriptions adds a subscription on the child collection (`BeatmapInfo`) with a total overhead of 74 seconds for a collection of 18215 realm objects.
      
      (as an aside, the user-reported case in our deployed production build has 7 active subscriptions on various realm collections, and a total refresh delay of 85 seconds. i've extracted only the two most expensive subscriptions for this reproduction - the rest are filtered to GUIDs and add less overhead)
      
      From my perspective, these object numbers are not large enough to cause concern.
      
      We do have other linked realm models which could be adding hidden overheads due to how the data diff is done internally (`BeatmapSetInfo` has many `File`s and `BeatmapInfo` has attached `BeatmapDifficulty`, `Ruleset` and `BeatmapMetadata`). I'm not immediately sure whether these are adding overhead but could test further if required.
      
      Looking at what is being done in this sample project, from a high level it feels like there has to be a way for this to be optimised at realm's end.
      
      ### Version
      
      10.11.2
      
      ### What SDK flavour are you using?
      
      Local Database only
      
      ### What type of application is this?
      
      Other
      
      ### Client OS and version
      
      OS agnostic
      
      ### Code snippets
      
      ```csharp
      using System.Diagnostics;
      using osu.Game.Beatmaps;
      using Realms;
      
      string filename = Path.Combine(Environment.CurrentDirectory, "client.realm");
      
      var realmConfiguration = new RealmConfiguration(filename)
      {
          SchemaVersion = 14,
      };
      
      List<IDisposable> subscriptions = new List<IDisposable>();
      
      using (var realm = Realm.GetInstance(realmConfiguration))
      {
          var beatmapSets = realm.All<BeatmapSetInfo>();
          var beatmaps = realm.All<BeatmapInfo>();
      
          RunTest(realm, beatmapSets);
      
          subscriptions.Add(beatmapSets.SubscribeForNotifications(Callback));
      
          RunTest(realm, beatmapSets);
      
          subscriptions.Add(beatmaps.SubscribeForNotifications(Callback2));
      
          RunTest(realm, beatmapSets);
      }
      
      void RunTest(Realm realm, IQueryable<BeatmapSetInfo> beatmapSets)
      {
          Console.WriteLine();
          Console.WriteLine("------------");
          Console.WriteLine($"Running with {subscriptions.Count} subscriptions active");
      
          Refresh(realm);
      
          Console.WriteLine();
          Console.WriteLine("Performing arbitrary write...");
          realm.Write(() => beatmapSets.First().DeletePending = true);
          Console.WriteLine("Write completed.");
      
          Refresh(realm);
      }
      
      static void Refresh(Realm realm)
      {
          var stopwatch = new Stopwatch();
      
          stopwatch.Start();
          Console.WriteLine();
          Console.WriteLine("Refresh started...");
          realm.Refresh();
          Console.WriteLine($"Refresh completed! ({stopwatch.ElapsedMilliseconds}ms)");
      }
      
      static void Callback(IRealmCollection<BeatmapSetInfo> sender, ChangeSet changes, Exception error)
      {
          Console.WriteLine($"Got callback ({sender.Count} beatmaps)");
      }
      
      static void Callback2(IRealmCollection<BeatmapInfo> sender, ChangeSet changes, Exception error)
      {
          Console.WriteLine($"Got callback ({sender.Count} beatmaps)");
      }
      

      Stacktrace of the exception/crash you're getting

      No response

      Relevant log output

      No response

            Assignee:
            Unassigned Unassigned
            Reporter:
            unitosyncbot Unito Sync Bot
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: