Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore options for reducing the payload sent for live queries #7

Closed
n1ru4l opened this issue Aug 14, 2020 · 11 comments
Closed

Explore options for reducing the payload sent for live queries #7

n1ru4l opened this issue Aug 14, 2020 · 11 comments
Labels
help wanted Extra attention is needed

Comments

@n1ru4l
Copy link
Owner

n1ru4l commented Aug 14, 2020

A library such as https://www.npmjs.com/package/fast-json-patch could be used to only send patches over the network for altering the existing result on the client.

@n1ru4l
Copy link
Owner Author

n1ru4l commented Aug 15, 2020

The problem with fast-json-patch it is not easy to create "efficient" patches for lists. E.g. instead of a "remove one item from array" instruction you will get a patch instructions for updating all elements from the array.

Maybe it is possible to create better patches using fast-json-patch by denormalizing the GraphQL result tree first.

e.g.

{
  "node": {
    "__typename": "Foo",
    "id": "18d821",
    "items": [
      {
        "__typename": "Bar",
        "id": "12129fc",
        "name": "hello"
      },
      {
        "__typename": "Bar",
        "id": "12ksasfc",
        "name": "foob"
      }
    ]
  }
}

->

{
  "__objects": {
    "18d821": {
      "__typename": "Foo",
      "items": ["$$ref:12129fc", "$$ref:12ksasfc"]
    },
    "12129fc": {
      "__typename": "Bar",
      "name": "hello"
    },
    "12ksasfc": {
      "__typename": "Bar",
      "name": "foob"
    }
  },
  "__tree": {
    "node": "$$ref:18d821"
  }
}

which should result in smaller patches and also less patch complexity.

Let's say the order of array elements changes. That would result in the following patch or the first response:

[
  {
    "op": "replace",
    "path": "/node/items/1/name",
    "value": "hello"
  },
  {
    "op": "replace",
    "path": "/node/items/1/id",
    "value": "12129fc"
  },
  {
    "op": "replace",
    "path": "/node/items/0/name",
    "value": "foob"
  },
  {
    "op": "replace",
    "path": "/node/items/0/id",
    "value": "12ksasfc"
  }
]

For the second object that would be:

[
  {
    "op": "replace",
    "path": "/__objects/18d821/items/1",
    "value": "$$ref:12129fc"
  },
  {
    "op": "replace",
    "path": "/__objects/18d821/items/0",
    "value": "$$ref:12ksasfc"
  }
]

@n1ru4l
Copy link
Owner Author

n1ru4l commented Sep 11, 2020

See #40

@willstott101
Copy link

Is your approach in #40 inventing a new wire-format to avoid shortcomings in existing json patch libraries? Or is a normal standards-compliant JSON patch for a normal standards-compliant GQL result sent over the wire? Which seems far more portable to me.

Would it be possible to use your approach to help generate a better - but compliant - JSON patch? Such that a client need not know about anything but json patch? Or is JSON patch as a standard just completely in-appropriate?

@n1ru4l
Copy link
Owner Author

n1ru4l commented Oct 5, 2020

Hey @willstott101, thanks for showing interest 😊

Currently, it is meant to normalize a GraphQL query and then create a more efficient JSON patch on the server-side. On the client-side, it applies the JSON-patch to the previous normalized result and then deflates it to the "expected" GraphQL result that can be consumed by relay or apollo-client.

Ideally, the client only needs to know how to apply JSON patch operations and denormalize (inflate) the result.

You could skip the normalization part and create in-efficient patches that send a lot of redundant data to the client (which might be fine since you probably don't have any HTTP overhead for a WebSocket connection), depending on your schema.

I realized that having a GraphQL list that changes frequently could result in pretty inefficient patches, which then results in a patch operation that is bigger than the actual query result we are diffing against. In that case, you would probably be better off skipping the JSON patch completely. See the README https://github.com/n1ru4l/graphql-live-queries/pull/40/files#diff-d018057eb168caad73104f87ad8ffca9 for an example.

It is also possible to decouple those two parts into denormalization and json-patch generation layers that could be composed.

The normalization is not mature enough (that's why I did not merge it yet), it does not handle nested types with field arguments and aliases.

All methods have some kind of trade-offs I guess. The idea is to find the best balance.

For now, I am more focused on improving query invalidation functionality. As I think fetching some more data isn't a problem in the app I am using this library right now.

But this is definitely a thing I wanna get back too once I got some more time.

I hope that information was helpful to you!

If you have some awesome ideas or even wanna help out, let me know :)

@n1ru4l
Copy link
Owner Author

n1ru4l commented Nov 10, 2020

I started a more generic json patch implementation here: #243

@n1ru4l
Copy link
Owner Author

n1ru4l commented Nov 30, 2020

@willstott101 Check out https://github.com/n1ru4l/graphql-live-query/tree/main/packages/graphql-live-query-patch

This is sufficient for my needs right now, let me know whether this is what you had in mind. For now, I don't plan on applying any further optimizations to the execution result before doing the JSON diffing.

@n1ru4l n1ru4l closed this as completed Nov 30, 2020
@willstott101
Copy link

Yeah that looks nice and simple to me, I think the diffing previous/next is a fine plug and play solution, and json patch libraries clearly make that quite doable. I like the simplicity (assuming what's in the readme is exactly the wire format) of just adding a patch key to the query result.

@n1ru4l
Copy link
Owner Author

n1ru4l commented Nov 30, 2020

The only issue I see right now lists. The patches generated when an item is removed are not so optimal IMHO 😅. It would be nice if json-patch would support some kind of custom object identifier e.g. via id property.

The README JSON payloads are examples from actually running the middleware :)

@willstott101
Copy link

Yeah that could help, I can see it being tricky to implement without either being excruciatingly slow, or needing some kind of id hint.

In many implementations I wonder if it could end up making more sense building the patches manually from events or, depending on the back-end architecture, even from two states as you use the library to do already.

I suspect it would be difficult to get anything optimal without building the patches yourself, but the key thing for me is that anyone can find out how to make a json patch, and can test it's doing what they expect.

@n1ru4l
Copy link
Owner Author

n1ru4l commented Nov 30, 2020

Yeah, ideally a fully reactive backend would be the best solution, another solution for reducing the over-head could be to only re-execute partials of a query (and thus only having to diff smaller payloads). Still, that is not optimal for lists, I will do some research for a better list diffing implementation.

@n1ru4l
Copy link
Owner Author

n1ru4l commented Nov 30, 2020

I created #308 for tracking this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants