๋ฐ์ดํ„ฐ ์—”์ง€๋‹ˆ์–ด๋ง

๋ฐ์ดํ„ฐ ๋ถ„์„ ํ™˜๊ฒฝ ๊ตฌ์ถ• - 09. Streamlit ์†Œ๊ฐœ ๋ฐ ์„ค์น˜, ์‹ค์ œ ํ˜„์—… ์‚ฌ์šฉ๊ธฐ

Tempo 2025. 4. 5. 08:00
์ด์ „์— ์†Œ๊ฐœํ–ˆ๋˜ Clickhouse์—์„œ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ถ„์„์„ ์œ„ํ•ด
๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” Streamlit ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ ํ˜„์—…์—์„œ Streamlit์œผ๋กœ ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ–ˆ๋˜ ํ›„๊ธฐ๋„ ์งง๊ฒŒ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

 

 

 

๋ฐ์ดํ„ฐ ๋ถ„์„ ํ™˜๊ฒฝ ๊ตฌ์ถ• - 05. Clickhouse ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ถ„์„ ํ…Œ์ด๋ธ” ์ƒ์„ฑํ•˜๊ธฐ

๐Ÿš€ ์ด ๊ธ€์—์„œ ๋‹ค๋ฃฐ ๋‚ด์šฉ1๏ธโƒฃ Kafka์™€ ์—ฐ๊ฒฐ๋œ ClickHouse ํ…Œ์ด๋ธ” ์ƒ์„ฑ (ํ™˜๊ฒฝ์„ค์ • ํฌํ•จ)2๏ธโƒฃ ์‹ค์Šต์šฉ ๋ฐ์ดํ„ฐ์…‹ kafka produce3๏ธโƒฃ Kafka ํ…Œ์ด๋ธ”์—์„œ ๋ฐ์ดํ„ฐ ์…‹ ํ™•์ธ ๋ฐฉ๋ฒ•1. Kafka์™€ ์—ฐ๊ฒฐ๋œ ClickHouse ํ…Œ์ด๋ธ” ์ƒ

jongwho.tistory.com

 

 

๐Ÿ› ๏ธ Streamlit ์„ค์น˜ ๋ฐ ์ฐจํŠธ ์ƒ์„ฑ

๋ฐ˜์‘ํ˜•

 

์•„๋ž˜ ๋ช…๋ น์–ด๋ฉด streamlit ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

pip install streamlit

 

๊ทธ ์ดํ›„์— streamlit ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ๋ช…๋ น์–ด๋กœ tutorial ํŽ˜์ด์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

streamlit hello

 

 

streamlit์—์„œ ์ œ๊ฐ€ ์ฃผ๋กœ ์‚ฌ์šฉํ–ˆ๋˜ ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” altair_chart ์˜€์Šต๋‹ˆ๋‹ค(https://docs.streamlit.io/develop/api-reference/charts/st.altair_chart). ์ด ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ฐจํŠธ ๋‚ด์—์„œ ์ถ”๊ฐ€ ์•ก์…˜(์˜ˆ: ๋ฒ„ํŠผ ํด๋ฆญ ๋“ฑ)์ด ๊ฐ€๋Šฅํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ธ๋ฐ์š”. ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งŒ๋“  ์•ฑ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

์—ฐ๋„๋ณ„ ๋งค์ถœ ์ฐจํŠธ
์ฐจํŠธ ํด๋ฆญ ์‹œ ์›”๋ณ„ ๋งค์ถœ ์ถœ๋ ฅ

์‹ค์ œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋™์ž‘ ์˜ˆ์‹œ

 

 

๐Ÿ› ๏ธ Streamlit ์ฐจํŠธ ์ƒ์„ฑ ์ฝ”๋“œ ์˜ˆ์‹œ

๋”๋ณด๊ธฐ
import streamlit as st
import pandas as pd
import numpy as np
import altair as alt

# ์ฐจํŠธ ์„ ํƒ ํ‚ค ์ดˆ๊ธฐํ™”
chart_key = "alt_chart"

if "select_state" not in st.session_state:
    st.session_state["select_state"] = False

# ์—ฐ๋„๋ณ„ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
years = list(range(2020, 2025))
sales_data = [100, 200, 150, 300, 250]  # ์˜ˆ์‹œ ๋ฐ์ดํ„ฐ


annual_data = pd.DataFrame({"year": years, "annual_sales": sales_data})


# State ๋ณ€๊ฒฝ ํ•จ์ˆ˜
def change_state():
    st.session_state["select_state"] = True


# ์›”๋ณ„ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ํ•จ์ˆ˜
def generate_monthly_data(selected_year):
    np.random.seed(selected_year)  # ์—ฐ๋„๋ณ„ ๊ณ ์œ  ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
    return pd.DataFrame(
        {
            "year": selected_year,
            "month": range(1, 13),
            "monthly_sales": np.random.randint(50, 300, 12),
        }
    )


# Altair ์„ ํƒ ์„ค์ •
selection = alt.selection_point(
    fields=["year"],  # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„๋“œ ๊ธฐ์ค€ ์„ ํƒ
    on="click",  # ํด๋ฆญ ์ด๋ฒคํŠธ ์‚ฌ์šฉ
    empty=False,  # ํ•ญ์ƒ ํ•˜๋‚˜์˜ ์„ ํƒ ์œ ์ง€
)

# ๋ฐ” ์ฐจํŠธ ์ƒ์„ฑ
chart = (
    alt.Chart(annual_data)
    .mark_bar()
    .encode(
        x=alt.X("year:O", title="์—ฐ๋„"),  # ์ˆœ์„œํ˜•(O) ๋ฐ์ดํ„ฐ๋กœ ์ฒ˜๋ฆฌ
        y=alt.Y("annual_sales:Q", title="์—ฐ๊ฐ„ ๋งค์ถœ"),
        color=alt.condition(selection, alt.value("#4A90E2"), alt.value("#D3D3D3")),
    )
    .interactive()
    .add_params(selection)
)  # ์„ ํƒ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€

# ์ฐจํŠธ ํ‘œ์‹œ (ํ‚ค ์ง€์ • ํ•„์ˆ˜)
chart_event = st.altair_chart(
    chart,
    key=chart_key,
    on_select=change_state,  # ์„ ํƒ์‹œ ์•ฑ ๋ฆฌ๋กœ๋“œ
)

if st.session_state["select_state"]:
    # ์„ ํƒ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
    if chart_event and chart_event.get("selection", {}).get("param_1"):
        selected_year = chart_event["selection"]["param_1"][0]["year"]
        monthly_data = generate_monthly_data(selected_year)
        st.dataframe(monthly_data, hide_index=True)
else:
    st.write("์ฐจํŠธ์˜ ๋ฐ”๋ฅผ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.")

์œ„์™€ ๊ฐ™์ด streamlit์„ ์‚ฌ์šฉํ•˜๋ฉด ๋น ๋ฅด๊ฒŒ ์ฐจํŠธ ๋ฐ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“‹ ์‚ฌ๋‚ด ์„œ๋น„์Šค์—์„œ Streamlit์„ ์‚ฌ์šฉํ•œ ์งง์€ ํ›„๊ธฐ

POC์™€ ๋น ๋ฅธ ์‹œ๊ฐ„ ๋‚ด์— ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์„ฑ๊ณผ ๋ฐ์ดํ„ฐ ๋ถ„์„ ๋ ˆํฌํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ ๊ฐ€์žฅ ์ตœ๊ณ ์˜ ์•ฑ
๐Ÿ˜‚ ํ•˜์ง€๋งŒ Streamlit์˜ ํ•œ๊ณ„๋Š” ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.

 

โœ… ์žฅ์ 

์‚ฌ๋‚ด์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฐ์ดํ„ฐ ๋ ˆํฌํŠธ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹œ๊ฐ„์ด ์ •๋ง ์ด‰๋ฐ•ํ–ˆ๊ณ  ๋น ๋ฅด๊ฒŒ ์„œ๋น„์Šค๋ฅผ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ, ๊ทธ๋ฆฌ๊ณ   ์‚ฌ๋‚ด์—์„œ Snowflake๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๊ธฐ์— ๋ถ„์„๊ฐ€์™€ ํ˜‘์—…ํ•˜๊ธฐ ์ข‹์€ ํˆด์„ ์„ ํƒํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

Snowflake๋Š” ์ž์ฒด์ ์œผ๋กœ Streamlit ์•ฑ์„ ๋‚ด์žฅํ•˜๊ณ  ์žˆ์–ด ๋ถ„์„๊ฐ€๊ฐ€ ์ ‘๊ทผํ•˜๊ธฐ ์‰ฌ์› ๊ณ  ๋˜ํ•œ Python ๊ธฐ๋ฐ˜์˜ ํ”„๋ ˆ์ž„์›Œํฌ์˜€๊ธฐ์— ํ•™์Šต ๊ณก์„ ์ด ๊ฐ€ํŒŒ๋ฅด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. 

 

โš ๏ธ ๋‹จ์ 

Streamlit ์ž์ฒด์ ์œผ๋กœ ์ธ์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด์— AWS ์ธํ”„๋ผ์— Streamlit ์„œ๋ฒ„๋ฅผ ์˜ฌ๋ฆฌ๋ฉด์„œ Cognito์™€ ALB ์กฐํ•ฉ์œผ๋กœ ์ด๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ Streamlit ์•ฑ ์ž์ฒด๋Š” Websocket ๋ฐฉ์‹์œผ๋กœ ์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ณ„์† ์‹คํ–‰๋˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ์ฆ‰ ์›น ๋ธŒ๋ผ์šฐ์ €์— Streamlit ์•ฑ์„ ์ง€์† ๋žœ๋”๋งํ•˜์—ฌ ์ฐจํŠธ ๋ฐ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์ธ๋ฐ์š”, ํ•ด๋‹น ํŠน์ง•์ด ๋ฐ์ดํ„ฐ ๋ถ„์„ ๊ตฌ์กฐ์—์„œ๋Š” ๋‹จ์ ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค(100๋งŒ๊ฑด ์ด์ƒ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•  ๋•Œ).

 

์ข…ํ•ฉ์ ์œผ๋กœ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž(์‚ฌ๋‚ด์—์„œ๋Š” ์ „๋ฌธ์ ์ธ ๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€๊ฐ€ ์•„๋‹Œ) ๊ด€์ ์—์„œ ๋ฐ์ดํ„ฐ ๋ถ„์„์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๊ฑด ์ˆ˜๊ฐ€ ๋งŽ์ง€ ์•Š๋‹ค๋ฉด Streamlit์„ ์„ ํƒํ•˜์—ฌ ๋น ๋ฅด๊ฒŒ MVP๋ฅผ ๋งŒ๋“ค์–ด์„œ ํŒ€์›์—๊ฒŒ ํ”ผ๋“œ๋ฐฑ ๋ฐ›๊ณ  ๊ทธ๊ฑธ ๋‹ค์‹œ ๋ฐ˜์˜ํ•˜๋Š” ๋ฃจํ”„๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ๋Š” ์ข‹์€ ์•ฑ์œผ๋กœ ์ƒ๊ฐ๋ฉ๋‹ˆ๋‹ค. 

 

 

๐Ÿ“š ๋‹ค์Œ ๊ธ€์—์„œ๋Š” Streamlit์— clickhouse๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ์‹ค์‹œ๊ฐ„ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์•ฑ์„ ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•