<?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: Egzakutacno</title>
    <description>The latest articles on DEV Community by Egzakutacno (@egzakutacno).</description>
    <link>https://dev.to/egzakutacno</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%2F4105271%2Ff4794a7c-f659-4111-ab38-fa9342c97872.jpg</url>
      <title>DEV Community: Egzakutacno</title>
      <link>https://dev.to/egzakutacno</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/egzakutacno"/>
    <language>en</language>
    <item>
      <title>What 6.8M job ads taught us about running search on a small machine</title>
      <dc:creator>Egzakutacno</dc:creator>
      <pubDate>Tue, 15 Sep 2026 10:17:47 +0000</pubDate>
      <link>https://dev.to/egzakutacno/what-68m-job-ads-taught-us-about-running-search-on-a-small-machine-2g54</link>
      <guid>https://dev.to/egzakutacno/what-68m-job-ads-taught-us-about-running-search-on-a-small-machine-2g54</guid>
      <description>&lt;p&gt;We run &lt;a href="https://deunemployment.com/" rel="noopener noreferrer"&gt;DeUnemployment&lt;/a&gt;, a free job search over 6,811,344 indexed&lt;br&gt;
postings, 3,588,091 of them with full descriptions. It runs on one 4-vCPU VPS with 8 GB of RAM.&lt;br&gt;
That is not where this kind of thing usually gets built, and the constraint decided most of what&lt;br&gt;
follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first assumption was wrong
&lt;/h2&gt;

&lt;p&gt;We assumed the database would be the bottleneck. It wasn't. Profiling a single query put the CPU at&lt;br&gt;
246% — the embedding model and the dot products were the entire cost. Storage and SQL were nowhere&lt;br&gt;
near the top of the profile.&lt;/p&gt;

&lt;p&gt;The fixes were unglamorous. A warm cache. Keeping garbage collection off the response path.&lt;br&gt;
Separating workers so they stop competing. A warm query now returns in 0.0s; a cold one takes&lt;br&gt;
7–12.5s.&lt;/p&gt;

&lt;p&gt;The more interesting part is what we did not build. We priced a real inverted index: 84 MB of extra&lt;br&gt;
state, two to three days of build, and 0.1–0.3s saved per warm query. On a large machine that trade&lt;br&gt;
might be obvious. On ours the memory and the operational surface were not worth a third of a second,&lt;br&gt;
so we skipped it and spent the effort on the cache instead. If your index is the bottleneck, build&lt;br&gt;
the index. Ours was not.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Still listed" is not "still open"
&lt;/h2&gt;

&lt;p&gt;We measured listing age, not liveness. Two numbers from the September snapshot are worth keeping&lt;br&gt;
apart: 29.8% of postings carried a source-reported publish date older than 90 days, and 50.79% of&lt;br&gt;
those same postings were seen again at the source within the previous week.&lt;/p&gt;

&lt;p&gt;Older is not automatically dead. A hard-to-fill requisition, a pipelined role, a slow approval chain&lt;br&gt;
and a listing somebody forgot to close look identical from the outside. We can measure age. We cannot&lt;br&gt;
measure intent, so we label it "still listed" rather than "open", and we publish the definitions next&lt;br&gt;
to the numbers (&lt;a href="https://deunemployment.com/about-data" rel="noopener noreferrer"&gt;methodology&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What pay does to shelf life
&lt;/h2&gt;

&lt;p&gt;Across 6,564,432 sampled postings, the 812,943 that state any pay figure have a median listing age of&lt;br&gt;
22 days, against 27 days for the 5,751,489 that don't, and a lower stale-90 share (25.7% vs 28.27%).&lt;br&gt;
Only 12.4% of postings state pay at all. This is a correlation measured on a minority of the corpus,&lt;br&gt;
not a causal claim, and we say so where we publish it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The crawlers found us before the users did
&lt;/h2&gt;

&lt;p&gt;The index went public and the AI crawlers arrived the same day. Not a trickle. ClaudeBot alone has&lt;br&gt;
105,670 requests on disk, 4,907 of them today; Googlebot 10,409; Applebot 5,181. Eleven distinct&lt;br&gt;
crawlers in total.&lt;/p&gt;

&lt;p&gt;On a 4-core box this is not a traffic story, it is a resources story: a crawler walking every &lt;code&gt;/job/&lt;/code&gt;&lt;br&gt;
URL holds the CPU that a person's search needs.&lt;/p&gt;

&lt;p&gt;The rule is one line long, and we have not made an exception to it: an indexer can wait, an assistant&lt;br&gt;
answering a person cannot. Throttling is therefore per crawler (not per request), applied only to bulk&lt;br&gt;
indexers and only on &lt;code&gt;/job/&lt;/code&gt; pages, as a token bucket with a burst of 10 so a genuine re-visit is never&lt;br&gt;
shed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;crawler&lt;/th&gt;
&lt;th&gt;allowed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ClaudeBot&lt;/td&gt;
&lt;td&gt;0.2 req/s (12/min)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CCBot&lt;/td&gt;
&lt;td&gt;0.3 req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazonbot, Meta&lt;/td&gt;
&lt;td&gt;0.5 req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bytespider&lt;/td&gt;
&lt;td&gt;0.1 req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nothing is ever blocked. A shed request gets &lt;code&gt;429&lt;/code&gt; with a &lt;code&gt;Retry-After&lt;/code&gt; and the crawler comes back&lt;br&gt;
later. Googlebot, Bingbot, Applebot and every question-answering agent — OAI-SearchBot, PerplexityBot,&lt;br&gt;
ChatGPT-User, Claude-SearchBot, Gemini — are exempt by name, because slowing them down is slowing our&lt;br&gt;
own discovery. Claude-SearchBot spent one day inside the throttled table by mistake, which would have&lt;br&gt;
put a 429 in front of the one Anthropic agent that sends us readers; that is what the exemption list is&lt;br&gt;
for. ClaudeBot also gets &lt;code&gt;Crawl-delay: 3&lt;/code&gt; in robots.txt, and behind all of it there is a load-shed:&lt;br&gt;
when more than 6 cold SEO renders are in flight at once, bulk crawlers are answered with &lt;code&gt;503&lt;/code&gt; instead&lt;br&gt;
of queueing in front of a person's search.&lt;/p&gt;

&lt;p&gt;The numbers moved more than we expected. Pages served to crawlers went from 644/min to 49/min,&lt;br&gt;
process memory from 3,885 MB to 765 MB, 69 crawler requests shed in total — and no 429 for anyone&lt;br&gt;
actually looking for a job.&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%2F0xzxuibxdfjpn2z4lrxy.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%2F0xzxuibxdfjpn2z4lrxy.png" alt="AI crawlers, last 14 days: 125,723 crawler hits on disk, 12,056 today, 11 distinct crawlers, 69 throttled" width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Five ways into the same corpus
&lt;/h2&gt;

&lt;p&gt;The web search is the obvious door, but it is not the only one, and the others exist because people&lt;br&gt;
kept asking for them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The site.&lt;/strong&gt; Plain-language search, no account. Anonymous traffic gets a small number of cold
searches per minute; cache hits are not counted against it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A chat agent on the site.&lt;/strong&gt; Ask for a job in words and it searches the same index and answers with
postings. Rate-limited, for the same reason as everything else here: an assistant that is polite
enough to wait beats one that takes the index down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The public API.&lt;/strong&gt; &lt;code&gt;GET /api/public/search?q=…&amp;amp;limit=10&lt;/code&gt;, JSON, no key, 30 requests a minute per IP.
Same index as the site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An MCP server.&lt;/strong&gt; Keyless, so an assistant that speaks MCP can query the corpus instead of guessing
at it. Rate-limited like the rest of the public surface, because the alternative is an index that
stops answering for everyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A local clone.&lt;/strong&gt; The companion repo is written for agents: you point your agent at it, answer a few
questions about the job you want, and it embeds that description, pulls the nearest groups of the
corpus onto your disk and builds a self-contained search page. Search then runs on your own machine,
and your notes and résumé stay there. The data endpoints need an access key, so this route is gated
on purpose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a browser extension for filling application forms, still in development, so I will not&lt;br&gt;
link it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the size of the corpus matters more than anything clever
&lt;/h2&gt;

&lt;p&gt;The site is free, with no ads and no accounts, because the search is not the whole point. The point is&lt;br&gt;
the dataset underneath it: 82 countries, monthly snapshots, published as JSON and CSV under CC BY 4.0&lt;br&gt;
at &lt;a href="https://deunemployment.com/report" rel="noopener noreferrer"&gt;deunemployment.com/report&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our target is a corpus of 50M+ postings. Not for the search, which does not need it — for the&lt;br&gt;
cross-tabs. At 6.8M rows a question like "does stated pay shorten a listing, by country and by source"&lt;br&gt;
is still noisy. More rows is how that becomes answerable, and answerable is the only reason we keep&lt;br&gt;
crawling. Every number we publish stays tied to its definition and its date.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would tell someone starting here
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Measure before you architect. The bottleneck was not where we assumed, and it took a profiler, not
an opinion, to find out.&lt;/li&gt;
&lt;li&gt;Publish the definition next to the metric. The definition is the part that makes the number usable.&lt;/li&gt;
&lt;li&gt;Spend the budget where the users are. We throttle indexers, not assistants: an indexer that gets a
429 comes back tomorrow, a person asking a question does not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can search the corpus at &lt;a href="https://deunemployment.com/jobs" rel="noopener noreferrer"&gt;deunemployment.com/jobs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>performance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>got tired of the same recycled job ads, so I built a search that pulls 2M+ live postings daily straight from ATS feeds</title>
      <dc:creator>Egzakutacno</dc:creator>
      <pubDate>Wed, 02 Sep 2026 02:06:29 +0000</pubDate>
      <link>https://dev.to/egzakutacno/got-tired-of-the-same-recycled-job-ads-so-i-built-a-search-that-pulls-2m-live-postings-daily-726</link>
      <guid>https://dev.to/egzakutacno/got-tired-of-the-same-recycled-job-ads-so-i-built-a-search-that-pulls-2m-live-postings-daily-726</guid>
      <description>&lt;p&gt;i've been job hunting on and off for months and I kept running into the same problem: the big job boards show the same ~20 promoted posts over and over, half the listings are dead or reposted from six months ago, and half of what's left goes through some recruiter who never replies.&lt;br&gt;
So I built my own. It ingests from the open-jobs dataset, which grabs postings directly from ATS feeds — Workday, Greenhouse, Lever, Ashby, etc. Right now it's indexing about 2.2M live postings, refreshed daily. Apply link always goes to the original employer's posting, no middlemen.&lt;/p&gt;

&lt;p&gt;i'm proud of i'ts search. You can type a plain keyword like "truck driver" and it just works. But it also handles semantic input ; paste something like "ICU nurse for 6 years, want to move to Texas" and it matches by meaning, not just keywords. Full job descriptions and salary info where available.&lt;/p&gt;

&lt;p&gt;No sign-up, no email gate, no recruiters inboxing you. NO ADS &lt;a href="https://deunemployment.com" rel="noopener noreferrer"&gt;https://deunemployment.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;p.s. there is sign-up for features like quiz, which throws 15-25 cards at you, and you just click left or right to get a better grasp of what kind of jobs you like, and other backend AI stuff, but i wont bore you here. &lt;/p&gt;

&lt;p&gt;hope you find yourself a job :D&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
