Real-time data sync vs. Firebase?

I’m a huge Parse fan from back in the day. I’m working now on a cross-platform app—Apple platforms to begin with and then extending from there. In selecting a back-end system, I’m thinking of optimizing for ones that automate the process of keeping all data in sync between client and server, since that tends to be a pain to do manually. (Feel free to convince me I’m thinking about this wrong—it’s certainly not the only dimension I could prioritize.)

My sense is that Firebase does well with that: whether a particular object is client-side, server-side, or needs syncing isn’t something the developer needs to think about.

Reading the Parse docs (server, iOS/Objc, and Swift), I get the sense that Parse isn’t quite as set-it-and-forget it: there are a bunch of building blocks to make implementing client-server sync and offline storage easier, but one still has to manage it semi-manually on an object level.

Am I understanding and thinking about this correctly?

Thanks!

If you want to sync data in real time you need to enable a live qiery server for the tables that you want to sync. It will open a websocket connection and then you can listen for the update event to fetch data every time something changes. This is about 40 lines of frontent code for each table. This is how it happens in parse. Yet If you have more than 3 tables that you want a realtime sync for, you may have difficulties. Also on high traffic websockets may not be that reliable, but putting a redis cache should help significantly. Overall though I feel like firebase is better for realtime sync.

All that said this is what kept me from using Firebase Reasons Not To Use Firebase

Where might I find an example of these 40 lines of code? I’d be interested in both Swift (for SwiftUI) and JS (React).

Here is a snippet for react.

useEffect(() => {
  async function subscribeToEvent() {
    const Parse = (await import("parse")).default;
    Parse.initialize(process.env.NEXT_PUBLIC_APP_ID);
    Parse.serverURL = `${process.env.NEXT_PUBLIC_SERVER_URL}/parse`;

    const query = new Parse.Query("Prize");
    const subscription = await query.subscribe();

    subscription.on("create", () => {
      // do something when a record is created in the Prize table
    });
    subscription.on("update", () => {
      // do something when a record is updated in the Prize table
    });
    subscription.on("delete", () => {
      // do something when a record is deleted in the Prize table
    });
  }
  subscribeToEvent();
}, []);

You also need to enable the live query server on backend in your server parameters and indicate the table that you’re going to be listening to.

  liveQuery: {
    classNames: ['Prize']
  }
let httpServer = require('http').createServer(app);
httpServer.listen(port);
ParseServer.createLiveQueryServer(httpServer);
1 Like

Bear in mind that live queries hit performance and are hard to scale. I myself use useSWR package that is sending HTTP requests every X seconds, which mirrors the livequery performance without binding the client to the server.

Also make sure your React app is not in strict mode in production, because useEffect fires twice in strict mode so each your client will be getting 2 live query connections, which will worsen performance.

Thank you! Two follow-ups on the performance front:

  1. What sort of scale is problematic in particular for large queries? Is it the number of tables? The number of distinct queries? Or the number of clients querying?
  2. By default do live queries use websockets? Or something else?
1 Like

I’m not an expert but here’s what I know:

  1. It’s the number of tables that hits performance. As far as I’ve read it’s not recommended to have more than 3 tables (classes) connected to the live query server. In general scaling live queries requires using redis cache. Parse server has a module for caching live queries with redis cache.
    The number of clients connected also matters. I remember reading on this forum that a single live query server can handle about 30k simultaneous clients.

  2. As far as I know Parse live query server uses Websockets under the hood. And websockets use HTTP connections under the hood. So in my opinion if you can use HTTP connections directly it’s better. For react there is a package called useSWR that can automatically send HTTP connections to fetch the recent data (every second for example). Which means that with it your clients are not bound to the server instance and can be served by different instances as required by your load balancer.

This is not techical advice though.

This is how to use useSWR module to emulate live queries without having to use the live query server.

// client side
import useSWR from "swr";

// inside react component
const [numberOfPeople, setNumberOfPeople] = useState(0);

// this is a fetcher function to connect to the actual function in the backend

function fetchNumberOfPeople({numberOfPeople}) {
const Parse = (await import("parse")).default;
Parse.initialize(process.env.NEXT_PUBLIC_APP_ID);
Parse.serverURL = `${process.env.NEXT_PUBLIC_SERVER_URL}/parse`;

const response = await Parse.Cloud.run("getNumberOfPeople", { groupId});
  if (response && response.sts) {
    setNumOfPeople(response.msg);
  }
}

// this is the function that emulates the live queries
useSWR({ groupId, setNumberOfPeople}, fetchNumberOfPeople, { refreshInterval: 1000 }); // refresh every second;

// server side
// this is the actual function on the backend

Parse.Cloud.define("getNumberOfPeople", async (req) => {
  const { groupId} = req.params;
  try {
    const qNumOfPeople= new Parse.Query("People");
    qNumOfPeople.equalTo("groupId", groupId);
    const rNumOfPeople = await qNumOfPeople.count({ useMasterKey: true });

    if (rNumOfPeople ) {
      return { sts: true, msg: rNumOfPeople };
    } else {
      return { sts: false };
    }
  } catch (err) {
    console.log("Error in getNumberOfPeople", err.message);
    return err;
});

I’m a huge fan of Parse and I’m also a contributor.

I agree that Parse is missing something that you can easily find on Firebase and Realm, something that every Parse developer had to develop multiple times in every Parse Apps.

If you built a robust app, most probably you had to:

  • make a query
  • store objects locally
  • enable a LiveQuery
  • keep the stored objects in sync locally on create, update, delete, enter, leave
  • handle the online, offline, reconnection cases
  • run a fallback query from time to time because you never know if the LiveQuery worked
  • handle exceptions

The above process has to be implemented for each Class that you need in the app.