Enhance styles and add comparison table for PickleDB vs PickleDBSQLite

Commit 7f0e0af · patx · 2025-12-22T00:28:32-05:00

Changeset
7f0e0af6b3ef1866cd35045d24dc4911dd288380
Parents
3a0787a4ff1b4036ef03ef66f1ec76373fc20582

View source at this commit

Enhance styles and add comparison table for PickleDB vs PickleDBSQLite

Added new accent colors for warnings and dangers, updated callout styles, and introduced a comparison table for PickleDB backends.

Comments

No comments yet.

Log in to comment

Diff

diff --git a/docs/index.html b/docs/index.html
index dd4c79e..86edfe2 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -21,6 +21,8 @@
       --accent-forest: #3a9943;
       --accent-sage: #6b9b6e;
       --accent-mint: #4ecdc4;
+      --accent-warning: #f59e0b;
+      --accent-danger: #ef4444;
       --border: #dfe8e0;
       --border-dark: #c5d4c7;
       --shadow: rgba(26, 31, 27, 0.08);
@@ -490,8 +492,13 @@
     }
 
     .callout-warning {
-      border-left-color: var(--accent-lime);
-      background: #fefce8;
+      border-left-color: var(--accent-warning);
+      background: #fef3c7;
+    }
+
+    .callout-danger {
+      border-left-color: var(--accent-danger);
+      background: #fee2e2;
     }
 
     .callout-success {
@@ -504,13 +511,21 @@
       color: var(--text-primary);
     }
 
+    .callout p + p {
+      margin-top: 12px;
+    }
+
     .callout strong {
       color: var(--accent-mint);
       font-weight: 700;
     }
 
     .callout-warning strong {
-      color: #84a617;
+      color: #d97706;
+    }
+
+    .callout-danger strong {
+      color: #dc2626;
     }
 
     .callout-success strong {
@@ -721,6 +736,29 @@
       color: #4d5a0f;
     }
 
+    /* Comparison Table */
+    .comparison-table {
+      background: var(--bg-card);
+      border: 3px solid var(--accent-warning);
+      border-radius: 20px;
+      overflow: hidden;
+      margin: 40px 0;
+      box-shadow: 0 8px 24px var(--shadow-strong);
+    }
+
+    .comparison-table thead {
+      background: linear-gradient(135deg, var(--accent-warning), var(--accent-lime));
+    }
+
+    .comparison-table tbody tr:hover {
+      background: #fef3c7;
+    }
+
+    .comparison-table td:first-child {
+      font-weight: 600;
+      color: var(--text-primary);
+    }
+
     /* Footer */
     .footer-note {
       text-align: center;
@@ -900,15 +938,77 @@ asyncio.run(main())</code></pre>
           <li><strong>Unified sync/async:</strong> Every core method works in both worlds via the same name</li>
           <li><strong>Atomic disk writes:</strong> Uses a temp file and <code>os.replace</code> to avoid partial writes</li>
           <li><strong>No hidden autosave:</strong> Nothing is written to disk unless you call <code>save()</code> or exit a context manager cleanly</li>
-          <li>
-            <strong>Single file simplicity:</strong> Because the JSON database is a single file, <code>PickleDB</code> is 
-            <strong>not</strong> thread-safe or process-safe. For safer multi-thread/multi-process access on a single host,
-            use <code>PickleDBSQLite</code> (SQLite handles locking and ACID transactions). For networked or multi-host workloads,
-            consider <a href="https://github.com/patx/mongokv">mongoKV</a>.
-          </li>
-          <li><strong>The optional SQLite backend is not in-memory:</strong> <code>PickleDBSQLite</code> does <em>not</em> load data into RAM. Every operation executes directly against SQLite.</li>
         </ul>
       </div>
+
+      <div class="callout-danger callout">
+        <p><strong>⚠️ Thread & Process Safety:</strong> <code>PickleDB</code> (the default JSON-backed class) is <strong>not thread-safe or process-safe</strong>. Multiple threads or processes writing to the same JSON file <strong>will cause data corruption</strong>.</p>
+        <p><strong>When you need concurrency on a single machine:</strong> Use <code>PickleDBSQLite</code> instead. SQLite provides file-level locking and ACID transactions, making it safe for concurrent access across threads and processes on the same host.</p>
+        <p><strong>⚡ Performance trade-off:</strong> <code>PickleDBSQLite</code> is <strong>significantly slower</strong> than <code>PickleDB</code> because it reads/writes to disk on every operation instead of keeping everything in memory. Use it <em>only when you need the safety guarantees</em>.</p>
+        <p><strong>For networked or multi-host workloads:</strong> Neither class is suitable. Consider Redis, PostgreSQL, MongoDB, or <a href="https://github.com/patx/mongokv">mongoKV</a>.</p>
+      </div>
+    </section>
+
+    <section class="section fade-in">
+      <h2>Choosing the Right Backend</h2>
+      
+      <p>pickleDB offers two storage backends, each optimized for different use cases:</p>
+
+      <table class="comparison-table">
+        <thead>
+          <tr>
+            <th>Feature</th>
+            <th>PickleDB (orJSON)</th>
+            <th>PickleDBSQLite</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td><strong>Storage Model</strong></td>
+            <td>In-memory with JSON persistence</td>
+            <td>Disk-based SQLite with orjson encoding</td>
+          </tr>
+          <tr>
+            <td><strong>Performance</strong></td>
+            <td>Very fast (all operations in RAM)</td>
+            <td>Slower (disk I/O on every operation)</td>
+          </tr>
+          <tr>
+            <td><strong>Thread Safety</strong></td>
+            <td>❌ Not safe</td>
+            <td>✅ Safe (SQLite locking)</td>
+          </tr>
+          <tr>
+            <td><strong>Process Safety</strong></td>
+            <td>❌ Not safe (data corruption risk)</td>
+            <td>✅ Safe (file-level locking)</td>
+          </tr>
+          <tr>
+            <td><strong>ACID Transactions</strong></td>
+            <td>❌ No</td>
+            <td>✅ Yes</td>
+          </tr>
+          <tr>
+            <td><strong>Memory Usage</strong></td>
+            <td>High (entire DB in RAM)</td>
+            <td>Low (only active queries in RAM)</td>
+          </tr>
+          <tr>
+            <td><strong>Best For</strong></td>
+            <td>Single-threaded apps, scripts, fast prototypes</td>
+            <td>Multi-threaded/process apps, ASGI servers</td>
+          </tr>
+          <tr>
+            <td><strong>Dependencies</strong></td>
+            <td>None (built-in)</td>
+            <td><code>aiosqlite</code> (optional extra)</td>
+          </tr>
+        </tbody>
+      </table>
+
+      <div class="callout-warning callout">
+        <p><strong>Rule of thumb:</strong> Use <code>PickleDB</code> (JSON) by default for maximum performance. Only switch to <code>PickleDBSQLite</code> when you need thread/process safety or ACID guarantees, and can accept the performance penalty.</p>
+      </div>
     </section>
 
     <section class="section fade-in">
@@ -946,7 +1046,7 @@ asyncio.run(main())</code></pre>
 
     <section class="section fade-in">
       <h2>API Reference</h2>
-      <p>pickleDB exposes two primary classes: <code>PickleDB</code> for JSON-on-disk, and <code>PickleDBSQLite</code> for an optional SQLite backend. All core methods share the same names in synchronous and asynchronous code — just add <code>await</code> when you’re inside an <code>async</code> function.</p>
+      <p>pickleDB exposes two primary classes: <code>PickleDB</code> for JSON-on-disk, and <code>PickleDBSQLite</code> for an optional SQLite backend. All core methods share the same names in synchronous and asynchronous code — just add <code>await</code> when you're inside an <code>async</code> function.</p>
 
       <div class="api-section">
         <div class="api-class">
@@ -1018,7 +1118,7 @@ asyncio.run(main())</code></pre>
               </div>
             </div>
             <p class="api-desc">
-              Retrieve the value stored under <code>key</code>, or <code>default</code> if it doesn’t exist.
+              Retrieve the value stored under <code>key</code>, or <code>default</code> if it doesn't exist.
             </p>
             <ul class="api-usage">
               <li><strong>Sync:</strong> <code>db.get("name")</code> or <code>db.get("name", default="guest")</code></li>
@@ -1088,7 +1188,7 @@ asyncio.run(main())</code></pre>
               class PickleDBSQLite(sqlite_path: str = "pickledb.sqlite3", table_name: str = "kv")
             </div>
             <div class="api-class-note">
-              Optional SQLite-backed key-value store using the same sync/async method names. <strong>No in-memory cache</strong>; all operations hit the database directly.
+              Optional SQLite-backed key-value store using the same sync/async method names.
             </div>
           </div>
 
@@ -1100,8 +1200,11 @@ asyncio.run(main())</code></pre>
           </p>
           <p class="api-class-note" style="margin-top: 4px;">
             <strong>Why use it?</strong> SQLite adds file-level locking and ACID transactions, so concurrent readers and
-            serialized writers across multiple threads/processes on the <em>same machine</em> are handled for you. It’s still
-            not a distributed database, but it’s much safer than multiple processes writing to a plain JSON file.
+            serialized writers across multiple threads/processes on the <em>same machine</em> are handled for you. It's still
+            not a distributed database, but it's much safer than multiple processes writing to a plain JSON file.
+          </p>
+          <p class="api-class-note" style="margin-top: 4px; color: var(--accent-danger); font-weight: 600;">
+            <strong>⚠️ Performance warning:</strong> Unlike <code>PickleDB</code>, this class does <strong>NOT</strong> store data in memory. Every read and write hits the disk, making it significantly slower. Only use <code>PickleDBSQLite</code> when thread/process safety is essential.
           </p>
 
           <div class="api-method">
@@ -1220,70 +1323,6 @@ asyncio.run(main())</code></pre>
       </div>
     </section>
 
-<section class="section fade-in">
-  <h2>Why Two Different Classes?</h2>
-
-  <p>
-    <code>PickleDB</code> and <code>PickleDBSQLite</code> are separate classes because
-    they represent <strong>different storage models</strong>, not interchangeable
-    backends.
-  </p>
-
-  <table>
-    <thead>
-      <tr>
-        <th>Feature</th>
-        <th>PickleDB</th>
-        <th>PickleDBSQLite</th>
-      </tr>
-    </thead>
-    <tbody>
-      <tr>
-        <td>Data model</td>
-        <td>In-memory dict</td>
-        <td>SQLite table</td>
-      </tr>
-      <tr>
-        <td>In-memory cache</td>
-        <td>Yes (entire DB)</td>
-        <td>No</td>
-      </tr>
-      <tr>
-        <td>Persistence</td>
-        <td>Explicit <code>load()</code> / <code>save()</code></td>
-        <td>Immediate</td>
-      </tr>
-      <tr>
-        <td>Read/write cost</td>
-        <td>Memory</td>
-        <td>Disk + SQL</td>
-      </tr>
-      <tr>
-        <td>Concurrency</td>
-        <td>Single process only</td>
-        <td>Multi-process safe</td>
-      </tr>
-    </tbody>
-  </table>
-
-  <div class="callout">
-    <p>
-      <strong>Design choice:</strong>
-      pickleDB avoids “pluggable backends”.
-      Different behavior gets a different class so performance and guarantees
-      stay obvious.
-    </p>
-  </div>
-
-  <div class="callout-warning callout">
-    <p>
-      <strong>Important:</strong>
-      <code>PickleDBSQLite</code> does <em>not</em> store data in memory.
-      Every operation reads from or writes to SQLite directly.
-    </p>
-  </div>
-</section>
-
     <section class="section fade-in">
       <h2>Common Patterns</h2>
 
@@ -1346,7 +1385,7 @@ time.sleep(<span class="number">3</span>)
 
     <section class="section fade-in">
       <h2>Performance Snapshot</h2>
-      <p>Example timings loading, reading, and saving large JSON payloads using async mode on a Dell XPS 9350, Ubuntu 24.04:</p>
+      <p>Example timings loading, reading, and saving large JSON payloads using <code>PickleDB</code> (JSON) in async mode on a Dell XPS 9350, Ubuntu 24.04:</p>
 
       <table class="perf-table">
         <thead>
@@ -1382,19 +1421,24 @@ time.sleep(<span class="number">3</span>)
       <div class="callout-success callout">
         <p><strong>Benchmark Details:</strong> Full benchmark script available at <a href="https://gist.github.com/patx/025ed3a10482459f35c738228ebd0721">this GitHub Gist</a>.</p>
       </div>
+
+      <div class="callout-danger callout">
+        <p><strong>PickleDBSQLite Performance:</strong> The SQLite backend is <strong>orders of magnitude slower</strong> than these numbers because it performs disk I/O on every operation. Use it only when you need thread/process safety.</p>
+      </div>
     </section>
 
     <section class="section fade-in">
       <div class="warning-list">
         <h3>When Not to Use pickleDB</h3>
         <ul>
-          <li>You need multi-process or multi-host concurrency</li>
-          <li>ASGI web servers or frameworks <small>(unless you're using a single worker or SQLite backend carefully)</small></li>
-          <li>Your dataset is too large to comfortably fit in memory</li>
-          <li>You need rich querying, indexing, or joins</li>
+          <li><strong>PickleDB (JSON):</strong> Multi-threaded or multi-process applications (use <code>PickleDBSQLite</code> instead)</li>
+          <li><strong>Both classes:</strong> ASGI web servers with multiple workers</li>
+          <li><strong>Both classes:</strong> Your dataset is too large to comfortably fit in memory (JSON) or requires high-performance disk access (SQLite)</li>
+          <li><strong>Both classes:</strong> You need distributed/multi-host concurrency</li>
+          <li><strong>Both classes:</strong> You need rich querying, indexing, or joins</li>
         </ul>
         <p style="color: var(--text-secondary); margin-top: 24px;">
-          In those cases, consider Redis, SQLite, PostgreSQL, MongoDB, or <a href="https://github.com/patx/mongokv">mongoKV</a>.
+          In those cases, consider Redis, PostgreSQL, MongoDB, or <a href="https://github.com/patx/mongokv">mongoKV</a>.
         </p>
       </div>
     </section>
@@ -1407,4 +1451,3 @@ time.sleep(<span class="number">3</span>)
 
 </body>
 </html>
-