diff --git a/app.py b/app.py index ca69e37..115e1b5 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,7 @@ from requests_oauthlib import OAuth2Session import os import subprocess import psutil +import time import mysql.connector import mysql.connector.pooling from datetime import datetime, timedelta @@ -89,7 +90,7 @@ def stop_bot(): # Database Connection Pool für bessere Verbindungsverwaltung app_pool = mysql.connector.pooling.MySQLConnectionPool( pool_name="app_pool", - pool_size=15, # Reduziert von 20 auf 15 (Bot: 30, App: 15 = max 45 statt 50+) + pool_size=8, # Stark reduziert von 15 auf 8 (Bot: 25, App: 8 = 33 total) pool_reset_session=True, host=DB_HOST, port=DB_PORT, @@ -109,14 +110,24 @@ def get_db_connection(): return connection except mysql.connector.PoolError as e: print(f"Pool error in app.py: {e}") - # Fallback zu direkter Verbindung bei Pool-Problemen - return mysql.connector.connect( - host=DB_HOST, - port=DB_PORT, - user=DB_USER, - password=DB_PASS, - database=DB_NAME - ) + # Besserer Fallback mit Retry-Mechanismus + for attempt in range(3): + try: + print(f"Attempting direct connection (attempt {attempt + 1}/3)") + return mysql.connector.connect( + host=DB_HOST, + port=DB_PORT, + user=DB_USER, + password=DB_PASS, + database=DB_NAME, + connect_timeout=5, + autocommit=True + ) + except Exception as retry_error: + print(f"Direct connection attempt {attempt + 1} failed: {retry_error}") + if attempt == 2: # Letzter Versuch + raise retry_error + time.sleep(1) # Kurze Pause zwischen Versuchen from contextlib import contextmanager @@ -231,6 +242,24 @@ def landing_page(): """Landing Page""" return render_template("landing.html") +@app.route("/db_status") +def db_status(): + """Debug-Route für DB-Pool Status""" + try: + status = get_pool_status() + with get_db_cursor() as (cursor, connection): + cursor.execute("SELECT 1") + db_ok = True + except Exception as e: + status = {"error": str(e)} + db_ok = False + + return jsonify({ + "pool_status": status, + "database_ok": db_ok, + "timestamp": datetime.now().isoformat() + }) + @app.route("/about") def about(): """Öffentliche Über-uns-Seite""" @@ -268,29 +297,56 @@ def load_user_data(): g.is_admin = session.get("is_admin", False) g.bot_running = bot_status() # Lädt den Bot-Status in g - # Hole die Liste der Gilden aus der Datenbank - connection = get_db_connection() - cursor = connection.cursor(dictionary=True) - - # Lade die Gilden des Nutzers + # Lade die Gilden des Nutzers aus der Session (ohne DB-Zugriff) user_guilds = session.get("discord_guilds", []) - user_guild_ids = [guild["id"] for guild in user_guilds] - - # Finde nur die Gilden, die auch in der Datenbank existieren - cursor.execute("SELECT guild_id FROM guilds WHERE guild_id IN (%s)" % ','.join(['%s'] * len(user_guild_ids)), user_guild_ids) - existing_guilds = cursor.fetchall() - - # Filtere die Gilden des Nutzers basierend auf der Existenz in der Datenbank - g.guilds = [guild for guild in user_guilds if int(guild["id"]) in {g["guild_id"] for g in existing_guilds}] - - cursor.close() - connection.close() + + # Nur DB-Zugriff wenn Gilden-Daten nicht im Cache sind + guild_cache_key = f"guild_cache_{g.user_info['id']}" + if guild_cache_key not in session or not session[guild_cache_key]: + try: + with get_db_cursor() as (cursor, connection): + user_guild_ids = [guild["id"] for guild in user_guilds] + + if user_guild_ids: # Nur wenn Gilden existieren + # Finde nur die Gilden, die auch in der Datenbank existieren + placeholders = ','.join(['%s'] * len(user_guild_ids)) + cursor.execute(f"SELECT guild_id FROM guilds WHERE guild_id IN ({placeholders})", user_guild_ids) + existing_guilds = cursor.fetchall() + existing_guild_ids = {g["guild_id"] for g in existing_guilds} + + # Filtere die Gilden des Nutzers basierend auf der Existenz in der Datenbank + filtered_guilds = [guild for guild in user_guilds if int(guild["id"]) in existing_guild_ids] + + # Cache die gefilterten Gilden für 5 Minuten + session[guild_cache_key] = filtered_guilds + session[f"{guild_cache_key}_time"] = time.time() + g.guilds = filtered_guilds + else: + g.guilds = [] + except Exception as e: + print(f"Error loading guild data: {e}") + # Fallback: Verwende alle Gilden aus der Session + g.guilds = user_guilds + # Setze einen Flag für DB-Probleme + g.db_error = True + else: + # Prüfe Cache-Alter (5 Minuten TTL) + cache_time = session.get(f"{guild_cache_key}_time", 0) + if time.time() - cache_time > 300: # 5 Minuten + # Cache ist abgelaufen, lösche ihn + session.pop(guild_cache_key, None) + session.pop(f"{guild_cache_key}_time", None) + g.guilds = user_guilds # Fallback + else: + # Verwende gecachte Daten + g.guilds = session[guild_cache_key] else: # Falls der Benutzer nicht eingeloggt ist, keine Daten setzen g.user_info = None g.is_admin = False g.guilds = [] g.bot_running = False + g.db_error = False @app.route("/callback") def callback(): diff --git a/bot.py b/bot.py index fbe4c8e..81fe745 100644 --- a/bot.py +++ b/bot.py @@ -196,7 +196,7 @@ def retry_query(func, *args, retries=3, delay=5): pool = mysql.connector.pooling.MySQLConnectionPool( pool_name="mypool", - pool_size=25, # Reduziert von 30 auf 25 (App: 15, Bot: 25 = 40 total) + pool_size=15, # Weiter reduziert von 25 auf 15 (App: 8, Bot: 15 = 23 total) pool_reset_session=True, autocommit=True, host=DB_HOST,