[streamlit] Streamlit 심화 개념

https://docs.streamlit.io/get-started/fundamentals/advanced-concepts

 

Streamlit Docs

Join the community Streamlit is more than just a way to make data apps, it's also a community of creators that share their apps and ideas and help each other make their work better. Please come join us on the community forum. We love to hear your questions

docs.streamlit.io

이제 스트림릿 앱이 어떻게 실행되고 데이터를 처리하는지 알았으니 이제 효율성에 대해 이야기해 보겠습니다. 캐싱을 사용하면 함수의 출력을 저장하여 재실행할 때 건너뛸 수 있습니다. 세션 상태를 사용하면 재실행 사이에 보존되는 각 사용자에 대한 정보를 저장할 수 있습니다. 이를 통해 불필요한 재계산을 피할 수 있을 뿐만 아니라 동적 페이지를 생성하고 진행 중인 프로세스를 처리할 수 있습니다.

1. Caching

캐싱을 사용하면 웹에서 데이터를 로드하거나 대용량 데이터 세트를 조작하거나 고비용 계산을 수행할 때에도 앱의 성능을 유지할 수 있습니다. 캐싱의 기본 개념은 고비용 함수 호출의 결과를 저장하고 동일한 입력이 다시 발생할 때 캐싱된 결과를 반환하는 것입니다. 이렇게 하면 동일한 입력값을 가진 함수가 반복적으로 실행되는 것을 방지할 수 있습니다. Streamlit에서 함수를 캐시하려면 캐싱 데코레이터를 적용해야 합니다. 두 가지 선택지가 있습니다:

  • 데이터를 반환하는 계산을 캐시하는 데 권장되는 방법은 st.cache_data입니다. 직렬화 가능한 데이터 객체(예: str, int, float, DataFrame, dict, list)를 반환하는 함수를 사용할 때는 st.cache_data를 사용하세요. 이 함수는 각 함수 호출 시 데이터의 새 복사본을 생성하여 돌연변이와 경쟁 조건으로부터 데이터를 안전하게 보호합니다. 대부분의 경우 st.cache_data의 동작이 원하는 대로 작동하므로 확실하지 않은 경우 st.cache_data로 시작하여 작동하는지 확인해 보세요!
  • ML 모델이나 데이터베이스 연결과 같은 전역 리소스를 캐시하는 데 권장되는 방법은 st.cache_resource입니다. 함수가 여러 번 로드하고 싶지 않은 직렬화할 수 없는 객체를 반환할 때 st.cache_resource를 사용하세요. 이 함수는 복사나 복제 없이 모든 재실행 및 세션에서 공유되는 캐시된 객체 자체를 반환합니다. st.cache_resource를 사용하여 캐시된 객체를 변경하면 해당 변경 사항은 모든 재실행 및 세션에 걸쳐 존재합니다.
@st.cache_data
def long_running_function(param1, param2):
    return …

위의 예시에서 long_running_function은 @st.cache_data로 장식되어 있습니다. 결과적으로 Streamlit은 다음과 같이 기록합니다:

  • 함수 이름("long_running_function")입니다.
  • 입력값(param1, param2)입니다.
  • 함수 내의 코드입니다.

long_running_function 내에서 코드를 실행하기 전에 Streamlit은 캐시에 이전에 저장된 결과가 있는지 확인합니다. 주어진 함수와 입력 값에 대해 캐시된 결과를 찾으면 해당 캐시된 결과를 반환하고 함수의 코드를 다시 실행하지 않습니다. 그렇지 않으면 Streamlit은 함수를 실행하고 결과를 캐시에 저장한 후 스크립트 실행을 진행합니다. 개발 중에 함수 코드가 변경되면 캐시가 자동으로 업데이트되어 최신 변경 사항이 캐시에 반영됩니다.

Streamlit 캐싱 데코레이터, 구성 매개변수 및 제한 사항에 대한 자세한 내용은 캐싱을 참조하세요.

2. Session State

세션 상태는 스크립트 재실행 사이에 보존되는 정보를 저장할 수 있는 사전과 같은 인터페이스를 제공합니다. 키 또는 속성 표기법과 함께 st.session_state를 사용하여 값을 저장하고 불러올 수 있습니다. 예를 들어, st.session_state["my_key"] 또는 st.session_state.my_key. 위젯은 자체적으로 상태 저장성을 처리하므로 항상 세션 상태를 사용할 필요는 없다는 점을 기억하세요!

2.1. What is a session?

세션은 앱을 보는 단일 인스턴스입니다. 브라우저의 서로 다른 두 탭에서 앱을 보는 경우 각 탭에는 고유한 세션이 있습니다. 따라서 앱의 각 뷰어는 특정 뷰에 연결된 세션 상태를 갖게 됩니다. 스트림릿은 사용자가 앱과 상호 작용할 때 이 세션을 유지합니다. 사용자가 브라우저 페이지를 새로고침하거나 앱의 URL을 다시 로드하면 세션 상태가 재설정되고 새 세션으로 다시 시작됩니다.

2.2. Examples of using Session State

다음은 페이지가 실행된 횟수를 계산하는 간단한 앱입니다. 버튼을 클릭할 때마다 스크립트가 다시 실행됩니다.

import streamlit as st

if "counter" not in st.session_state:
    st.session_state.counter = 0

st.session_state.counter += 1

st.header(f"This page has run {st.session_state.counter} times.")
st.button("Run it again")
  • 첫 실행: 각 사용자에 대해 앱이 처음 실행될 때 세션 상태는 비어 있습니다. 따라서 키-값 쌍(key-value pair)이 생성됩니다("카운터":0). 스크립트가 계속되면 카운터가 즉시 증가("카운터":1)하고 결과가 표시됩니다: "이 페이지는 1번 실행되었습니다." 페이지가 완전히 렌더링되면 스크립트가 완료된 것이고 Streamlit 서버는 사용자가 무언가를 할 때까지 기다립니다. 사용자가 버튼을 클릭하면 재실행이 시작됩니다.
  • 두 번째 실행: "카운터"는 이미 세션 상태의 키이므로 다시 초기화되지 않습니다. 스크립트가 계속 진행됨에 따라 카운터가 증가("카운터":2)하고 결과가 표시됩니다: "이 페이지는 2번 실행되었습니다."

세션 상태가 유용한 몇 가지 일반적인 시나리오가 있습니다. 위에서 설명한 것처럼 세션 상태는 한 번의 재실행에서 다음 재실행으로 이어지는 점진적인 프로세스를 구축하려는 경우에 사용됩니다. 세션 상태는 캐싱과 유사하게 재계산을 방지하는 데에도 사용할 수 있습니다. 하지만 중요한 차이점이 있습니다:

  • 캐싱은 저장된 값을 특정 함수 및 입력에 연결합니다. 캐시된 값은 모든 세션에서 모든 사용자가 액세스할 수 있습니다.
  • 세션 상태는 저장된 값을 키(문자열)에 연결합니다. 세션 상태의 값은 저장된 단일 세션에서만 사용할 수 있습니다.

앱에 난수 생성이 있는 경우 세션 상태를 사용할 가능성이 높습니다. 다음은 각 세션이 시작될 때 데이터가 무작위로 생성되는 예시입니다. 이 무작위 정보를 세션 상태에 저장하면 각 사용자가 앱을 열 때 서로 다른 무작위 데이터를 얻게 되지만 앱과 상호 작용할 때 계속 변경되지 않습니다. 피커로 다른 색상을 선택하면 다시 실행할 때마다 데이터가 다시 무작위화되지 않는 것을 볼 수 있습니다. (새 탭에서 앱을 열어 새 세션을 시작하면 다른 데이터를 볼 수 있습니다!)

import streamlit as st
import pandas as pd
import numpy as np

if "df" not in st.session_state:
    st.session_state.df = pd.DataFrame(np.random.randn(20, 2), columns=["x", "y"])

st.header("Choose a datapoint color")
color = st.color_picker("Color", "#FF0000")
st.divider()
st.scatter_chart(st.session_state.df, x="x", y="y", color=color)

모든 사용자에 대해 동일한 데이터를 가져오는 경우 해당 데이터를 검색하는 함수를 캐시할 수 있습니다. 반면에 사용자의 개인 정보를 쿼리하는 등 특정 사용자에 대한 데이터를 가져오는 경우에는 세션 상태에 저장하는 것이 좋습니다. 이렇게 하면 쿼리된 데이터를 해당 세션에서만 사용할 수 있습니다. 

기본 개념에서 언급했듯이 세션 상태는 위젯과도 관련이 있습니다. 위젯은 마법과도 같아서 자체적으로 상태 저장성을 조용히 처리합니다. 하지만 고급 기능으로 위젯에 키를 할당하여 코드 내에서 위젯의 값을 조작할 수 있습니다. 위젯에 할당된 모든 키는 세션 상태의 키가 되어 위젯의 값에 연결됩니다. 이 키를 사용하여 위젯을 조작할 수 있습니다. 스트림릿의 기본 사항에 대한 이해를 마친 후에는 위젯 동작에 대한 가이드를 확인하여 자세한 내용을 살펴보세요.

3. Connections

위에서 힌트를 제공했듯이 @st.cache_resource를 사용하여 연결을 캐시할 수 있습니다. 이것은 모든 파이썬 라이브러리의 거의 모든 연결을 사용할 수 있는 가장 일반적인 솔루션입니다. 하지만 Streamlit은 SQL과 같이 가장 많이 사용되는 연결을 처리하는 편리한 방법도 제공합니다! st.connection이 캐싱을 대신 처리하므로 코드 줄이 줄어듭니다. 데이터베이스에서 데이터를 가져오는 것은 매우 쉽습니다:

import streamlit as st

conn = st.connection("my_database")
df = conn.query("select * from my_table")
st.dataframe(df)

물론 사용자 이름과 비밀번호가 어디로 가는지 궁금할 수도 있습니다. 스트림릿에는 시크릿 관리를 위한 편리한 메커니즘이 있습니다. 지금은 st.connection이 시크릿과 어떻게 잘 작동하는지 살펴봅시다. 로컬 프로젝트 디렉토리에 .streamlit/secrets.toml 파일을 저장할 수 있습니다. toml 파일에 시크릿을 저장하면 st.connection이 이를 사용하기만 하면 됩니다! 예를 들어, 앱 파일 streamlit_app.py가 있는 경우 프로젝트 디렉토리는 다음과 같이 보일 수 있습니다: 

your-LOCAL-repository/
├── .streamlit/
│   └── secrets.toml # Make sure to gitignore this!
└── streamlit_app.py

위의 SQL 예제에서 secrets.toml 파일은 다음과 같이 보일 수 있습니다:

[connections.my_database]
    type="sql"
    dialect="mysql"
    username="xxx"
    password="xxx"
    host="example.com" # IP or URL
    port=3306 # Port number
    database="mydb" # Database name

secrets.toml 파일을 리포지토리에 커밋하고 싶지 않으므로 앱을 게시할 준비가 되면 호스트가 비밀을 처리하는 방법을 알아두어야 합니다. 호스트 플랫폼마다 시크릿을 전달하는 방식이 다를 수 있습니다. 예를 들어 스트림릿 커뮤니티 클라우드를 사용하는 경우 배포된 각 앱에는 비밀번호를 로드할 수 있는 설정 메뉴가 있습니다. 앱을 작성하고 배포할 준비가 되면 커뮤니티 클라우드에 앱을 배포하는 방법에 대한 모든 내용을 확인할 수 있습니다.