diff --git a/nbs/load_tests.ipynb b/nbs/load_tests.ipynb
new file mode 100644
index 0000000..8fe264f
--- /dev/null
+++ b/nbs/load_tests.ipynb
@@ -0,0 +1,274 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0446d834",
+ "metadata": {},
+ "source": [
+ "# Load tests\n",
+ "\n",
+ "> Performance testing for apswutils using different configuration"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "03c8fc1f",
+ "metadata": {},
+ "source": [
+ "Plan: \n",
+ "\n",
+ "1. Create a single db load test that uses threading to perform a high volume of writes and some reads to replicate behavior under load by many users. Lock errors will be append to a simple text file called 'lock-errors.txt'\n",
+ "2. Extract that from the cell and feed that into timeit and/or cProfile in another cell"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c28db89e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import threading\n",
+ "import random\n",
+ "from string import ascii_letters\n",
+ "from apswutils.db import Database, Table\n",
+ "import timeit\n",
+ "import inspect"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "686289a0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_table(reset=False):\n",
+ " db = Database(\"load.db\")\n",
+ " if reset:\n",
+ " for t in db.tables: t.drop()\n",
+ " users = Table(db, 'Users')\n",
+ " users.create(columns=dict(id=int, name=str), transform=True, pk='id')\n",
+ " return users"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a6eaa87e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "
"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "users = get_table(reset=False)\n",
+ "users"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f1476bfb",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'SCyFLvRxqC'"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def get_random_name(length=10):\n",
+ " return ''.join(random.choice(ascii_letters) for _ in range(length))\n",
+ "get_random_name()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "117629a1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_user(id): return users.get(id)\n",
+ "def set_user(id,name): return users.insert({'name':name},pk='id')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1976b770",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'id': 1, 'name': 'BqjlFTjvLU', 'type': 'write'}"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def db_worker():\n",
+ " record = set_user(random.randint(1,1000), get_random_name()).result[0]\n",
+ " record['type'] = 'write'\n",
+ " return record\n",
+ "db_worker()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4ff89700",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def db_worker_batch(size=100):\n",
+ " for i in range(size): db_worker()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d36eb83a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def run_concurrent_workers(n_threads=10):\n",
+ " print(users.count)\n",
+ " threads = []\n",
+ " for _ in range(n_threads):\n",
+ " t = threading.Thread(target=db_worker_batch)\n",
+ " threads.append(t)\n",
+ " t.start()\n",
+ " \n",
+ " for t in threads:\n",
+ " t.join()\n",
+ " print(users.count)\n",
+ " print('-------')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "86ab4bd7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "555201\n",
+ "555301\n",
+ "-------\n"
+ ]
+ }
+ ],
+ "source": [
+ "run_concurrent_workers(1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8d418750",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "setup = \"\"\"\n",
+ "import threading\n",
+ "import random\n",
+ "from string import ascii_letters\n",
+ "from apswutils.db import Database, Table\n",
+ "\"\"\"\n",
+ "setup += f'\\n{inspect.getsource(get_table)}'\n",
+ "setup += f'\\nusers = get_table()'\n",
+ "setup += f'\\n{inspect.getsource(get_random_name)}'\n",
+ "setup += f'\\n{inspect.getsource(get_user)}'\n",
+ "setup += f'\\n{inspect.getsource(set_user)}'\n",
+ "setup += f'\\n{inspect.getsource(db_worker)}'\n",
+ "setup += f'\\n{inspect.getsource(db_worker_batch)}'\n",
+ "setup += f'\\n{inspect.getsource(run_concurrent_workers)}'\n",
+ "\n",
+ "# print(setup)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "60d38c66",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "test_code = \"\"\"run_concurrent_workers(1000)\"\"\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5b656178",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "555301\n",
+ "655301\n",
+ "-------\n",
+ "655301\n",
+ "755301\n",
+ "-------\n",
+ "755301\n",
+ "855301\n",
+ "-------\n",
+ "855301\n",
+ "955301\n",
+ "-------\n",
+ "955301\n",
+ "1055301\n",
+ "-------\n",
+ "[291.8241256250185, 300.83988204202615, 285.32696783400024, 269.2562499170017, 268.16414191701915]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(timeit.repeat(stmt=test_code, setup=setup, number=1))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ccd6594e",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}