UPDATE: Issue will be solved in next version of Carto. See github

I'm using Carto VL to build a simple webapp for some data inspection within our GIS team.

The web-app can be found here (Link might not work after future commits, use url of code below)

Zooming, panning etc however is far from smooth and I am wondering how I could improve performance. Chrome uses tons of RAM (>1GB) to render the website.

There are a couple of elements in this script that can make it slow:

  1. The layer is a join table.
  2. One table contains the geometries, the other table contains the data. Both tables have a couple of indices.
  3. Number of polygons is 17000. I could simplify those but my understanding is that the advantage of using vector layers is that this is done for you.

My script is hosted using Gihub Pages and the code can be found here. The code should also work locally.

This is my first attempt to use Carto-VL

<!DOCTYPE html>
  <title>Aqueduct 3.0 QA | CARTO</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta charset="UTF-8">
  <!-- Include CARTO VL JS -->
  <script src="https://libs.cartocdn.com/carto-vl/v0.5.3/carto-vl.min.js"></script>
  <!-- Include Mapbox GL JS -->
  <script src="https://libs.cartocdn.com/mapbox-gl/v0.45.0-carto1/mapbox-gl.js"></script>
  <!-- Include Mapbox GL CSS -->
  <link href="https://libs.cartocdn.com/mapbox-gl/v0.45.0-carto1/mapbox-gl.css" rel="stylesheet" />
  <link href="https://carto.com/developers/carto-vl/examples/maps/style.css" rel="stylesheet">
  <div id="map"></div>
  <aside class="toolbox">
    <div class="box">
        <p class="description open-sans">Style, Popup and Join</p>
      <footer class="js-footer"></footer>
  <div id="loader">
    <div class="CDB-LoaderIcon CDB-LoaderIcon--big">
      <svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
        <circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
    const map = new mapboxgl.Map({
      container: 'map',
      style: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
      center: [0, 30],
      zoom: 3,
      scrollZoom: true,
      dragRotate: false,
      touchZoomRotate: false,

    // Define user
      user: 'wri-playground',
      apiKey: 'default_public'

    // Define layer
    var sql = `SELECT l.pfaf_id, l.the_geom, l.the_geom_webmercator, l.cartodb_id, r.pfafid_30spfaf06, r.temporal_resolution, r.year, r.month, r.waterstress_label_dimensionless_30spfaf06, r.waterstress_category_dimensionless_30spfaf06, r.waterstress_score_dimensionless_30spfaf06, r.waterstress_raw_dimensionless_30spfaf06, r.avg1y_ols_ols10_weighted_waterstress_dimensionless_30spfaf06, r.avg1y_ols_ols10_waterstress_dimensionless_30spfaf06, r.ols_ols10_waterstress_dimensionless_30spfaf06, r.ols_ols10_ptotww_m_30spfaf06 FROM y2018m07d18_rh_upload_hydrobasin_carto_v01_v02 l, y2018m07d18_rh_qa_annual_weighted_unweighted_allbasins_v01_v02 r WHERE l.pfaf_id = r.pfafid_30spfaf06 AND r.year = 2014 AND r.month =12 AND r.temporal_resolution = 'year'`

    const source = new carto.source.SQL(sql);

    const viz = new carto.Viz(`
      color: ramp(buckets($waterstress_label_dimensionless_30spfaf06,
        "Low - Medium",
        "Medium - High",
        "Extremely High",
        "Arid and Low Water Use"]),
      strokeWidth: 0.5
      @pfaf_id: $pfaf_id
      @waterstress_raw_dimensionless_30spfaf06: $waterstress_raw_dimensionless_30spfaf06


    const layer = new carto.Layer('layer', source, viz);

    layer.addTo(map, 'watername_ocean');

    layer.on('loaded', hideLoader);

    function hideLoader() {
      document.getElementById('loader').style.opacity = '0';

    // Interactivity
    const interactivity = new carto.Interactivity(layer);
    interactivity.on('featureEnter', featureEvent => {
      featureEvent.features[0].strokeColor.blendTo('rgba(255, 0, 0, 0.5)', 100);
    interactivity.on('featureLeave', featureEvent => {

    const interactivity = new carto.Interactivity(layer);
    interactivity.on('featureClick', featureEvent => {
        const coords = featureEvent.coordinates;
        const feature = featureEvent.features[0];
        new mapboxgl.Popup()
            .setLngLat([coords.lng, coords.lat])
            .setHTML(`<p>pfaf_id: ${feature.variables.pfaf_id.value}</p><br/>
                      <p>waterstress_raw_dimensionless_30spfaf06: ${feature.variables.waterstress_raw_dimensionless_30spfaf06.value}</p>`)

  • 1
    Could you try to modify the query to use an INNER JOIN, instead of a CROSS JOIN to improve the speed in which the SQL query of the source is executed?Also, I would recommend simplifying the geometries with ST_Simplify(the_geom, 0.001) before loading the 17000 polygons of the map in order to reduce the number of vertices to load. – oriolbx Jul 23 '18 at 15:08
  • still no smooth performance. Using 0.001 for ST_Simplify only reduced the table size from 280MB to 250MB. Trying 0.01 now. Updated index.html here: github.com/rutgerhofste/carto_qa/tree/… – RutgerH Jul 23 '18 at 16:21
  • using 0.01 brings it down to 55MB but without noticible improvements in performance. – RutgerH Jul 23 '18 at 16:37
  • Conclusion so far: Query Layers are just slow. Even the example carto.com/developers/carto-vl/examples/… takes more than 5 seconds to load and has a low framerate when panning. – RutgerH Jul 23 '18 at 16:46

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.