How to Implement Server-Side Memory Management in Bokeh

Effective server-side memory management is crucial for stable, scalable Bokeh applications in production. This guide covers configuration options, custom cleanup hooks, and containerization strategies to ensure Bokeh servers gracefully release memory and avoid leaks when serving multiple users.

1. Configure Session Timeouts and Cleanup Intervals

Bokeh provides built-in CLI options to limit unused session lifetimes and trigger periodic cleanup:

Option Description Example
--unused-session-lifetime Maximum time (ms) an idle session is kept before cleanup --unused-session-lifetime 300000 (5 minutes)
--check-unused-sessions Interval (ms) between cleanup checks --check-unused-sessions 60000 (1 minute)
Note: Values are in milliseconds. Shorter lifetimes reduce memory footprint but may disconnect infrequent users. Adjust based on traffic patterns.

2. Use Custom Session Destruction Hooks

Register Python callbacks that run when a session is destroyed to explicitly release resources—close database connections, clear caches, or delete large objects.

from bokeh.io import curdoc

def cleanup_resources(session_context):
    # Example: close DB connection, clear caches
    db_conn = session_context.locals.get('db_conn')
    if db_conn:
        db_conn.close()
    cache = session_context.locals.get('data_cache')
    if cache:
        cache.clear()

# Register the hook on the current Document
curdoc().on_session_destroyed(cleanup_resources)
    

“Bokeh now supports Document.on_session_destroyed for per-session cleanup logic, enabling developers to override default GC behavior.”

3. Leverage session_context for Per-Session Data

Avoid global variables for session-specific data. Use session_context.locals to store per-session objects that Bokeh will release automatically upon cleanup.

def modify_doc(doc):
    # Store large DataFrame in session_context, not module global
    df = load_large_dataframe()
    doc.session_context.locals['data_cache'] = df

    p = figure()
    p.line(df.x, df.y)
    doc.add_root(p)

# In application startup:
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application

handler = FunctionHandler(modify_doc)
app = Application(handler)
    

4. Monitor Memory Usage in Production

Continuously monitor Bokeh server processes with psutil or external APM tools to detect unbounded memory growth.

# monitor_memory.py
import psutil, time

def monitor_bokeh_server(name_filter='bokeh'):
    while True:
        for proc in psutil.process_iter(['name','pid','memory_info']):
            if name_filter in proc.info['name']:
                rss_mb = proc.info['memory_info'].rss / (1024*1024)
                print(f"{proc.info['pid']}: {rss_mb:.1f} MB")
        time.sleep(60)
    

Tip: Run this script as a sidecar container or systemd service. Alert when memory exceeds thresholds (e.g., 1GB).
See also  How to optimize bokeh memory usage

5. Containerization and Resource Limits

Deploy Bokeh servers in Docker or Kubernetes with strict memory limits to enforce OOM (Out Of Memory) safety:

# Dockerfile snippet
FROM python:3.11-slim
RUN pip install bokeh panel
COPY . /app
CMD ["bokeh", "serve", "/app", "--allow-websocket-origin=*", \
     "--unused-session-lifetime", "300000", \
     "--check-unused-sessions", "60000"]
  

In Kubernetes, set resources.limits.memory and resources.requests.memory to desired values and configure liveness/readiness probes to restart pods on OOM failures.

See also  How to use curdoc in Bokeh

6. Advanced: Dynamic Cleanup via Tornado Lifecycle

For deep customization, tap into Tornado web server hooks to perform cleanup at HTTP connection close:

from bokeh.server.tornado import BokehTornado

class CustomTornado(BokehTornado):
    def on_connection_close(self, handler):
        # Perform session-specific cleanup
        session_context = handler.get_session_context()
        cleanup_resources(session_context)
        super().on_connection_close(handler)

# Use CustomTornado when starting server programmatically
from bokeh.server.server import Server
server = Server(applications={'/': app}, tornado_application=CustomTornado)
server.start()
  

7. Troubleshooting and Diagnostics

  • Enable debug logging: bokeh serve app.py --log-level debug to see session cleanup messages.
  • Watch for “Extra unexpected referrers” and “Failed to release session” in logs.
  • Test with --num-procs 2 to isolate per-process memory behavior.
See also  How to optimize bokeh memory usage

Quick Reference

Feature Configuration Purpose
unused-session-lifetime CLI flag Automatically cleanup idle sessions after timeout
check-unused-sessions CLI flag Periodic session cleanup interval
on_session_destroyed Document hook Custom resource cleanup logic
session_context.locals API Store per-session data safely
Container resource limits Docker/K8s config Enforce memory caps, trigger restarts

Production Checklist

  1. Enable --unused-session-lifetime & --check-unused-sessions.
  2. Register on_session_destroyed hooks to free resources.
  3. Use session_context.locals for session data isolation.
  4. Monitor server memory via psutil or APM.
  5. Containerize with strict memory limits and probes.