Sync app.storage between multiple instances #1606
Replies: 7 comments 8 replies
-
The most popular solution to sync state between multiple instances is Redis. Maybe we could make the storage solution configurable? Then, instead of writing to disk a Redis database can be used as a shared storage across all instances. |
Beta Was this translation helpful? Give feedback.
-
Anyone interested in creating a draft? |
Beta Was this translation helpful? Give feedback.
-
so i managed to make a basic work around that purely works for my site which uses https://console.upstash.com/ and this py lib: https://github.com/upstash/redis-python. i know the example below isnt minimal but it shows how you can use it. Note: this works when deploying with fly.io but doesnt work with on_air because on_air generates a new browser id everytime. from nicegui import ui, app
import hashlib
from fastapi.responses import RedirectResponse
import psycopg2
from upstash_redis.client import Redis
import json
import asyncio
primary_color = '#2d3748'
# NOTE: this is the external database, you can just make a local one for testing
external_database_url = ""
# NOTE: this is an upstash redis database, you can make one for free here: https://console.upstash.com/
redis_client = Redis(
url="",
token=""
)
admin_users = ['lee81', 'ff24']
session_ids = []
def hash_data(data):
sha256 = hashlib.sha256()
sha256.update(data.encode())
return sha256.hexdigest()
def get_session_identifier():
session_identifier = app.storage.browser['id']
if session_identifier in session_ids:
return session_identifier
else:
session_ids.append(session_identifier)
return session_identifier
def get_redis_data(key):
return redis_client.get(key)
@ui.page('/')
def confirm_delivery():
user_info_json = get_redis_data(get_session_identifier())
if user_info_json is None:
return RedirectResponse('/login')
user_info = json.loads(user_info_json)
if not user_info.get('authenticated'):
return RedirectResponse('/login')
with ui.header(fixed=False).style(f'background-color: {primary_color};'):
with ui.row().style('align-items: center;'):
ui.label('test').style(
'font-size: 25px; font-weight: bold;')
ui.link('Upload Document', '/upload_document')
@ui.page('/upload_document')
def upload_document():
user_info_json = get_redis_data(get_session_identifier())
if user_info_json is None:
return RedirectResponse('/login')
user_info = json.loads(user_info_json)
if user_info.get('username') not in admin_users:
return RedirectResponse('/')
ui.label('hello world')
@ui.page('/login')
def login():
async def try_login():
try:
input_username = username.value
input_password = password.value
conn = psycopg2.connect(external_database_url)
c = conn.cursor()
encrypted_username = hash_data(input_username)
encrypted_password = hash_data(input_password)
c.execute(
'SELECT * FROM login_info WHERE username = %s::bytea AND password = %s::bytea',
(encrypted_username, encrypted_password,)
)
result = c.fetchone()
if result:
if encrypted_password == bytes(result[1]).decode('utf-8'):
session_id = get_session_identifier()
session_data = {
"username": input_username,
"authenticated": True,
"haulier": bytes(result[2]).decode('utf-8'),
}
session_json = json.dumps(session_data)
redis_client.set(session_id, session_json)
ui.notify('Login Successful', color='positive')
await asyncio.sleep(1.5)
ui.open('/')
else:
ui.notify('Invalid username or password', color='negative')
username.set_value('')
password.set_value('')
else:
ui.notify('Invalid username or password', color='negative')
username.set_value('')
password.set_value('')
except Exception as e:
print(e)
ui.query('body').style(f'background-color: {primary_color};')
with ui.card().style('width: 250px;').classes('absolute-center'):
username = ui.input('Enter Your Username').style(
'width: 225px;').props('outlined color="emerald-500"')
password = ui.input('Enter Your Password', password=True, password_toggle_button=True).style(
'width: 225px;').props('outlined color="emerald-500"').on('keydown.enter', try_login)
ui.button('Log In', on_click=try_login, color=primary_color).style(
'color: white; margin-left: auto; margin-right: auto;')
ui.run(title='test', storage_secret='test', reload=False) |
Beta Was this translation helpful? Give feedback.
-
This feature is already quite highly voted (add your's if you need it). There was also a discussion on Discord. If there are not a lot of data, a workaround could be to use the |
Beta Was this translation helpful? Give feedback.
-
I was looking online for a way to configure NiceGUI's storage centrally using something like Redis, and came across this discussion. Would definitely be a plus for any multi-instance configuration. |
Beta Was this translation helpful? Give feedback.
-
Just came across this, I have the same problem where I want to scale out the app servers and have user storage. I figured I could just mount a shared volume, but then checking out This is probably a very high priority improvement for anyone looking to do serious deployments at some scale. I like the idea of the user instantiating their own storage class, but also having some built-in ones (the existing file-based one for simplicity, plus Postgres, Redis etc). I'd be tempted to have a stab at one but I'm nowhere near familiar enough with the code. 😞 |
Beta Was this translation helpful? Give feedback.
-
How would be go about to sponsor such an improvement @rodja ? We would be happy to instantiate a storage class if that was an option. Any way of overcoming multiple workers/threads issues for app.storage.user is very interesting to us and we would be willing to put some money behind this. |
Beta Was this translation helpful? Give feedback.
-
With Fly.io, Google Cloud Run, Docker Swarm, Kubernetes etc. a NiceGUI app can be is deployed to multiple instances which get fed requests by load balancer. In this scenario each instance will create their own
.nicegui
directory for storage. Depending on where a user requests are placed the storage data created by one instance may not be available to the other.NiceGUI should allow a simple way to keep multiple instances in sync.
Beta Was this translation helpful? Give feedback.
All reactions