Как кешировать боке с помощью Redis - PullRequest
0 голосов
/ 04 мая 2020

Я использую сервер Bokeh для рендеринга графика временных рядов на карте. По мере продвижения временных рядов фокус карты смещается.

Приведенный ниже код работает, но каждая последовательность создает вызов, который отправляется в API Google (GMAP) для получения фона. Это требует времени для рендеринга. В местах, где временные ряды несколько раз смещали фокус в быстрой последовательности, фон не успел отрендериться до того, как его обновят.

Я пытался понять, могут ли эти запросы / как эти запросы быть сделано заранее, кэшировано (с использованием redis), что позволяет пользователю просматривать кэш со всеми данными, уже загруженными для каждого тика на временных сериях.

main.py

import settings
from bokeh.plotting import figure, gmap
from bokeh.embed import components
from bokeh.models import CustomJS, ColumnDataSource, Slider, GMapOptions, GMapPlot, Range1d, Button
from bokeh.models.widgets import DataTable, TableColumn, HTMLTemplateFormatter
from bokeh.layouts import column, row, gridplot, layout
from bokeh.io import show, export_png, curdoc

from filehandler import get_graph_data


"""
Get arguments from request
"""
try:
    args = curdoc().session_context.request.arguments
    pk = int(args.get('pk')[0])
except:
    pass
"""
get data for graph from file and initialise variables
"""
#load data into dictionary from file referenced by pk
data_dict = get_graph_data(pk)

no_of_markers = data_dict.get('markers') 
length_of_series = data_dict.get('length')
series_data = data_dict.get('data') #lat/lon position of each series at each point in time
series_names = series_data.get('series_names') #names of series
range_x_axis = data_dict.get('xaxis') #min/max lat co-ords
range_y_axis = data_dict.get('yaxis') #min/max lon co-ords


"""
Build data
"""
graph_source = ColumnDataSource(series_data)

"""
Build markers to show current location
"""
markers = ColumnDataSource(data=dict(lon=[], lat=[]))

"""
Build mapping layer
"""
def create_map_backdrop(centroid, zoom, tools):
    """
    Create the map backdrop, centered on the starting point
    Using GoogleMaps api
    """
    map_options = GMapOptions(lng=centroid[1],
                              lat=centroid[0],
                              map_type='roadmap',
                              zoom=zoom,
                              )

    return gmap(google_api_key=settings.MAP_KEY,
                map_options=map_options,
                tools=tools,
                )

#set map focus
centroid = (graph_source.data['lats'][0][0],
            graph_source.data['lons'][0][0],
            )


"""
Build Plot
"""

tools="pan, wheel_zoom, reset"
p = create_map_backdrop(centroid, 18, tools)
p.multi_line(xs='lons',
             ys='lats',
             source=graph_source,
             line_color='color',
             )
p.toolbar.logo = None
p.circle(x='lon', y='lat', source=markers)


"""
User Interactions
"""

def animate_update():
    tick = slider.value + 1
    slider.value = tick

def slider_update(attr, old, new):
    """
    Updates all of the datasources, depending on current value of slider
    """
    start = timer()
    if slider.value>series_length:
        animate()
    else:
        tick = slider.value
        i=0
        lons, lats = [], []
        marker_lons, marker_lats = [], []

        while i < no_of_markers:

            #update lines
            lons.append(series_data['lons'][i][0:tick])
            lats.append(series_data['lats'][i][0:tick])

            #update markers
            marker_lons.append(series_data['lons'][i][tick])
            marker_lats.append(series_data['lats'][i][tick])

            #update iterators
            i += 1

        #update marker display
        markers.data['lon'] = marker_lons
        markers.data['lat'] = marker_lats

        #update line display
        graph_source.data['lons'] = lons
        graph_source.data['lats'] = lats    

        #set map_focus
        map_focus_lon = series_data['lons'][tick]
        map_focus_lat = series_data['lats'][tick]

        #update map focus
        p.map_options.lng = map_focus_lon
        p.map_options.lat = map_focus_lat



slider = Slider(start=0, end=series_length, value=0, step=5)
slider.on_change('value', slider_update)
callback_id = None

def animate():
    global callback_id
    if button.label == "► Play":
        button.label = "❚❚ Pause"
        callback_id = curdoc().add_periodic_callback(animate_update, 1)

    else:
        button.label = "► Play"
        curdoc().remove_periodic_callback(callback_id)

button = Button(label="► Play", width=60)
button.on_click(animate)

"""
Display plot
"""


grid = layout([[p, data_table],
                [slider, button],
                ])

curdoc().add_root(grid)

Я пытался кэшировать данные графика (p), но похоже, что это сохраняется до вызова API API.

Я исследовал кэширование карты плитки напрямую из API, а затем встраивают их в график в качестве фонового изображения (используя bokeh ImageURL), но я не могу заставить ImageUrl распознать изображение в памяти.

Документация сервера предполагает, что redis может использовать в качестве бэкэнда, поэтому мне было интересно, может ли это ускорить процесс, но когда я пытаюсь запустить его, bokeh serve myapp --allow-websocket-origin=127.0.0.1:5006 --backend=redis я получаю --backend is not a recognised command.

Есть ли способ кэшировать полностью визуализированный граф (возможно только сам документ графика), сохраняя при этом возможность взаимодействия пользователей с сюжетом; или кэшировать график gmap после его рендеринга, а затем добавить его к остальной части графика?

1 Ответ

1 голос
/ 04 мая 2020

Если это был автономный контент Bokeh (т.е. не приложение сервера Bokeh), то вы сериализуете представление JSON графика с помощью json_items и повторно гидрируете его явно в браузере с помощью Bokeh.embed_items. Это JSON потенциально может храниться в Redis, и, возможно, это будет актуально. Но сервер Bokeh не такой. После первоначального создания сеанса никогда не будет никакого «целого документа» для хранения или кэширования, только последовательность инкрементальных, частичных обновлений, которые происходят по протоколу веб-сокетов. Например, сервер говорит «этот конкретный c источник данных изменен», а браузер говорит: «Хорошо, я должен пересчитать границы и выполнить повторную визуализацию».

Тем не менее, есть некоторые изменения, которые я бы предложил.

Во-первых, вы не должны обновлять столбцы CDS один за другим. Вам не следует делать это:

# BAD
markers.data['lon'] = marker_lons
markers.data['lat'] = marker_lats

Это создаст два отдельных события обновления и два отдельных запроса на повторную визуализацию. Помимо дополнительной работы, которую это вызывает, это также тот случай, когда первое обновление гарантированно не соответствует старым / новым координатам. Вместо этого вы всегда должны обновлять CDS .data dict «атомарно», за один раз go:

source.data = new_data_dict

Дополнительно вы можете попробовать curdoc().hold, чтобы собрать обновления в меньшее количество событий. ,

...