CQL跟踪查看器:用仪表板可视化CQL痕迹
#python #visualization #cassandra #dash

介绍

随着数据的快速增长和需求,对大量数据的管理和理解变得越来越重要。 Cassandra查询语言(CQL)是一种与Apache Cassandra数据库进行交流的语言,在此过程中起了工具作用。

CQL与SQL有许多相似之处,SQL是用于管理关系数据库管理系统中的数据的标准语言。它使您可以与Cassandra进行交互,例如创建,更新和删除表,插入和查询数据。

但是,在处理大量数据时,性能是关键。这就是CQL中的跟踪。通过跟踪,您可以在查询中执行群集时跟踪查询的旅程。这包括有关查询通过的阶段的详细信息,每个阶段的持续时间以及涉及哪些节点。这些详细信息可以通过识别瓶颈或改进区域来帮助诊断问题并优化性能。

但是,由于其详细性和复杂性,解释CQL痕迹的原始输出可能令人生畏。这就是为什么可视化这些痕迹变得非常有益的原因。可视化可以通过提供更直观和用户友好的表示形式来简化这些细节的解释。可视化输出使开发人员和管理员可以更好地了解查询的执行,并更轻松地查明性能问题。

对于CQL跟踪查看器的第一个版本,我们选择了一个散点图可视化,该图显示了一个时间表上每个涉及节点的跟踪事件。这就是外观:

Image description

在即将到来的部分中,我们将详细探讨CQL跟踪,讨论使用Dash Python框架来构建我们的可视化工具CQL Trace Viewer的好处,并提供实施过程的概述。

什么是CQL跟踪?

CQL跟踪是Apache Cassandra的内置功能,Apache Cassandra是一个强大的工具,开发人员可以利用它来了解其内部处理其CQL查询的方式。它提供了查询执行路径的详细分解,可帮助您查看每个阶段花费了多少时间以及在此过程中涉及哪些节点。

打开跟踪就像在执行查询之前在CQL Shell中运行TRACING ON;命令一样简单。让我们在Astra创建的数据库的CQL Console中尝试一下:

cqlsh> TRACING ON;
cqlsh> SELECT * FROM testks.keyvalue limit 100;

查询结果之后,我们可以看到以下跟踪表:

 activity                                                                                                                  | timestamp                  | source       | source_elapsed | client
---------------------------------------------------------------------------------------------------------------------------+----------------------------+--------------+----------------+-----------------------------------------
                                                                                                        Execute CQL3 query | 2023-04-11 15:11:23.336000 | 172.25.225.5 |              0 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                           Parsing SELECT * FROM testks.keyvalue limit 100; [CoreThread-4] | 2023-04-11 15:11:23.336000 | 172.25.225.5 |            339 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                                                        Preparing statement [CoreThread-4] | 2023-04-11 15:11:23.336000 | 172.25.225.5 |            576 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                                               Computing ranges to query... [CoreThread-4] | 2023-04-11 15:11:23.337000 | 172.25.225.5 |            979 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
               Submitting range requests on 25 ranges with a concurrency of 1 (0.0 rows per range expected) [CoreThread-4] | 2023-04-11 15:11:23.337000 | 172.25.225.5 |           1166 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                                      Submitted 1 concurrent range requests [CoreThread-4] | 2023-04-11 15:11:23.339000 | 172.25.225.5 |           3270 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                         Sending READS.RANGE_READ message to /172.25.188.4, size=187 bytes [CoreThread-11] | 2023-04-11 15:11:23.339000 | 172.25.225.5 |           3424 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                          Sending READS.RANGE_READ message to /172.25.146.4, size=187 bytes [CoreThread-8] | 2023-04-11 15:11:23.339000 | 172.25.225.5 |           3424 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                       READS.RANGE_READ message received from /172.25.225.5 [CoreThread-2] | 2023-04-11 15:11:23.340000 | 172.25.188.4 |             60 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
            Executing seq scan across 3 sstables for (min(-9223372036854775808), min(-9223372036854775808)] [CoreThread-2] | 2023-04-11 15:11:23.340000 | 172.25.188.4 |            357 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                                    Read 100 live rows and 0 tombstone ones [CoreThread-2] | 2023-04-11 15:11:23.342000 | 172.25.188.4 |           2512 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                       Enqueuing READS.RANGE_READ response to /172.25.188.4 [CoreThread-2] | 2023-04-11 15:11:23.342000 | 172.25.188.4 |           2571 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                         Sending READS.RANGE_READ message to /172.25.225.5, size=3783 bytes [CoreThread-1] | 2023-04-11 15:11:23.342000 | 172.25.188.4 |           2758 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                       READS.RANGE_READ message received from /172.25.225.5 [CoreThread-6] | 2023-04-11 15:11:23.343000 | 172.25.146.4 |            295 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                       READS.RANGE_READ message received from /172.25.188.4 [CoreThread-4] | 2023-04-11 15:11:23.343000 | 172.25.225.5 |           7990 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                                     Processing response from /172.25.188.4 [CoreThread-4] | 2023-04-11 15:11:23.343000 | 172.25.225.5 |           8047 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
            Executing seq scan across 1 sstables for (min(-9223372036854775808), min(-9223372036854775808)] [CoreThread-6] | 2023-04-11 15:11:23.357000 | 172.25.146.4 |          15388 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                                    Read 100 live rows and 0 tombstone ones [CoreThread-6] | 2023-04-11 15:11:23.367000 | 172.25.146.4 |          26344 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                       Enqueuing READS.RANGE_READ response to /172.25.146.4 [CoreThread-6] | 2023-04-11 15:11:23.367000 | 172.25.146.4 |          26524 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                         Sending READS.RANGE_READ message to /172.25.225.5, size=3811 bytes [CoreThread-5] | 2023-04-11 15:11:23.367000 | 172.25.146.4 |          26889 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                       READS.RANGE_READ message received from /172.25.146.4 [CoreThread-9] | 2023-04-11 15:11:23.369000 | 172.25.225.5 |          33230 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                                     Processing response from /172.25.146.4 [CoreThread-4] | 2023-04-11 15:11:23.369000 | 172.25.225.5 |          33467 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
 Didn't get enough response rows; actual rows per range: 4.0; remaining rows: 0, new concurrent requests: 1 [CoreThread-4] | 2023-04-11 15:11:23.373000 | 172.25.225.5 |          37490 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0
                                                                                                          Request complete | 2023-04-11 15:11:23.373604 | 172.25.225.5 |          37604 | 90b5:897e:74c8:4fd7:8dda:e206:7a0f:32a0

在这里,活动列描述正在发生的事情,时间戳列提供了活动开始的时间,源列指示群集中的哪个节点执行了活动,source_elapsed显示了自查询开始以来微秒中的时间在源节点处执行。

虽然此详细的输出可能很有价值,但也可能是压倒性的。这是我们的CQL跟踪查看器进来的地方,通过以更平易近人和视觉格式展示该输出来有助于理解此输出。

在下一部分中,我们将探讨为什么我们选择了此任务的DASH框架。

为什么要冲刺?

在构建Web应用程序以进行数据可视化方面时,选择正确的框架可以使一切与众不同。对于CQL跟踪查看器,我们选择了Dash,这是用于构建分析Web应用程序的Python框架。

dash建立在烧瓶,plotly.js和react.js的顶部,利用这些库的功率和灵活性。这意味着它受益于烧瓶的知名和简单的后端功能,plotly.js的交互式和高质量的数据可视化以及基于反应性的基于组件的UI构建React.js。这些技术的组合为DASH提供了构建数据密集型应用程序的坚实基础。

此外,DASH专门旨在创建分析Web应用程序,使其成为我们可视化工具的理想选择。它的交互式绘图。JS图表和图表使用户能够以比原始跟踪输出提供的更有意义地理解和与数据交互。

此外,DASH允许我们仅在Python中创建应用程序。这可以大大简化开发过程,尤其是对于那些在Python中精通的人。不需要JavaScript或HTML知识,尽管可以根据需要使用这些知识。

这种以Python为中心的方法意味着该应用可以轻松地与科学的Python生态系统集成。可以在应用程序中无缝地使用诸如用于数据操作的熊猫和数值计算的numpy的库,从而进一步扩展其功能。

此外,DASH应用程序本质上是基于Web的,并且可以在服务器上部署并通过Internet共享。这使得任何需要了解其CQL查询的内部工作的人,任何地方和任何时候都可以访问CQL跟踪查看器。

在下一部分中,我们将深入研究实施详细信息。

执行

设置

在我们开始实施应用程序之前,我们需要设置开发环境。假设以下requirements.txt文件:

dash==2.9.3
pandas==2.0.1

我们可以创建虚拟环境并使用以下命令安装依赖项:

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

现在让我们写一些代码!

应用骨骼

在高级别上,实施DASH应用程序涉及创建仪表板实例,定义应用程序的布局并启动服务器。让我们在名为main.py的文件中创建一个最小的dash应用程序以查看此操作。

from dash import Dash, html

dash_app = Dash(__name__, title=CQL Trace Viewer)
app = dash_app.server

dash_app.layout = html.Div([])

if __name__ == "__main__":
    dash_app.run_server(debug=True)

dash_app是与我们的dash应用程序进行交互的主要变量。 dash_app.server引用了嵌入式烧瓶应用程序,我们将其展示在看似未使用的变量app中。当我们在Google Cloud Platform(GCP)上部署应用程序时,这将派上用场。

dash_app.layout定义了应用程序的布局。在这里,我们使用html.Div组件为我们的应用程序创建一个容器。空列表[]表示该容器没有孩子。

,我们将在此容器中添加组件。

有条件的if __name__ == "__main__":允许我们从命令行执行脚本时在调试模式下启动服务器。调试模式可以启用热线充电,这意味着服务器将在检测到源代码的更改时自动更新,并且在UI中显示更详细的错误消息。

现在,我们已经拥有仪表板应用的骨架,下一步是用组件填充布局以与CQL痕迹交互并显示可视化。

添加UI组件

我们需要添加的第一个UI组件是一个输入字段,该字段允许用户粘贴其CQL跟踪。让我们为此创建一个文本区域。为了使起始屏幕更具吸引力,我们将将我们的示例跟踪存储在名为trace.txt的文件中,并在最初加载页面时将其读取到文本区域中。

from dash import Dash, dcc, html

with open('trace.txt', 'r') as trace_file:
    trace_input = dcc.Textarea(
        id='cql-trace',
        value=trace_file.read(),
        style={'width': '100%', 'height': 300},
    )

dash_app.layout = html.Div([
    trace_input
])

使用python main.py启动服务器并在浏览器中导航到http://127.0.0.1:8050/时,我们可以看到结果:

CQL trace input

接下来,让我们添加一个散点图组件,以后将用于绘制解析的跟踪。

trace_scatter = dcc.Graph(
    id='trace_scatter',
)

dash_app.layout = html.Div([
    trace_input,
    trace_scatter
])

这就是空散点图在作用中的外观:

Empty scatter plot

现在我们已经准备了基本的UI组件,让我们添加一些交互性。

添加回调

在破折号中,我们可以通过回调处理用户输入。在我们的情况下,我们需要定义一个将CQL跟踪作为输入的回调函数,解析输出并返回散点图的绘图图。每当用户更改输入字段的值时,都会调用此函数。

import traceback
import pandas as pd
from dash.dependencies import Input, Output
from io import StringIO

@dash_app.callback(
    Output(trace_scatter, 'figure'),
    Input(trace_input, 'value'),
)
def parse_trace(raw_trace):
    try:
        if raw_trace:
            df = pd.read_csv(StringIO(raw_trace), sep='\s*\|\s*', header=0, skiprows=[1], engine='python')
            scatter_fig = build_scatter_fig(df)
            return scatter_fig
        else:
            return {}
    except Exception:
        traceback.print_exc()

@dash_app.callback装饰器定义了回调函数。 OutputInput对象指定组件及其相应的属性,这些属性用作回调的输入和输出。在我们的情况下,trace_input是输入组件,而trace_scatter是输出组件。 trace_scatter组件的figure属性用作输出值。

回调函数将原始跟踪作为输入,并将其解析为PANDAS DataFrame。我们可以使用read_csv功能来解析表。作为分离器,我们使用'\s*\|\s*',该'\s*\|\s*'基于|拆分值,并同时修剪所有空格。表标头处于第一行,我们需要跳过第二行,因为它不包含任何数据。

build_scatter_fig函数是一个辅助函数,可为散点图创建绘图图。我们将在下一个小节中实现此功能。

建立情节图

要构建我们的散点图,我们必须执行一些数据转换。具体来说,我们需要为每个活动计算一个可以在X轴上绘制的活动的绝对时间戳,并建立一个值以显示Y轴上的值。

为了展示随着时间的推移活动,我们可以在X轴上绘制timestamp列。但是,timestamp仅具有毫秒的精度,并且没有在每个活动中更新。另一方面,source_elapsed列是一个单调的时钟,其精确度精度。但是,由于source_elapsed从其跟踪活动开始时代表了每个节点中经过的时间。

为了获得每个活动的绝对精确时间戳,我们需要为每个节点计算一个根时间戳。我们可以通过从每个节点的第一个活动的timestamp中减去source_elapsed来做到这一点。虽然这不会使时钟之间的时钟在考虑之间,但这是我们能做的最好的。

为了构建Y轴值,我们可以简单地将sourceactivity列加入。这将为我们为痕迹中的每个活动提供独特的价值。最后,我们想通过其来源进行分组并相应地进行颜色。这是通过收集字典trace_activities中每个源的活动来实现的,然后将输出缩放到单个列表中,我们可以将其转换回数据框并用作散点图的输入。

这里是代码:

import plotly.express as px
import datetime

def build_scatter_fig(df):
    source_root_timestamps = {}
    trace_activities = {}

    for index, row in df.iterrows():
        source = row['source']
        row['source_activity'] = "{}: {}".format(row['source'], row['activity'])
        activity_timestamp = datetime.datetime.strptime(row['timestamp'], '%Y-%m-%d %H:%M:%S.%f')
        elapsed_micros = 0

        try:
            elapsed_micros = int(row['source_elapsed'])
        except ValueError:
            pass
            # do nothing, this is probably just a `--` value

        if source not in source_root_timestamps:
            source_root_timestamps[source] = activity_timestamp - datetime.timedelta(microseconds=elapsed_micros)
        if source not in trace_activities:
            trace_activities[source] = []

        activity_elapsed_timestamp = source_root_timestamps[source] + datetime.timedelta(microseconds=elapsed_micros)

        trace_activity = {'activity': row['activity'], 'timestamp': activity_timestamp, 'source_activity': row['source_activity'],
                          'start': activity_elapsed_timestamp, 'source': source}
        trace_activities[source].append(trace_activity)

    flattened_activities = [item for sublist in list(trace_activities.values()) for item in sublist]

    fig_df = pd.DataFrame.from_records(flattened_activities)
    fig = px.scatter(data_frame=fig_df, x='start', y='source_activity', color='source')
    fig.update_yaxes(autorange="reversed")
    return fig

现在让我们看一下!

Scatter plot with activities

请注意,此基本版本不包括为节点之间的消息生成箭头的代码,您可以在屏幕截图中看到这些箭头作为简介的一部分。如果您对此功能感兴趣,
请看一下complete source code

部署

要部署该应用程序,我们可以使用专门为服务器端渲染网站构建的Google Cloud App Engine。在Google Cloud Console中创建一个新项目后,我们必须配置cql-trace-viewer应用程序。

接下来,我们必须创建一个名为app.yaml的文件。在我们的情况下,足以指定Python运行时,App Engine将根据现有文件的惯例来弄清楚其余的名为main.py的约定,该文件定义了指向烧瓶应用程序的app变量。

# app.yaml
runtime: python39

然后,我们可以使用Google Cloud SDK部署应用程序:

gcloud app deploy

它在https://cql-trace-viewer.ue.r.appspot.com/

上活着

讨论

在用仪表板上解析痕迹是一个有趣的练习时,我们想解决此过程中出现的几个重要讨论点。

首先,已经存在许多跟踪工具,其中许多具有功能强大的功能和精心设计的用户界面。 Cassandra可能会提供一种潜在的改进,以提供以标准化的机器可读格式输出轨迹的选项。这种格式可以很容易地导入这些现有工具中的任何一个,简化了过程并扩展了可视化和分析选项的范围。

第二个讨论点围绕跟踪输出的可变性,尤其是关于活动的变异性,这在很大程度上取决于数据库的实现和配置。值得注意的是,跟踪输出中列出的活动在不同版本或Cassandra的分布之间可能有很大差异。

例如,dataStax Enterprise(DSE)6.8和Apache Cassandra 4.0之间的跟踪输出有很大差异。这些变化表明,CQL跟踪查看器的解析器可能需要根据所使用的Cassandra的特定版本或分布进行调整或更新。

概括

在整个博客文章中,我们都经历了CQL Trace Viewer的概念,设计和开发,这是一种使用仪表板框架构建的Web应用程序,可视化追踪CQL查询的输出。

我们首先潜入CQL跟踪是,探索它如何提供有关CQL查询内部处理的见解。我们强调了可视化的需求,强调了将原始,复杂的跟踪输出转换为直观且用户友好的格式的价值。

然后,将DASH作为开发框架的选择是合理的。我们详细介绍了Dash的内置功能,其根源如何在烧瓶,Plotly.js和React.js中,以及它与科学Python生态系统的兼容性使其成为手头任务的理想候选者。

我们提供了实现的高级概述,演示了如何使用文本输入,绘制散点图图和解析输入并生成图形的回调函数创建破折号应用程序。然后,我们展示了如何将应用程序部署到Google Cloud App Engine。

最后,我们讨论了未来改进的潜力,包括跟踪输出的标准化以及将解析器调整为Cassandra的不同版本和分布的需求。

参考