본문 바로가기
SingleStoreDB/엔지니어링

DashApp 따라하기 with SingleStore

by 에이플랫폼 [Team SingleStore Korea] 2025. 2. 27.

 

안녕하세요 에이플랫폼 입니다.

이번 시간에는 SingleStore Helios Free Shared Tier와 SingleStore Notebook을 활용해서 간단한 DashApp을 만들어 보겠습니다.

저번 게시물인 Semantic Search 따라하기 with SingleStore 를 보셨다면

좀 더 수월하게 해보실 수 있을 것 같습니다.


 
 

실습

테이블 만들기

%%sql
DROP TABLE IF EXISTS orders;

CREATE TABLE IF NOT EXISTS orders (
    order_id INT PRIMARY KEY,
    order_date DATE,
    amount DECIMAL(10, 2),
    name VARCHAR(50)
);
 

데이터 삽입

아래는 예시의 데이터 입니다.

원하시는 데이터로 설정 하셔도 됩니다.

%%sql
INSERT INTO orders (order_id, order_date, amount, name) VALUES
(1, '2024-01-01', 150.00, "Laptop"),
(2, '2024-01-01', 20.00, "Speaker"),
(3, '2024-01-01', 60.00, "Monitor"),
(4, '2024-01-02', 300.00, "Laptop"),
(5, '2024-01-02', 100.00, "Laptop"),
(6, '2024-01-02', 100.00, "Laptop"),
(7, '2024-01-02', 25.00, "Speaker"),
(8, '2024-01-02', 20.00, "Speaker"),
(9, '2024-01-02', 75.00, "Monitor"),
(10, '2024-01-03', 350.00, "Laptop"),
(11, '2024-01-03', 150.00, "Laptop"),
(12, '2024-01-03', 25.00, "Speaker"),
(13, '2024-01-03', 35.00, "Speaker"),
(14, '2024-01-03', 55.00, "Monitor"),
(15, '2024-01-04', 120.00, "Laptop"),
(16, '2024-01-04', 120.00, "Laptop"),
(17, '2024-01-04', 30.00, "Speaker"),
(18, '2024-01-04', 40.00, "Speaker"),
(19, '2024-01-04', 25.00, "Speaker"),
(20, '2024-01-04', 50.00, "Monitor"),
(21, '2024-01-04', 70.00, "Monitor");
 

Connection Pool 만들기

SQLAlchemy를 활용하여 선택한 작업 영역에 대한 데이터베이스 연결 풀을 생성하고, 이를 사용해 쿼리를 실행하는 메서드를 정의합니다.

from sqlalchemy import create_engine, text
import requests

ca_cert_url = "https://portal.singlestore.com/static/ca/singlestore_bundle.pem"
ca_cert_path = "/tmp/singlestore_bundle.pem"

response = requests.get(ca_cert_url)
with open(ca_cert_path, "wb") as f:
    f.write(response.content)

sql_connection_string = connection_url.replace("singlestoredb", "mysql+pymysql")
engine = create_engine(
    f"{sql_connection_string}?ssl_ca={ca_cert_path}",
    pool_size=10,           # Maximum number of connections in the pool is 10
    max_overflow=5,         # Allow up to 5 additional connections (temporary overflow)
    pool_timeout=30         # Wait up to 30 seconds for a connection from the pool
)

def execute_query(query: str):
    with engine.connect() as connection:
        return pd.read_sql_query(query, connection)
 

💡이 단계를 실행 하실 때 Add Firewall 이 라는 배너가 상단에 나올 수 있습니다.

Add to Firewall 버튼을 눌러주시고

위와 같이 나오면 그냥 Save 만 눌러주시면 됩니다.

그럼 FQDN allowlist에 성공적으로 등록이 되었습니다.


꺾은선 차트 만들기

plotly를 사용한 꺾은선 차트 생성

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go

def generate_line_chart(type):
    if type == 'Count':
        df = execute_query("SELECT order_date, name, COUNT(*) as sales from orders group by order_date, name order by order_date")
    elif type == 'Total Value':
        df = execute_query("SELECT order_date, name, SUM(amount) as sales from orders group by order_date, name order by order_date")
    fig = px.line(df, x='order_date', y='sales', color='name', markers=True,
              labels={'sales': 'Sales', 'date': 'Order Date'},
              title='Sales Over Time')
    fig.update_layout(
        font_family="Roboto",
        font_color="gray",
        title_font_family="Roboto",
        title_font_color="Black",
        legend_title_font_color="gray"
    )
    return fig

line_chart = generate_line_chart("Count")
line_chart.show()
 

실행하시면 아래와 같은 차트가 생성 됩니다.

Pie 차트 만들기

파이 차트를 만들어 일일 판매량에 대한 각 항목 유형의 기여도를 확인할 수 있습니다

def generate_pie_chart(date):
    df = execute_query(f"SELECT name, SUM(amount) as sales from orders where order_date = '{date}' group by name")
    fig = px.pie(df,
            names='name',
            values='sales',
            hover_data=['sales'],
            labels={'sales': 'Total Sales', 'name': 'Type'},
            title='Total Cost by Item Type')
    return fig

pie_chart = generate_pie_chart("2024-01-01")
pie_chart.show()
 

실행하시면 아래와 같은 차트가 생성 됩니다.


Dash 앱의 레이아웃 및 콜백 정의

이제 Dash 앱의 간단한 레이아웃콜백을 정의하겠습니다.

레이아웃은 대시보드의 UI 요소를 정의하고 콜백은 UI 요소와 이전에 정의한 sqlalchemy 쿼리 엔진 간의 상호 작용을 정의합니다

레이아웃과 콜백의 더욱 자세한 설명은 링크를 참조 부탁드립니다.

레이아웃: https://dash.plotly.com/layout

 

Part 1. Layout | Dash for Python Documentation | Plotly

# Run this app with `python app.py` and # visit http://127.0.0.1:8050/ in your web browser. from dash import Dash, html, dcc import plotly.express as px import pandas as pd app = Dash() # assume you have a "long-form" data frame # see https://plotly.com/py

dash.plotly.com

 
from singlestoredb import apps
from dash import Dash, callback, html, dcc, Input, Output

def get_order_dates():
    df = execute_query("select distinct order_date from orders order by order_date")
    return df['order_date']

initial_dates = get_order_dates()

# Create the Dash App
app = Dash("Sales Report", requests_pathname_prefix=os.environ['SINGLESTOREDB_APP_BASE_PATH'])

# Define the Layout of the Dash App. We will be defining
# - A line chart depicting a time series of sales
# - A dropdown that shows 'Count'/'Total Value' options, which is used to render different line charts
# - An interval counter to keep pinging the Dash App server to get the latest dashboard
# - A pie chart depicting the total proportion of sales for a day by item type
# - A drop down showing the different dates, which is used to render different pie charts

app.layout = html.Div([
    html.P('Sales Dashboard', style={'textAlign':'center', 'marginTop': 50, 'color': '#8800cc', 'fontSize': '32px', 'fontFamily':'Roboto'} ),
    html.Div([
        dcc.Interval(
            id='interval-component',
            interval=2 * 5000,  # Update every second
            n_intervals=0  # Start at 0
        ),
        html.Div(
            dcc.Dropdown(['Count', 'Total Value'], 'Count', id='category-dropdown', style={'width': '200px', 'marginRight':'32px' }),
            style={'display': 'flex', 'justifyContent': 'flex-end'}
        ),
        dcc.Loading(
            id="loading-spinner",
            type="circle",  # Type of spinner: 'circle', 'dot', 'cube', etc.
            children=[
                dcc.Graph(figure = line_chart, id='line-chart'),
            ]
        ),
        html.Div(
            dcc.Dropdown(initial_dates, initial_dates[0], id='date-dropdown', style={'width': '200px', 'marginRight':'32px' }),
            style={'display': 'flex', 'justifyContent': 'flex-end'}
        ),
        dcc.Graph(figure = pie_chart, id='pie-chart'),
    ], style={'margin': '32px'})
])

# Define a callback to update the bar chart based on the category dropdown selection
@app.callback(
    Output("line-chart", "figure"),
    Input("category-dropdown", "value"))  # Use the stored value
def update_bar_chart(type):
    return generate_line_chart(type)

# Define a callback to update the pie chart based on the date dropdown selection
@app.callback(
    Output("pie-chart", "figure"),
    Input("date-dropdown", "value"),
    Input('interval-component', 'n_intervals'))
def update_pie_chart(date, n_intervals):
    return generate_pie_chart(date)

# Define a callback to update the date dropdown periodically
@app.callback(
    Output('date-dropdown', 'options'),
    Input('interval-component', 'n_intervals'))
def update_date_dropdown(n_intervals):
    return get_order_dates()
 

Dash App 서버 시작

Dash App 링크가 표시됩니다.

테이블에 더 많은 데이터를 삽입하고 대시보드의 변경 사항을 실시간으로 볼 수 있습니다.

connectionInfo = await apps.run_dashboard_app(app)
 

생성된 링크를 클릭 해보시면

예시와 같은 결과물의 Dash App 이 실행됩니다.


 

오늘은 간단히 여기까지 소개해 드리겠습니다.

데이터도 추가해 보셔도 좋고 통계를 볼 수 있는 다른 테이블을 생성 하고 보셔도 좋을 것 같습니다.

앞으로도 종종 SingleStore를 활용하여 따라할 수 있는 실습 내용으로 찾아 뵙겠습니다.