handle seminarplatz_vergabe concurrently

This commit is contained in:
Michael Schären 2026-04-19 23:20:00 +02:00
parent 3558bdde0a
commit 5cbda2dca7
7 changed files with 425 additions and 15 deletions

View File

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="$USER_HOME$/anaconda3" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$PROJECT_DIR$/ViewsundTrigger.sql" value="e1dddddf-a4bd-4327-84c2-f4c31c6f0bd2" />
<file url="file://$PROJECT_DIR$/create_movies.sql" value="e1dddddf-a4bd-4327-84c2-f4c31c6f0bd2" />
<file url="file://$PROJECT_DIR$/movies_data.sql" value="e1dddddf-a4bd-4327-84c2-f4c31c6f0bd2" />
<file url="file://$PROJECT_DIR$/postgreSQL_Vertiefung_CDS104_FS26.sql" value="e1dddddf-a4bd-4327-84c2-f4c31c6f0bd2" />
</component>
</project>

2
.idea/misc.xml generated
View File

@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.12" /> <option name="sdkName" value="Python 3.12" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="$USER_HOME$/anaconda3" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
</project> </project>

4
.idea/sqldialects.xml generated
View File

@ -1,10 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="SqlDialectMappings"> <component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/ViewsundTrigger.sql" dialect="PostgreSQL" />
<file url="file://$PROJECT_DIR$/create_movies.sql" dialect="PostgreSQL" />
<file url="file://$PROJECT_DIR$/movies_data.sql" dialect="PostgreSQL" />
<file url="file://$PROJECT_DIR$/postgreSQL_Vertiefung_CDS104_FS26.sql" dialect="PostgreSQL" />
<file url="PROJECT" dialect="PostgreSQL" /> <file url="PROJECT" dialect="PostgreSQL" />
</component> </component>
</project> </project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,417 @@
{
"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
}