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

Memory leaks with continue updated input data #270

Closed
shenenya opened this issue Nov 16, 2019 · 33 comments · Fixed by #280
Closed

Memory leaks with continue updated input data #270

shenenya opened this issue Nov 16, 2019 · 33 comments · Fixed by #280
Assignees

Comments

@shenenya
Copy link

shenenya commented Nov 16, 2019

These is memory leaks with vega-embed. For example, with following example, by clicking button update. The memory used keeps increasing. Is there any way to solve it? Thanks.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-lite@4.0.0-beta.2"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-embed@5"></script>
</head>

<body>
  <div id="vis"></div>

  <script>
    const spec1 = {
      $schema: "https://vega.github.io/schema/vega-lite/v3.json",
      description: "A simple bar chart with embedded data.",
      width: 360,
      data: {
        values: [
          { a: "A", b: 28 },
          { a: "B", b: 55 },
          { a: "C", b: 43 },
          { a: "D", b: 91 },
          { a: "E", b: 81 },
          { a: "F", b: 53 },
          { a: "G", b: 19 },
          { a: "H", b: 87 },
          { a: "I", b: 52 }
        ]
      },
      mark: "bar",
      encoding: {
        x: { field: "a", type: "ordinal" },
        y: { field: "b", type: "quantitative" },
        tooltip: { field: "b", type: "quantitative" }
      }
    };
    const spec2 = {
      $schema: "https://vega.github.io/schema/vega-lite/v3.json",
      description: "A simple bar chart with embedded data.",
      width: 500,
      data: {
        values: [
          { a: "A", b: 33 },
          { a: "B", b: 32 },
          { a: "C", b: 16 },
          { a: "D", b: 28 },
          { a: "E", b: 35 },
          { a: "F", b: 81 },
          { a: "G", b: 90 },
          { a: "H", b: 10 },
          { a: "I", b: 5 }
        ]
      },
      mark: "bar",
      encoding: {
        x: { field: "a", type: "ordinal" },
        y: { field: "b", type: "quantitative" },
        tooltip: { field: "b", type: "quantitative" }
      }
    };
    let spec = spec1;
    function myFunction() {
      setInterval(function() {
        if (spec === spec1) spec = spec2;
        else spec = spec1;

        vegaEmbed("#vis", spec)
          .then()
          .catch(console.warn);
      }, 1000);
    }
  </script>

  <button onclick="myFunction()">Update</button>
</body>
@domoritz
Copy link
Member

domoritz commented Nov 17, 2019

In order to free resources when they are not needed anymore, you need to call view.finalize. Here is the updated code for your example

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-lite@4.0.0-beta.2"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-embed@5"></script>
</head>

<body>
  <div id="vis"></div>

  <script>
    const spec1 = {
      $schema: "https://vega.github.io/schema/vega-lite/v4.json",
      width: 360,
      data: {
        values: [
          { a: "A", b: 28 },
          { a: "B", b: 55 },
          { a: "C", b: 43 },
          { a: "D", b: 91 },
          { a: "E", b: 81 },
          { a: "F", b: 53 },
          { a: "G", b: 19 },
          { a: "H", b: 87 },
          { a: "I", b: 52 }
        ]
      },
      mark: "bar",
      encoding: {
        x: { field: "a", type: "ordinal" },
        y: { field: "b", type: "quantitative" },
        tooltip: { field: "b", type: "quantitative" }
      }
    };
    const spec2 = {
      $schema: "https://vega.github.io/schema/vega-lite/v4.json",
      description: "A simple bar chart with embedded data.",
      width: 500,
      data: {
        values: [
          { a: "A", b: 33 },
          { a: "B", b: 32 },
          { a: "C", b: 16 },
          { a: "D", b: 28 },
          { a: "E", b: 35 },
          { a: "F", b: 81 },
          { a: "G", b: 90 },
          { a: "H", b: 10 },
          { a: "I", b: 5 }
        ]
      },
      mark: "bar",
      encoding: {
        x: { field: "a", type: "ordinal" },
        y: { field: "b", type: "quantitative" },
        tooltip: { field: "b", type: "quantitative" }
      }
    };
    let spec = spec1;
    let view;

    function myFunction() {
      setInterval(function() {
        if (spec === spec1) spec = spec2;
        else spec = spec1;
        
        if (view) {
           view.finalize();
        }

        vegaEmbed("#vis", spec)
          .then(function(result) {
          // save view so we can finalize it
          	view = result.view;
        	})
          .catch(console.warn);
      }, 1000);
    }
  </script>

  <button onclick="myFunction()">Update</button>
</body>

Try it at https://blockbuilder.org/domoritz/cfdc1c68553a7b942ad3055a63995dd3.

@domoritz
Copy link
Member

The cleaner (and faster) solution is to use the view API instead. I'll leave this as an exercise for the reader ;-)

https://vega.github.io/vega/docs/api/view/

@shenenya
Copy link
Author

Thanks. How I get it done with react-vega?

@shenenya
Copy link
Author

I tried the updated code. But the memory is still increasing.

@domoritz
Copy link
Member

How are you tracking memory usage? can you use the browser memory debugger? It should also show who is holding on to memory.

@shenenya
Copy link
Author

I only open one tab in chrome, that runs the example above. Do not do anything else with the chrome. Keep looking at the process named "Google Chrome Helper (GPU)". It is found that it keeps increasing, from hundreds MB to several GBs.

@domoritz
Copy link
Member

Can you find out what's holding on to the memory?

@domoritz domoritz reopened this Nov 17, 2019
@shenenya
Copy link
Author

shenenya commented Nov 17, 2019

I debug the problem with Chrome Task Manager. I found the memory used by the tab keep increasing slowly. But the task named "GPU Process" keeps increasing markedly.

@shenenya
Copy link
Author

shenenya commented Nov 17, 2019

The chrome performance looks as follows:
截屏2019-11-17下午4 34 10

@domoritz
Copy link
Member

The listeners should get deleted on finalize but somehow the number keeps increasing. I have to dig more into this tomorrow.

Screen Shot 2019-11-17 at 00 34 42

@domoritz domoritz self-assigned this Nov 17, 2019
@shenenya
Copy link
Author

Thanks.

@shenenya
Copy link
Author

Do you find why the memory leaks happen?

@domoritz
Copy link
Member

No, I didn't have time to look yet.

@shenenya
Copy link
Author

Is there someone still take care of this problem? I believe it is an important problem for practical usage of vega-embed and related vega, vega-lite. I would like to help. Would anyone give some tip for how to debug and challenge this problem? Thanks.

@domoritz
Copy link
Member

I've been swamped with other requests but completely agree that this is an important issue to look into. What I'm curious about is what objects are holding onto memory that may not need to. I'd use the browser memory debugger to do that.

@domoritz
Copy link
Member

Looks like the culprit is Vega-Embed. Without Vega-Embed, memory consumption stays low. I think the problem may be the event handlers. Let me try to fix it.

Screen Shot 2019-11-26 at 12 14 16

@domoritz
Copy link
Member

Fixed in #280

@shenenya
Copy link
Author

Great. Looking forward to the next release.

@shenenya
Copy link
Author

I believe the problem appear again. The screenshot below show the memory profile of the example above.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-lite@4.12.1"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
</head>

<body>
  <div id="vis"></div>

  <script>
    const spec1 = {
      $schema: "https://vega.github.io/schema/vega-lite/v4.json",
      description: "A simple bar chart with embedded data.",
      width: 360,
      data: {
        values: [
          { a: "A", b: 28 },
          { a: "B", b: 55 },
          { a: "C", b: 43 },
          { a: "D", b: 91 },
          { a: "E", b: 81 },
          { a: "F", b: 53 },
          { a: "G", b: 19 },
          { a: "H", b: 87 },
          { a: "I", b: 52 }
        ]
      },
      mark: "bar",
      encoding: {
        x: { field: "a", type: "ordinal" },
        y: { field: "b", type: "quantitative" },
        tooltip: { field: "b", type: "quantitative" }
      }
    };
    const spec2 = {
      $schema: "https://vega.github.io/schema/vega-lite/v4.json",
      description: "A simple bar chart with embedded data.",
      width: 500,
      data: {
        values: [
          { a: "A", b: 33 },
          { a: "B", b: 32 },
          { a: "C", b: 16 },
          { a: "D", b: 28 },
          { a: "E", b: 35 },
          { a: "F", b: 81 },
          { a: "G", b: 90 },
          { a: "H", b: 10 },
          { a: "I", b: 5 }
        ]
      },
      mark: "bar",
      encoding: {
        x: { field: "a", type: "ordinal" },
        y: { field: "b", type: "quantitative" },
        tooltip: { field: "b", type: "quantitative" }
      }
    };
    let spec = spec1;
    function myFunction() {
      setInterval(function() {
        if (spec === spec1) spec = spec2;
        else spec = spec1;

        vegaEmbed("#vis", spec)
          .then()
          .catch(console.warn);
      }, 1000);
    }
  </script>

  <button onclick="myFunction()">Update</button>
</body>

截屏2020-05-18 上午9 17 01

@domoritz
Copy link
Member

Please finalize the embed result as described in the docs.

@shenenya
Copy link
Author

shenenya commented May 18, 2020

We use react-vega, which calls finalize as follows. Is there something we should do more? Thanks.

  clearView() {
    this.modifyView(view => {
      view.finalize();
    });
    this.viewPromise = undefined;

    return this;
  }

@domoritz
Copy link
Member

I don't know how this fits into the react lifecycle. However, your example above does not contain finalize. Can you add finalize to see whether it fixes the issue? If yes, then we know the problem is in react vega.

Thanks for your help!

@shenenya
Copy link
Author

I tried to call finalize as follows. And the memory cost show as the screenshot image.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-lite@4.12.1"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
</head>

<body>
  <div id="vis"></div>

  <script>
    const spec1 = {
      $schema: "https://vega.github.io/schema/vega-lite/v3.json",
      description: "A simple bar chart with embedded data.",
      width: 360,
      data: {
        values: [
          { a: "A", b: 28 },
          { a: "B", b: 55 },
          { a: "C", b: 43 },
          { a: "D", b: 91 },
          { a: "E", b: 81 },
          { a: "F", b: 53 },
          { a: "G", b: 19 },
          { a: "H", b: 87 },
          { a: "I", b: 52 }
        ]
      },
      mark: "bar",
      encoding: {
        x: { field: "a", type: "ordinal" },
        y: { field: "b", type: "quantitative" },
        tooltip: { field: "b", type: "quantitative" }
      }
    };
    const spec2 = {
      $schema: "https://vega.github.io/schema/vega-lite/v3.json",
      description: "A simple bar chart with embedded data.",
      width: 500,
      data: {
        values: [
          { a: "A", b: 33 },
          { a: "B", b: 32 },
          { a: "C", b: 16 },
          { a: "D", b: 28 },
          { a: "E", b: 35 },
          { a: "F", b: 81 },
          { a: "G", b: 90 },
          { a: "H", b: 10 },
          { a: "I", b: 5 }
        ]
      },
      mark: "bar",
      encoding: {
        x: { field: "a", type: "ordinal" },
        y: { field: "b", type: "quantitative" },
        tooltip: { field: "b", type: "quantitative" }
      }
    };
    let spec = spec1;
    function myFunction() {
      setInterval(function() {
        if (spec === spec1) spec = spec2;
        else spec = spec1;

        vegaEmbed("#vis", spec)
          .then(function(res){
          	res.finalize();
          })
          .catch(console.warn);
      }, 1000);
    }
  </script>

  <button onclick="myFunction()">Update</button>
</body>

截屏2020-05-18 下午3 22 10

@domoritz
Copy link
Member

That’s good, right?

@shenenya
Copy link
Author

Yes. I proposed an issue for react-vega.

@brandon-marginalunit
Copy link

brandon-marginalunit commented May 22, 2020

We're having what I think may be the same problem too; the difference for us is that we aren't using react-vega nor vega-embed. We're creating a new View directly, and then updating it via view.data("foo", [ ... ]).runAsync(). As with the others here, our memory usage monotonically increases each time we update with new data.

Since we aren't creating a new Vega instance each time, or using a fresh config each time, I don't think the .finalize() mitigation applies to us (please correct me if I'm wrong). From looking at the memory profiler, it seems like a bunch of detached SVG group elements are not being released by Vega:

DevTools_-_muse-dev_local_8090_

Unfortunately I don't have a clean minimal-reproduction example.

@domoritz
Copy link
Member

Can you try to reproduce the issue with a small example and file an issue with Vega directly?

@shenenya
Copy link
Author

Is there any plan to update react-vega to fix the problem? Or else is there any way to use vega-embed with react directly?

@brandon-marginalunit
Copy link

@domoritz I can file with Vega directly, but I'm not sure I have time to narrow it down to a small example. I'll see what I can do.

@domoritz
Copy link
Member

Or else is there any way to use vega-embed with react directly?

That's easily possible with a reference. Something like this:

class Vega {
  componentDidMount() {
    var embed = await vegaEmbed(this.refs.vis, spec);
  }

  render() {
    return (
      <div ref="vis" />
    );
  }
}

You may also be interested in vega/vega#2652

@shenenya
Copy link
Author

You may also be interested in vega/vega#2652

Is the memory leak caused by the Vega caches?

@jheer
Copy link
Member

jheer commented May 27, 2020

Memory leaks could arise from multiple sources:

  1. Creating multiple visualizations (new View instances) without calling finalize on the old View instances.
  2. Using streaming data that involves nested specifications (basically, group marks), in which deleted data leads to deleted groups with content that remains in caches.

PR vega/vega#2652 should fix number 2 above. Number 1 needs to be addressed by developer code (or in libraries such as vega-embed or vega-react).

@shenenya
Copy link
Author

The memory leaks still happen while react-vega calls finalize. Is it caused by number 2 above?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants