<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Albert Maharjan</title>
    <description>The latest articles on DEV Community by Albert Maharjan (@albert_maharjan_8f98ad7b9).</description>
    <link>https://dev.to/albert_maharjan_8f98ad7b9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4028137%2F884ca9f2-89b7-4373-af56-15b0c2712d95.jpg</url>
      <title>DEV Community: Albert Maharjan</title>
      <link>https://dev.to/albert_maharjan_8f98ad7b9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/albert_maharjan_8f98ad7b9"/>
    <language>en</language>
    <item>
      <title>Scaling "Sage": Building Nepal's First Home-Grown Anti-Plagiarism System</title>
      <dc:creator>Albert Maharjan</dc:creator>
      <pubDate>Tue, 14 Jul 2026 06:39:34 +0000</pubDate>
      <link>https://dev.to/albert_maharjan_8f98ad7b9/scaling-sage-building-nepals-first-home-grown-anti-plagiarism-system-4c21</link>
      <guid>https://dev.to/albert_maharjan_8f98ad7b9/scaling-sage-building-nepals-first-home-grown-anti-plagiarism-system-4c21</guid>
      <description>&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; 2026-07-14&lt;br&gt;
&lt;strong&gt;Project:&lt;/strong&gt; Sage Anti-Plagiarism System, Nepal's first in-house anti-plagiarism project, built at Softwarica College of IT &amp;amp; E-Commerce for academic integrity.&lt;br&gt;
&lt;strong&gt;System:&lt;/strong&gt; plagiarism similarity search over a ~48.8M-sentence embedding corpus&lt;br&gt;
&lt;strong&gt;Vector engine:&lt;/strong&gt; Qdrant 1.18.2 (384-dim embeddings, cosine distance, 8 shards, 52 segments)&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Over one year, report generation went from 62.5 s to 4.67 s (13.4× faster) while RAM fell from 40–60 GB to 11 GB. The decisive release (v4) put a 4-bit-quantized index and the navigation graph in RAM at the same time, so it got faster and lighter together. A three-regime load test then found a ceiling of about 1,300 queries/second, degrading gracefully with zero errors at 10,000 concurrent queries, and about 90 ms of real-user latency for a 100-sentence submission. The 4-bit compression costs almost nothing: 99% verdict agreement and 0.3% score error versus full precision (§4).&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvf2byasiazowy1qr5q6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvf2byasiazowy1qr5q6c.png" alt="Sage System" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Objective
&lt;/h2&gt;

&lt;p&gt;Two questions about the production similarity-search backend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How hard can it be pushed? Find the throughput ceiling and confirm it degrades gracefully rather than failing under load.&lt;/li&gt;
&lt;li&gt;What does a real user wait? Measure the search latency of one submission with no contention.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The report also records how the system reached its current performance over the past year, so the stress-test numbers can be read against that trajectory.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. One year of optimization evolution
&lt;/h2&gt;

&lt;p&gt;Benchmarked on a fixed reference submission of 508 sentence embeddings (4,495 words), measuring end-to-end plagiarism-report time.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Key change&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;Report time&lt;/th&gt;
&lt;th&gt;Speedup vs v2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jul 28, 2025&lt;/td&gt;
&lt;td&gt;Migrated Milvus to Qdrant&lt;/td&gt;
&lt;td&gt;40 GB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;62.53 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.0×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mar 26, 2026&lt;/td&gt;
&lt;td&gt;8-shard collection&lt;/td&gt;
&lt;td&gt;60 GB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37.41 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.7×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jun 10, 2026&lt;/td&gt;
&lt;td&gt;TurboQuant 4-bit + HNSW graph in RAM&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5.01 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jul 13, 2026&lt;/td&gt;
&lt;td&gt;Parallelized search operation&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.67 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13.4×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v2 to v3:&lt;/strong&gt; 8 shards let queries fan out in parallel and cut report time about 40%, but holding more index resident pushed RAM to 60 GB, the wrong direction for scaling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v3 to v4:&lt;/strong&gt; compressing vectors to 4 bits and pinning both the vectors and the navigation graph in RAM gave a 7.5× speedup and an 82% RAM reduction together (60 GB to 11 GB). Architecture in §3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v4 to v5:&lt;/strong&gt; parallelizing search removed the last 7% at no RAM cost. This stress test exercises the search component that v5 parallelized. It measures that component's throughput and latency, not full end-to-end report time (scope in §5).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Read the trajectory as a real-world improvement record, not a controlled ablation.&lt;/strong&gt; (1) v4 changed two things at once, 4-bit quantization and graph-in-RAM, so no single release's gain is attributable to one change. (2) The corpus grew over the year, so the 13.4× figure blends optimization with a changing data baseline.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Current architecture: TurboQuant and HNSW-in-RAM together
&lt;/h2&gt;

&lt;p&gt;At full precision the vectors are too large to hold in RAM. The v4/v5 design makes search fast anyway by running two optimizations at once, arranged as three memory tiers. (&lt;a href="https://qdrant.tech/articles/turboquant-quantization/" rel="noopener noreferrer"&gt;TurboQuant&lt;/a&gt; is a rotation-based, training-free quantization shipped in Qdrant 1.18; 4-bit gives 8× compression at 1–2 pp recall versus scalar quant.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tier 1, navigation graph, in RAM.&lt;/strong&gt; A hierarchical proximity graph (32 edges per node, built with a 512-neighbour construction pass) decides which vectors to examine. Candidate selection is a pure in-memory operation with no disk access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 2, 4-bit vectors, in RAM.&lt;/strong&gt; Every vector is compressed from 32-bit to 4 bits per dimension and pinned in RAM, then used to score candidates during traversal. These are fast approximate distances with no disk on the hot path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 3, full-precision vectors, on disk.&lt;/strong&gt; The exact 32-bit vectors are read only to rescore the handful of top candidates, restoring full accuracy without paying RAM for them.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4-bit:   48.8M × 384 × 4 bits   ≈  9.4 GB  →  ~10 GB resident
f32:     48.8M × 384 × 4 bytes  ≈  75 GB   (84.8 GB measured on disk, incl. overhead)
                                          ─────────────
                                          ~8× smaller in RAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why both, not either
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;TurboQuant only&lt;/th&gt;
&lt;th&gt;HNSW-in-RAM only&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Both (current)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Choose candidates&lt;/td&gt;
&lt;td&gt;graph on disk, slow&lt;/td&gt;
&lt;td&gt;RAM, fast&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;RAM, fast&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Score candidates&lt;/td&gt;
&lt;td&gt;RAM (4-bit), fast&lt;/td&gt;
&lt;td&gt;full vectors on disk, slow&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;RAM (4-bit), fast&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final accuracy&lt;/td&gt;
&lt;td&gt;approximate&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;full (disk rescore)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM footprint&lt;/td&gt;
&lt;td&gt;vectors + graph on disk&lt;/td&gt;
&lt;td&gt;graph + ~85 GB (won't fit)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~11.5 GB total&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The whole hot loop, candidate selection and candidate scoring, runs in RAM inside about 11.5 GB, while the bulky exact vectors stay on disk and are touched only for the final rescore. That is what let v4 be 7.5× faster and 5.5× lighter than v3 at the same time. If either tier lived on disk, the hot loop would hit disk on every query and latency would be much worse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live memory footprint
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Disk&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vectors&lt;/td&gt;
&lt;td&gt;84.8 GB (full precision)&lt;/td&gt;
&lt;td&gt;10.0 GB (4-bit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index / id tracking&lt;/td&gt;
&lt;td&gt;~1.0 GB&lt;/td&gt;
&lt;td&gt;~1.7 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload&lt;/td&gt;
&lt;td&gt;1.7 GB&lt;/td&gt;
&lt;td&gt;negligible (on disk)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87.4 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~11.5 GB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 4-bit approximation's accuracy is validated separately in §4 (99% verdict agreement, 0.3% score error), so the compression is effectively free at the 0.80 threshold.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Search quality: does 4-bit quantization hurt accuracy?
&lt;/h2&gt;

&lt;p&gt;A compressed search is only useful if it returns the right matches. The 4-bit retrieval quality was measured against an f32 near-exact ground truth: the same in-RAM graph searched with quantization ignored at deep breadth (ef 256, 4× production's ef 64), on the live collection, read-only. 100 sampled real queries, &lt;code&gt;top_k&lt;/code&gt; up to 10.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The right metric.&lt;/strong&gt; Plagiarism detection does not depend on which exact corpus point is returned. The corpus is dense with near-duplicate sentences, so many points sit at nearly identical similarity. What matters is whether the similarity score is preserved and whether it crosses the 0.80 cosine threshold. Cosine 0.998 and 0.999 give the same verdict, and which one wins is irrelevant.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Threshold agreement (same flag @ 0.80)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.99&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same plagiarism verdict on 99% of queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Top-1 similarity error (mean |Δcosine|)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0033&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Best-match score within 0.3% of exact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prod's #1 is a true top-10 neighbour&lt;/td&gt;
&lt;td&gt;0.89&lt;/td&gt;
&lt;td&gt;Genuinely close match about 9 times in 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;recall@1 (exact same point id)&lt;/td&gt;
&lt;td&gt;0.52&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Confounded by ties, see below&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;recall@10 (id set overlap)&lt;/td&gt;
&lt;td&gt;0.74&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Confounded by ties, see below&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Strict ID recall is misleading here.&lt;/strong&gt; recall@1 = 0.52 looks low, but it is a near-duplicate measurement artifact. When the true neighbour is at 0.9991 and the runner-up at 0.9989, 4-bit resolution cannot separate them, so it returns the "wrong" id while returning a point that is equally similar. The two report-governing metrics, 99% threshold agreement and 0.3% score error, are the ones that matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion: the 4-bit compression is effectively free for this workload.&lt;/strong&gt; The v4 speed-and-memory win costs no meaningful accuracy at the 0.80 threshold.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Scope.&lt;/strong&gt; Measured on vector-search top-1 agreement as a proxy for the production recommend-by-id operation (same index, same quantization). The ground truth is near-exact (deep-ef graph, not a true full scan), so it may marginally overstate agreement, though at 99% and 0.3% the margin is not decision-relevant. Recall was not re-measured across submission types or thresholds other than 0.80.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Methodology
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scope.&lt;/strong&gt; This test isolates the vector-search operation only, not full report generation (which also downloads and converts the file, splits sentences, embeds, inserts, runs AI-text detection, and runs a reverse check). Search is about 0.39 s of the 4.67 s total (§7), so these numbers bound a sub-10% slice of end-to-end time: the search tier's capacity, not the whole pipeline.&lt;/p&gt;

&lt;p&gt;The load test drives the exact production search operation, a batched "find the most similar sentences to these" request. Each request carries real sentence identifiers drawn from the live corpus and asks for the single nearest match per sentence, identical in shape to how a submission is scored.&lt;/p&gt;

&lt;p&gt;The harness:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Samples real sentence identifiers into a reuse pool. The recommend-style query needs existing corpus points, not synthetic vectors.&lt;/li&gt;
&lt;li&gt;Issues batched search requests, each covering a configurable batch. One request equals one submission-shaped scoring call.&lt;/li&gt;
&lt;li&gt;Runs requests concurrently across a configurable worker count.&lt;/li&gt;
&lt;li&gt;Records per-request latency and reports throughput two ways, requests/second and queries/second (requests × batch), plus p50/p95/p99/max.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Workers&lt;/th&gt;
&lt;th&gt;Batch (sentences/req)&lt;/th&gt;
&lt;th&gt;Total requests&lt;/th&gt;
&lt;th&gt;Intent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;True single-submission latency, no contention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;Moderate concurrent load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;Deliberate overload&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Limitations.&lt;/strong&gt; Each config ran once, with no repeats and no warm-up, so cold vs warm-cache state is uncontrolled and single-sample p99/max should be read as indicative, not precise. Numbers reflect this host and corpus state on the test date.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workers&lt;/th&gt;
&lt;th&gt;Batch&lt;/th&gt;
&lt;th&gt;Queries/s&lt;/th&gt;
&lt;th&gt;Requests/s&lt;/th&gt;
&lt;th&gt;p50/req&lt;/th&gt;
&lt;th&gt;p95&lt;/th&gt;
&lt;th&gt;p99&lt;/th&gt;
&lt;th&gt;max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1055&lt;/td&gt;
&lt;td&gt;10.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;200 ms&lt;/td&gt;
&lt;td&gt;200 ms&lt;/td&gt;
&lt;td&gt;200 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;1295&lt;/td&gt;
&lt;td&gt;40.5&lt;/td&gt;
&lt;td&gt;1319 ms&lt;/td&gt;
&lt;td&gt;1645 ms&lt;/td&gt;
&lt;td&gt;1945 ms&lt;/td&gt;
&lt;td&gt;2311 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1339&lt;/td&gt;
&lt;td&gt;13.4&lt;/td&gt;
&lt;td&gt;7358 ms&lt;/td&gt;
&lt;td&gt;8276 ms&lt;/td&gt;
&lt;td&gt;9135 ms&lt;/td&gt;
&lt;td&gt;10090 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero errors across all runs, including 10,000 concurrent queries in flight (run 3). The collection stayed healthy throughout.&lt;/p&gt;

&lt;p&gt;The throughput ceiling is about 1,300 queries/second. It stays flat (1295 to 1339) while workers and batch swing 2 to 3×, which is the mark of a hard server-side capacity wall. Per-query cost is 1/1300, about 0.77 ms.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Analysis
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Saturation-bound.&lt;/strong&gt; Past the ceiling, throughput holds constant while latency grows linearly with concurrency, the behaviour of a saturated queue. Little's Law (throughput ≈ concurrency ÷ mean latency) holds on every run:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Predicted req/s&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;55 × 32&lt;/td&gt;
&lt;td&gt;55 ÷ 1.298 s&lt;/td&gt;
&lt;td&gt;42.4&lt;/td&gt;
&lt;td&gt;40.5 ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 × 100&lt;/td&gt;
&lt;td&gt;100 ÷ 6.926 s&lt;/td&gt;
&lt;td&gt;14.4&lt;/td&gt;
&lt;td&gt;13.4 ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The agreement confirms the server is the bottleneck, not the client and not the network. Adding workers past the ceiling buys no throughput, only queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency is driven by submission size, then by backlog.&lt;/strong&gt; At a fixed 0.77 ms/query, submission latency = (sentence count × 0.77 ms) + queue wait.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 sentences, no contention: 77 ms + overhead, about 90 ms measured ✓.&lt;/li&gt;
&lt;li&gt;508-sentence reference: about 0.39 s of pure search, a small slice of the 4.67 s report.&lt;/li&gt;
&lt;li&gt;Run 3's 7.4 s p50 is not "a submission takes 7 s". It is 100 simultaneous 100-sentence submissions (10,000 queries) draining through a 1,300/s pipe, so about 10,000 ÷ 1,300 ≈ 7.5 s to clear the batch. Self-inflicted overload, not a real scenario.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Degradation is safe.&lt;/strong&gt; No errors, timeouts, or crashes from 1 to 10,000 concurrent queries. The system slows down but never fails. RAM stays flat under load: the hot tiers are fixed-size and pinned, so concurrency spends CPU, search threads, and the disk-rescore path, not memory. This is why v5's parallelization added throughput without moving the 11 GB footprint.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Capacity model and recommendations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Capacity:&lt;/strong&gt; about 1,300 queries/second. Divide by average submission size for the sustainable rate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Avg submission size&lt;/th&gt;
&lt;th&gt;Sustained submissions/s before queueing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50 sentences&lt;/td&gt;
&lt;td&gt;~26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 sentences&lt;/td&gt;
&lt;td&gt;~13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;508 sentences (reference)&lt;/td&gt;
&lt;td&gt;~2.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Latency:&lt;/strong&gt; about 0.77 ms/sentence plus queue. Sub-100 ms for a 100-sentence document until sustained load nears the ceiling, then linear growth with backlog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operating guidance:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real traffic is almost certainly far below 1,300 q/s, so no action is needed today.&lt;/li&gt;
&lt;li&gt;Keep sustained concurrency where p99 stays acceptable. The 55-worker point was already into queue territory (p99 about 1.9 s).&lt;/li&gt;
&lt;li&gt;If large-submission latency ever hurts UX and the server is not saturated, split one submission into several concurrent smaller requests. Same server cost, lower wall-clock.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Levers to raise the ceiling&lt;/strong&gt; (the cheap wins, quantization, HNSW-in-RAM, sharding, and parallelization, are already spent):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cut disk-rescore cost.&lt;/strong&gt; The one remaining per-query disk touch. Faster storage, or tuning rescore oversampling (fewer f32 reads per query for a small recall trade), is the highest-impact lever now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale out.&lt;/strong&gt; Add replicas or a second node. Replication is currently single-copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrow search breadth.&lt;/strong&gt; Fewer candidates per query: faster, small recall cost, no re-indexing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More RAM.&lt;/strong&gt; Holding f32 vectors resident would remove the rescore hit, but about 85 GB will not fit the 11.5 GB envelope. That is a hardware decision, not a tuning one.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>qdrant</category>
      <category>plagiarism</category>
      <category>database</category>
    </item>
  </channel>
</rss>
