{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Seminarplatz-Vergabe\n", "\n", "In folgendem Problem geht es darum, dass eine Gruppe von Studierenden einen oder mehrere Seminarplätze in einem Kurs bekommen sollen. Es gibt 8 Seminargruppen mit insgesamt 40 Plätzen, diese sind in der Tabelle `seminar_sessions` abgelegt.\n", "\n", "Bei einer Anmeldung einer einzelnen Person wird die Anzahl der Plätze in der Tabelle `seminar_sessions` um 1 reduziert und ein Eintrag in der Tabelle `seminar_registrations` erstellt. Dies darf natürlich nur passieren, wenn noch Plätze verfügbar sind.\n", "\n", "Die Anzahl der vergebenen Plätze in der Tabelle `seminar_registrations` und die Anzahl der verfügbaren Plätze in der Tabelle `seminar_sessions` müssen konsistent sein, also in der Summe 40 ergeben.\n", "\n", "In einem Lasttest mit mehreren Threads zeigt sich: Die Anzahl der vergebenen Plätze in der Tabelle `seminar_registrations` und die Anzahl der verfügbaren Plätze in der Tabelle `seminar_sessions` sind nicht konsistent! \n", "\n", "### Ihre Aufgabe:\n", "Führen Sie geeignete Transaktionen ein, um die Konsistenz zu gewährleisten!\n", "\n", "Implementierungen Sie dazu zunächst die Funktionen, die mit TODO gekennzeichnet sind, und passen Sie die Funktion `reserve_seat` an, um die Konsistenz zu gewährleisten.\n", "\n", "Am Code des Lasttests müssen Sie nichts ändern, er dient nur dazu, das Problem zu demonstrieren.\n", "\n", "### Bemerkungen:\n", "\n", "- nicht jeder Durchlauf verursacht Inkonsistenzen, manchmal müssen Sie den Code mehrmals ausführen, um Inkonsistenzen zu finden\n", "- auch Deadlocks können auftreten, diese müssen auch vermieden werden\n", "- die User-Tabelle wurde weggelassen, da sie für das Problem nicht relevant ist\n", "- an manchen Stellen ist der Code absichtlich etwas ineffizient gehalten, um die Inkonsistenzen zu begünstigen (z.B. würden Probleme mit Inkonsistenzen seltener auftreten, wenn die Query `UPDATE seminar_sessions SET seats = seats-1` lauten würde).\n", "\n" ] }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2026-04-19T20:28:20.768652Z", "start_time": "2026-04-19T20:28:20.766464Z" } }, "source": [ "import psycopg2\n", "import psycopg2.extras" ], "outputs": [], "execution_count": 26 }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2026-04-19T20:28:21.151712Z", "start_time": "2026-04-19T20:28:21.149462Z" } }, "source": [ "# Datenbankverbindung herstellen\n", "def get_connection():\n", " conn = psycopg2.connect(\"dbname=seminar user=postgres password=sml12345\")\n", " return conn" ], "outputs": [], "execution_count": 27 }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2026-04-19T20:28:37.424939Z", "start_time": "2026-04-19T20:28:37.390822Z" } }, "source": [ "# Tabelle erzeugen - immer wenn diese Zelle ausgeführt wird, wird die Tabelle neu erzeugt\n", "conn = get_connection()\n", "cursor = conn.cursor()\n", "cursor.execute(\"\"\"\n", " DROP TABLE IF EXISTS seminar_registrations CASCADE;\n", " DROP TABLE IF EXISTS seminar_sessions;\n", " \n", " CREATE TABLE IF NOT EXISTS seminar_sessions (\n", " id SERIAL PRIMARY KEY, \n", " seats INTEGER NOT NULL\n", " );\n", " \n", " INSERT INTO seminar_sessions (seats) VALUES \n", " (4), (4), (5), (5), (4), (10), (5), (3);\n", " \n", " \n", " CREATE TABLE IF NOT EXISTS seminar_registrations (\n", " user_id INTEGER NOT NULL,\n", " session_id INTEGER NOT NULL,\n", " FOREIGN KEY (session_id) REFERENCES seminar_sessions(id)\n", " ); \n", "\"\"\")\n", "conn.commit()" ], "outputs": [], "execution_count": 33 }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2026-04-19T20:28:22.178605Z", "start_time": "2026-04-19T20:28:22.174509Z" } }, "source": [ "# Hilfsfunktion: Alle Seminar-Sessions ausgeben\n", "def show_sessions():\n", " cursor.execute(\"SELECT * FROM seminar_sessions\")\n", " rows = cursor.fetchall()\n", " print(\"Seminar Sessions:\")\n", " for row in rows:\n", " print(row[0], row[1])\n", "\n", "\n", "# Anzahl aller freien Plätze bestimmen\n", "def count_free_seats():\n", " cursor.execute(\"SELECT sum(seats) FROM seminar_sessions\")\n", " return cursor.fetchone()[0]\n", "\n", "# Anzahl der belegten Plätze bestimmen\n", "def count_occupied_seats():\n", " cursor.execute(\"SELECT count(*) FROM seminar_registrations\")\n", " return cursor.fetchone()[0]\n", "\n", "# Testaufrufe\n", "\n", "show_sessions()\n", "print(\"freie Plätze\", count_free_seats())\n", "print(\"belegte Plätze\", count_occupied_seats())" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Seminar Sessions:\n", "1 4\n", "2 4\n", "3 5\n", "4 5\n", "5 4\n", "6 10\n", "7 5\n", "8 3\n", "freie Plätze 40\n", "belegte Plätze 0\n" ] } ], "execution_count": 29 }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2026-04-19T20:28:22.832360Z", "start_time": "2026-04-19T20:28:22.829458Z" } }, "source": [ "# Diese Funktion soll einen Platz reservieren und True zurückgeben, wenn es geklappt hat.\n", "# Wenn kein Platz mehr frei ist, soll False zurückgegeben werden.\n", "def reserve_seat(conn, user_id, session_id):\n", " with conn.cursor() as cursor:\n", " cursor.execute(\"SELECT seats FROM seminar_sessions WHERE id = %s FOR UPDATE\", (session_id,))\n", " seats = cursor.fetchone()[0]\n", "\n", " if seats > 0:\n", " cursor.execute(\"UPDATE seminar_sessions SET seats = %s WHERE id = %s\", (seats-1, session_id,))\n", " cursor.execute(\"INSERT INTO seminar_registrations (user_id, session_id) VALUES (%s, %s)\", (user_id, session_id))\n", " conn.commit()\n", " return True\n", " else:\n", " conn.rollback()\n", " return False\n", " \n" ], "outputs": [], "execution_count": 30 }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2026-04-19T20:28:23.903374Z", "start_time": "2026-04-19T20:28:23.900910Z" } }, "source": [ "import random \n", "\n", "# 20 zufällige Anmeldungen durchführen - diese Funktion wird später als Thread gestartet\n", "def random_seat_reservation(n=20):\n", " # jeder Thread hat seine eigene Verbindung\n", " conn = get_connection()\n", " \n", " # n mal einen zufälligen Platz für eine zufällige User-ID reservieren\n", " for _ in range(n):\n", " random_user_id = random.randint(1, 100)\n", " random_session_id = random.randint(1, 8)\n", "\n", " if reserve_seat(conn, random_user_id, random_session_id):\n", " print(\"User\", random_user_id, \"hat sich für Session\", random_session_id, \"angemeldet\")\n", " else:\n", " print(\"Session\", random_session_id, \"ist ausgebucht\")" ], "outputs": [], "execution_count": 31 }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2026-04-19T20:28:40.301148Z", "start_time": "2026-04-19T20:28:40.248650Z" } }, "source": [ "import threading\n", "\n", "# Diese Funktion gibt Informationen über die Belegung der Seminare aus\n", "def print_info():\n", " print('-'*20)\n", " \n", " free_seats = count_free_seats()\n", " occupied_seats = count_occupied_seats()\n", "\n", " print(\"Freie Plätze:\", free_seats)\n", " print(\"Belegte Plätze:\", occupied_seats)\n", " print(\"Gesamt:\", free_seats + occupied_seats)\n", " \n", " if free_seats + occupied_seats == 40:\n", " print(\"Platzanzahl ist konsistent!\")\n", " else:\n", " print(\"Platzanzahl ist inkonsistent!\")\n", "\n", " print('-'*20)\n", "\n", "# -------------------------------------------------------------------------\n", "# Mini-Lasttest, 5 Threads starten, die jeweils 20 Reservierungen vornehmen\n", "# -------------------------------------------------------------------------\n", "\n", "print(\"Vor dem Lasttest:\")\n", "print_info()\n", "\n", "threads = [ threading.Thread(target=random_seat_reservation) for _ in range(5)]\n", "\n", "# Threads starten\n", "for t in threads:\n", " t.start()\n", "\n", "# Auf das Ende der Threads warten\n", "for t in threads:\n", " t.join()\n", "\n", " \n", "print(\"Nach dem Lasttest:\")\n", "print_info()" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Vor dem Lasttest:\n", "--------------------\n", "Freie Plätze: 40\n", "Belegte Plätze: 0\n", "Gesamt: 40\n", "Platzanzahl ist konsistent!\n", "--------------------\n", "User 50 hat sich für Session 2 angemeldet\n", "User 42 hat sich für Session 5 angemeldet\n", "User 59 hat sich für Session 5 angemeldet\n", "User 33 hat sich für Session 8 angemeldet\n", "User 6 hat sich für Session 2 angemeldet\n", "User 30 hat sich für Session 3 angemeldet\n", "User 14 hat sich für Session 5 angemeldet\n", "User 75 hat sich für Session 1 angemeldet\n", "User 44 hat sich für Session 8 angemeldet\n", "User 42 hat sich für Session 5 angemeldet\n", "User 87 hat sich für Session 7 angemeldet\n", "User 44 hat sich für Session 4 angemeldet\n", "User 87 hat sich für Session 2 angemeldet\n", "User 64 hat sich für Session 2 angemeldet\n", "User 83 hat sich für Session 1 angemeldet\n", "User 54 hat sich für Session 1 angemeldet\n", "User 47 hat sich für Session 3 angemeldet\n", "User 23 hat sich für Session 1 angemeldet\n", "Session 2 ist ausgebucht\n", "User 91 hat sich für Session 4 angemeldet\n", "User 22 hat sich für Session 3 angemeldet\n", "Session 2 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "User 86 hat sich für Session 3 angemeldet\n", "User 36 hat sich für Session 4 angemeldet\n", "Session 2 ist ausgebucht\n", "Session 5 ist ausgebucht\n", "User 1 hat sich für Session 8 angemeldet\n", "Session 1 ist ausgebucht\n", "User 3 hat sich für Session 4 angemeldet\n", "User 85 hat sich für Session 7 angemeldet\n", "Session 2 ist ausgebucht\n", "User 32 hat sich für Session 6 angemeldet\n", "Session 5 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "User 29 hat sich für Session 3 angemeldet\n", "User 58 hat sich für Session 6 angemeldet\n", "User 75 hat sich für Session 7 angemeldet\n", "User 50 hat sich für Session 4 angemeldet\n", "Session 2 ist ausgebucht\n", "Session 5 ist ausgebucht\n", "Session 4 ist ausgebucht\n", "User 99 hat sich für Session 6 angemeldet\n", "User 82 hat sich für Session 7 angemeldet\n", "Session 3 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "Session 5 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "Session 8 ist ausgebucht\n", "Session 5 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "Session 5 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "Session 3 ist ausgebucht\n", "Session 1 ist ausgebucht\n", "Session 1 ist ausgebucht\n", "User 67 hat sich für Session 6 angemeldet\n", "Session 5 ist ausgebucht\n", "Session 5 ist ausgebucht\n", "User 53 hat sich für Session 7 angemeldet\n", "Session 3 ist ausgebucht\n", "Session 7 ist ausgebucht\n", "Session 8 ist ausgebucht\n", "Session 8 ist ausgebucht\n", "Session 8 ist ausgebucht\n", "Session 7 ist ausgebucht\n", "Session 1 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "Session 4 ist ausgebucht\n", "Session 8 ist ausgebucht\n", "Session 3 ist ausgebucht\n", "Session 3 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "Session 7 ist ausgebucht\n", "Session 4 ist ausgebucht\n", "User 12 hat sich für Session 6 angemeldet\n", "Session 4 ist ausgebucht\n", "Session 4 ist ausgebucht\n", "Session 3 ist ausgebucht\n", "User 68 hat sich für Session 6 angemeldet\n", "Session 5 ist ausgebucht\n", "Session 8 ist ausgebucht\n", "Session 7 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "User 18 hat sich für Session 6 angemeldet\n", "Session 3 ist ausgebucht\n", "Session 4 ist ausgebucht\n", "User 56 hat sich für Session 6 angemeldet\n", "Session 1 ist ausgebucht\n", "Session 7 ist ausgebucht\n", "Session 4 ist ausgebucht\n", "Session 8 ist ausgebucht\n", "Session 1 ist ausgebucht\n", "User 88 hat sich für Session 6 angemeldet\n", "Session 7 ist ausgebucht\n", "Session 5 ist ausgebucht\n", "Session 5 ist ausgebucht\n", "Session 2 ist ausgebucht\n", "User 28 hat sich für Session 6 angemeldet\n", "Nach dem Lasttest:\n", "--------------------\n", "Freie Plätze: 0\n", "Belegte Plätze: 40\n", "Gesamt: 40\n", "Platzanzahl ist konsistent!\n", "--------------------\n" ] } ], "execution_count": 34 }, { "metadata": {}, "cell_type": "code", "outputs": [], "execution_count": null, "source": "" } ], "metadata": { "kernelspec": { "display_name": "base", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.7" } }, "nbformat": 4, "nbformat_minor": 2 }