Multiple Bokeh apps embedded on same webpage, using Flask with Gunicorn

What are you trying to do?

I’m trying to modify the flask_gunicorn_embed.py script from the github examples (link) to have multiple interactive Bokeh apps embedded on the same HTML page. My only requirements are that

  • both embedded apps are interactive, so each should be controlled by its own widget(s)

  • gunicorn will utilize multiple worker processes (i.e. --workers will be larger than 1)

If this is a question about Bokeh code, please post a Minimal, Reproducible Example so that reviewers can test and see what you see. A guide to do this is here: https://stackoverflow.com/help/minimal-reproducible-example

Yes, this is about Bokeh code. Specifically, it is focused on adapting (explained next) the flask_gunicorn_embed.py example from github.

What have you tried that did NOT work as expected?

The working procedure I’ve followed to modify flask_gunicorn_embed.py is below

First, I added a second Bokeh application. As with the existing one (used in the github example), I passed a single handler

bkapp1 = Application(FunctionHandler(bkapp))  # original
bkapp2 = Application(FunctionHandler(bkapp))  # added this  <---

Next, modify bkapp_page() to include a second application, where the server_document (doc) url parameter includes the second app’s name (I chose bkapp2)

@app.route("/", methods=["GET"])
def bkapp_page():
    script = {
        "app_one": server_document("http://localhost:8000/bkapp"),  # original
        "app_two": server_document("http://localhost:8000/bkapp2")  # added this  <---
    }

Next, modify the dict of applications submitted to BokehTornado (doc) to include the second app (bkapp2)

def bk_worker()
    BokehTornado(
        {
            "/bkapp1": bkapp,  # original
            "/bkapp2": bkapp2,  # added this  <---
        },
        extra_websocket_origins=["localhost:8000"],
    )

Lastly, modify the HTML body content in embed.html to include both apps. Replace

<body>
    <div>
        This Bokeh app...
    </div>
    {{ script|safe }}
</body>

by this

<body>
    <div>
        This Bokeh app...
    </div>
    {{ script.app_one|safe }}
    {{ script.app_two|safe }}
</body>

When I run this as recommended in the script’s docstring

gunicorn -w 4 flask_gunicorn_embed:app

the two Bokeh apps appear to be working fine (embedded on the same web page, as required).

Next, I replaced the entire app definition itself bkapp(doc) (lines 36 to 54 from flask_gunicorn_embed.py) with the sliders example from the Gallery (no changes), and everything continues to work just fine - two independent interactive versions of the same app are embeded on the same HTML page (see the attached screenshot). Each of the two slider widgets controls their own app and there are no delays or other unexpected behaviours.

This seems too simple and I’m sure I’m missing something or not following best practices.

Questions

  1. Is this the correct approach to embed multiple interactive Bokeh apps in the same HTML page using Flask and Gunicorn?
  2. Additionally, I’m concerned about using the same port (8000) in both apps’ server_document() definition (http://localhost:8000/bkapp and http://localhost:8000/bkapp2) - is this a problem, or could it cause performance issues as more apps are added in the same way?

System Details

bokeh==2.2.3
Python 3.8.5
Operating System == Ubuntu 20.04 LTS (Desktop version)

Other Links I searched through

  1. Google Groups post from 2016

@edesz There are certainly other ways to do this, but I can’t say I see anything wrong with this approach, either. The two apps have the same port because:

  • each working is running both apps (can create sessions for either), and
  • gunicorn is in front of all the workers, running on the one port

Another approach would be to manually start a bunch of bokeh worker processes behind a proxy like nginx. With that approach you could have each worker running all the apps (as you do now) or have separate workers for different apps, if that’s desirable for any reason.

1 Like

@Bryan, thanks for your answer.

After posting this question, I’ve tried my (naive) approach with more Bokeh apps (up to 10) and I don’t seem to be having any performance issues - it continues to just work. FYI - I’ve not tried this out with large datasets, so I can’t say whether it continues to scale in that respect.

I hadn’t thought of the nginx approach - that does seem like a good one too. Thanks again.

And thanks for so much effort into developing Bokeh. I am new to the project and it has allowed me to quickly create responsive interactive viz with minimal effort! Really great!

1 Like