<?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: Basu</title>
    <description>The latest articles on DEV Community by Basu (@bsknath).</description>
    <link>https://dev.to/bsknath</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%2F489956%2F23cc1517-361b-4061-b82b-7f3863449d35.png</url>
      <title>DEV Community: Basu</title>
      <link>https://dev.to/bsknath</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bsknath"/>
    <language>en</language>
    <item>
      <title>Why I Killed Our Kafka Cluster (And What I'd Do Differently)</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Wed, 05 Aug 2026 18:35:00 +0000</pubDate>
      <link>https://dev.to/bsknath/why-i-killed-our-kafka-cluster-and-what-id-do-differently-4nn</link>
      <guid>https://dev.to/bsknath/why-i-killed-our-kafka-cluster-and-what-id-do-differently-4nn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR: We ran Kafka in a payments company for 3 years. Most of our 180 topics were glorified queues. We moved to SNS/SQS in 5 months. Here's the real story, including the parts I got wrong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used to think running your own Kafka cluster was a sign of engineering maturity. Like your team had graduated to "real" distributed systems. Then I spent two years operating one inside a payments company with a six-person platform team and realized we hadn't graduated to anything — we'd just volunteered for operational overhead we didn't need.&lt;/p&gt;

&lt;p&gt;The moment it became undeniable was a Friday morning call with our acquiring bank's technical team, where I had to explain how a slow disk caused 12,000 duplicate transactions in their settlement file. Me on mute half the time, trying to figure out how to say "our messaging infrastructure isn't operated at the level your business requires" without actually saying that. That's when I decided Kafka had to go.&lt;/p&gt;

&lt;p&gt;What I Walked Into&lt;br&gt;
Kafka was already there when I joined the payments platform team. I tried to find out why it was chosen. Got nowhere. There was a Confluence page from 2019, "Event-Driven Architecture — Proposed," authored by someone who'd left a year and a half before I showed up. Linked to a dead Slack channel. The page had four views. I'm pretty sure two were me.&lt;/p&gt;

&lt;p&gt;The setup: 9 brokers on r5.2xlarge, running Kafka 2.7 (two minor versions behind because upgrading meant regression-testing consumers and nobody wanted to own that risk). Three t3.medium ZooKeeper nodes. A Schema Registry instance. Somewhere around 170-180 topics, I'd have to check my spreadsheet for the exact count. Retention was 7 days on most, 30 on a handful of others. Never found documentation on why some got 30 days. I suspect whoever set it up just picked different numbers for different topics based on vibes.&lt;/p&gt;

&lt;p&gt;Everything else in our stack was AWS-native. ECS, DynamoDB, S3, CloudWatch, IAM. And then Kafka, running on EC2 with its own access model, its own monitoring (JMX exporters into Grafana — I think three people looked at those dashboards regularly, and one of them was me), its own operational playbook that was partly in a wiki and partly in someone's head.&lt;/p&gt;

&lt;p&gt;Our consumers were all Java, built on spring-kafka. The framework was fine honestly — spring-kafka is mature and the abstractions are reasonable. The problem was never the client library. It was everything underneath it.&lt;/p&gt;

&lt;p&gt;What Broke&lt;br&gt;
Three incidents in five months. I wasn't looking for a migration project. The migration found me.&lt;/p&gt;

&lt;p&gt;Settlement duplicates, March. Settlement service consumes auth.completed to build end-of-day files for acquiring banks. Broker-6 in us-east-1c hit EBS I/O latency degradation on a Thursday evening. Not a failure — degradation. Triggered partition reassignment, which triggered consumer group rebalance, which led to roughly 12,000 auth events getting processed twice. Settlement file went out with duplicates. Bank caught it during their morning reconciliation before we did.&lt;/p&gt;

&lt;p&gt;The Friday call I mentioned. Wouldn't recommend it.&lt;/p&gt;

&lt;p&gt;This is the thing about Kafka's consumer group rebalancing — it's probably the worst operational aspect of the entire system. The protocol is complex, the failure modes are subtle, and when it goes wrong in a payments context, the consequences are financial. I've used Kafka in non-payments contexts where a rebalance causing a few duplicate processings is a non-event. Here it meant calling a bank.&lt;/p&gt;

&lt;p&gt;Fraud scoring gap, May. Fraud service subscribes to transaction.initiated, needs to respond within 200ms or auth falls through to default-allow. During a routine broker restart, partition leadership migrated. Consumers took about 90 seconds to reconnect. Transactions in that window went through unscored.&lt;/p&gt;

&lt;p&gt;Nothing bad happened. I put "we got lucky" in the incident report because I wanted that phrase on the record for when someone reviewed our risk posture.&lt;/p&gt;

&lt;p&gt;Reconciliation false positives, ongoing. Recon service consumed from two topics with different partition counts and consumer group configs. During rebalances it'd occasionally miss events, producing phantom mismatches. Ops team investigated each one manually — 20 to 40 minutes each, happening maybe 15-20 times a week. Priya from SRE brought it up in two consecutive retros. She was right both times and I should have moved faster after the first.&lt;/p&gt;

&lt;p&gt;The Audit That Made the Decision&lt;br&gt;
Blocked two weeks. Pulled consumer group configs, measured actual throughput, traced ordering dependencies topic by topic. Built a spreadsheet (Google Sheets, ugly, columns like "topic / peak msg/sec / consumers / ordering needed? / idempotent? / what breaks if delayed 5 min?").&lt;/p&gt;

&lt;p&gt;Most of our topics were fan-out notifications or work queues. Webhook delivery, emails, PDFs, audit logging, report generation. They needed reliable delivery. They didn't need a distributed commit log.&lt;/p&gt;

&lt;p&gt;Topics that genuinely needed strict ordering: ledger.entry.created (per-account — can't apply a debit before a preceding credit), auth.completed to settlement (per-merchant), dispute.state.changed (per-dispute lifecycle). Three.&lt;/p&gt;

&lt;p&gt;I'll be honest though: "genuinely needed" is doing some work in that sentence. I validated ordering requirements against normal operating conditions. I did not exhaustively validate every edge case — month-end batch windows, reconciliation sweeps, the weird quarterly processor settlement that runs differently from daily ones. I'll come back to this because it bit me later.&lt;/p&gt;

&lt;p&gt;There was also a whole bunch of topics with zero active consumers. Created during some experiment in 2020, never cleaned up. I deleted those first. Nobody noticed for three weeks, which tells you how much they mattered.&lt;/p&gt;

&lt;p&gt;Getting It Approved&lt;br&gt;
Architecture review. I showed the three incidents, showed on-call data (pulled 2.3 Kafka-related pages per week from PagerDuty, averaged over six months), showed the bus factor (two people could debug the cluster — me and one other engineer), showed that managed alternatives already existed inside the AWS ecosystem we were paying for.&lt;/p&gt;

&lt;p&gt;Framed it as operational risk, not technology preference. Didn't say "Kafka is bad." Said "we don't have the team to run Kafka at the level payments requires, and we don't need most of what Kafka offers."&lt;/p&gt;

&lt;p&gt;Director's concern was execution risk. Fair question. Our compliance lead, Meera, also asked whether SQS met our data residency requirements — single region, data doesn't leave the AWS region boundary. It did. But I hadn't thought to check before she asked, which is slightly embarrassing in retrospect. Payments infra changes always have a compliance dimension and I'd been thinking purely in terms of engineering.&lt;/p&gt;

&lt;p&gt;I committed to phased migration, topic-by-topic, zero transaction impact. Approved. PAY-4127. I typed that epic number into Slack probably 200 times over the next five months. Actually looking back, I think we also had a sub-epic for just the monitoring work but I can't remember the number.&lt;/p&gt;

&lt;p&gt;Ravi on the fraud team pushed back. He'd built a solid consumer framework: offset management, rebalance listeners, retry logic, graceful shutdown. Months of work, well-tested. I talked to him separately, not in the meeting. Told him the code was good but the reason it needed to exist was the problem. If the managed service provides retries and DLQs natively, a framework compensating for their absence is a signal you're fighting the wrong abstraction.&lt;/p&gt;

&lt;p&gt;He disagreed. Said we'd lose flexibility. I said we'd lose complexity that we'd been calling flexibility to justify maintaining it.&lt;/p&gt;

&lt;p&gt;He came around about three weeks later when he saw the SQS consumer code — maybe 40 lines, no rebalance handling. The code diff convinced him more than anything I said.&lt;/p&gt;

&lt;p&gt;How We Did It&lt;br&gt;
Early mistake I caught: I initially proposed Kinesis for ordered topics. My tech lead pointed out Kinesis has shard management, which is operationally the same class of problem as Kafka partitions. He was right. SQS FIFO queues instead.&lt;/p&gt;

&lt;p&gt;Our deploy pipeline ("Piper" internally, not sure that was ever its official name) needed per-topic feature flags for dual-publishing. Spent three days building that:&lt;/p&gt;

&lt;h1&gt;
  
  
  dual-publish config - per topic toggle
&lt;/h1&gt;

&lt;p&gt;messaging:&lt;br&gt;
  dual_publish:&lt;br&gt;
    enabled_topics:&lt;br&gt;
      - auth.completed&lt;br&gt;
      - merchant.updated&lt;br&gt;
    disabled_topics:&lt;br&gt;
      - ledger.entry.created  # not yet, ordered topic&lt;br&gt;
  target:&lt;br&gt;
    sns_arn_prefix: arn:aws:sns:us-east-1:XXXX:&lt;br&gt;
Unglamorous but it meant we could toggle per topic without redeploying. That investment paid for itself immediately.&lt;/p&gt;

&lt;p&gt;January through early March — notifications and async. Webhooks, emails, PDFs, audit fan-out. Idempotent consumers, fire-and-forget semantics. SNS fan-out to SQS subscribers. About a week of dual-publishing per topic, output comparison, cutover. Some of these were trivially easy and I probably over-engineered the verification for the first couple, but I was still building confidence in the process.&lt;/p&gt;

&lt;p&gt;DLQ visibility was an immediate win. Failed webhooks (merchant endpoint down) used to cause consumer lag that mixed with healthy messages. Now failed messages land in DLQ, CloudWatch alarm fires, clean messages keep flowing. The ops team's weekly false positive investigations from these topics went to zero. Priya sent me a Slack message: "thank you." No context needed.&lt;/p&gt;

&lt;p&gt;March through April — work queues. Report generation, settlement file building, chargeback docs. The settlement builder was the one where I ran shadow mode for 21 days comparing output byte-for-byte before cutting over. Maybe paranoid. Don't care. I wasn't having that Friday call again.&lt;/p&gt;

&lt;p&gt;Schema incident hit mid-March. This one still annoys me because I'd specifically written it down during planning — "need to handle schema governance without Schema Registry" — and then somehow convinced myself it wouldn't be urgent because "producers won't push breaking changes during migration." Dumb assumption. A producer team deployed a breaking change to merchant.updated, SQS consumer couldn't deserialize, messages piled into DLQ, merchant notifications down for 40 minutes. The Slack thread was not great.&lt;/p&gt;

&lt;p&gt;Four days building a JSON Schema validation layer at consumer ingress. Added a CI check on producer repos that validates against a contract file before deploy. It's not as good as Schema Registry (no automatic compatibility negotiation, no centralized schema evolution tracking) and I'm still not fully happy with it. That's a problem I've deferred, not solved. But it stops the bleeding.&lt;/p&gt;

&lt;p&gt;April through mid-July — ordered topics. Ledger FIFO queue, message group ID set to account_id. Load tested ten days at 3x production volume. Two weeks shadow mode. Settlement ordering, group ID per merchant_id. Dispute lifecycle was last — delayed three weeks because Ankur (dispute service owner) was on paternity leave and nobody else could validate ordering correctness end-to-end. We waited rather than risk it. Right call.&lt;/p&gt;

&lt;p&gt;Finished mid-July. Planned for June. Month late but no incidents during migration. I'll take it. Though honestly by July I was pretty tired of the whole thing and the last few topics I moved faster than I probably should have. They were low-risk topics but still, the discipline slipped a bit toward the end.&lt;/p&gt;

&lt;p&gt;If I did this migration again, I'd start with the hardest topic first, not the easiest. Building confidence by doing easy topics first felt right at the time but it also meant we discovered the real problems (schema governance, ordering edge cases) five months in rather than five weeks in. By then you've built momentum and organizational expectation around a timeline. Discovering fundamental gaps early is better even if it's scarier.&lt;/p&gt;

&lt;p&gt;What I Got Wrong&lt;br&gt;
The monitoring gap&lt;br&gt;
Priya told me before we started: "We'll lose JMX visibility before CloudWatch equivalents are ready." I nodded, agreed, and then didn't prioritize building the new monitoring because I was focused on the migration itself. For about two weeks after Phase 1 we were partially blind. I didn't know what "healthy" looked like on SQS for our traffic patterns because I hadn't established baselines.&lt;/p&gt;

&lt;p&gt;Burned an unplanned sprint building dashboards and alarms. Should have been sprint one.&lt;/p&gt;

&lt;p&gt;The mental model problem&lt;br&gt;
Kafka and SQS fail differently. Kafka: consumer stops, lag builds, you see it, you fix it, consumer catches up. SQS: message becomes visible again after timeout, gets redelivered, eventually lands in DLQ. Objectively better behavior in most cases — but foreign to engineers who'd spent years watching consumer lag as their primary health signal.&lt;/p&gt;

&lt;p&gt;One engineer (he reads my LinkedIn so I won't name him) messaged at 11 PM saying we were "losing transactions." We weren't. Messages were in the DLQ exactly as designed. But nobody had explained the new model. That's on me. Ran a brown-bag session two days later. Should have been week one.&lt;/p&gt;

&lt;p&gt;The month-end ordering surprise&lt;br&gt;
This is the one I'm least comfortable admitting. Remember when I said I validated ordering requirements against normal operating conditions? In month four post-migration, we discovered that one of our "doesn't need ordering" topics — processor.batch.settlement — actually did need ordering during month-end reconciliation windows. The daily flow was fine unordered. But the monthly processor batch sends a sequence of summary records that our recon system expected in order for a specific aggregation step.&lt;/p&gt;

&lt;p&gt;It had never been a problem on Kafka because the single-partition setup happened to preserve ordering. When we moved to SQS standard queue, messages could arrive out of order, and the monthly recon broke.&lt;/p&gt;

&lt;p&gt;We retrofitted it to a FIFO queue with group ID set to processor_id. Took a few days. Not catastrophic. But it's the kind of thing that makes me less confident about the other topics I classified as "ordering not required." Maybe they're fine. Maybe there's another month-end or quarter-end edge case waiting. I haven't exhaustively validated every temporal edge condition and I probably should.&lt;/p&gt;

&lt;p&gt;What I Gave Up and Still Think About&lt;br&gt;
I'm not going to pretend this was all upside. There are real tradeoffs I'm living with.&lt;/p&gt;

&lt;p&gt;Message replay is gone. Kafka lets you reset consumer offsets and replay from any point in the log. We used this maybe twice a year for debugging production issues — reset the fraud consumer back 2 hours, replay events, watch what happened. With SQS, once a message is consumed, it's gone. We now log all events to S3 as a side-channel for replay purposes, but it's not the same. It's batched, delayed, and honestly the tooling around querying those S3 logs is not great. I keep meaning to set up Athena queries for it properly but haven't gotten around to it.&lt;/p&gt;

&lt;p&gt;I miss it about once a quarter. Not enough to justify Kafka's operational overhead. But I miss it.&lt;/p&gt;

&lt;p&gt;I'll say this for Kafka: the pull-based consumption model and the persistent log are genuinely elegant ideas. Being able to have multiple consumer groups each reading the same stream at their own pace, rewinding independently — nothing else does that as cleanly. We just weren't using it enough to justify the operational cost. But if someone told me they needed those semantics for real, I wouldn't argue against Kafka. I'd argue for a dedicated team to run it.&lt;/p&gt;

&lt;p&gt;FIFO throughput ceiling is real. 300 messages per second per message group without batching, 3,000 with batching. We're well under today. But payments companies grow. I modeled the crossover point: if our transaction volume grows roughly 4x from current, the per-merchant settlement FIFO queue will start hitting group-level throughput limits during evening batch windows.&lt;/p&gt;

&lt;p&gt;I documented this in the ADR with a trigger condition: "If peak per-merchant throughput exceeds 200 msg/sec sustained, evaluate sharding strategy or alternative." We're at about 50 today. I check it quarterly. It's not a crisis. But it's a ceiling and I'm aware of it.&lt;/p&gt;

&lt;p&gt;Exactly-once is different. Kafka's transactional producer gives you true exactly-once within the Kafka ecosystem. SQS FIFO deduplication has a 5-minute window — well, technically it's "exactly-once delivery" within that window but you still need idempotent processing on the consumer side because deduplication only prevents the queue from delivering the same message twice, it doesn't prevent your producer from sending semantically-duplicate messages with different IDs. So really it's at-least-once with a deduplication convenience layer. We handle the rest with idempotency keys at the consumer level. It works. But it's application-level concern that Kafka's transactional model handled at infrastructure level.&lt;/p&gt;

&lt;p&gt;A purist would call this a regression. I call it pragmatic for our volume and failure patterns.&lt;/p&gt;

&lt;p&gt;Cost at scale is uncertain. We saw 40% cost reduction. But SQS pricing is per-message. Kafka's cost is mostly fixed infrastructure. If our message volume doubles (possible within 18 months based on merchant growth projections), the gap narrows. If it triples, SQS might actually be more expensive. I haven't hit that crossover yet but I've modeled it and it exists. Roughly $14K/month on messaging today vs. the ~$23K we were paying for Kafka infra. If volume triples, SQS would be around $42K. So there's a ceiling on the cost story too.&lt;/p&gt;

&lt;p&gt;I'm not worried about it today. I am aware of it.&lt;/p&gt;

&lt;p&gt;Fourteen Months Out&lt;br&gt;
Kafka brokers terminated. ZooKeeper gone. The Grafana Kafka dashboard — I actually forgot to decommission that instance for two months after migration. Nobody noticed, which kind of tells you everything about how much value it was providing.&lt;/p&gt;

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

&lt;p&gt;On-call pages from messaging: 2.3/week → something like 0.4. Mostly DLQ alerts that auto-resolve.&lt;br&gt;
Settlement duplicates: zero. The failure mode is architecturally gone.&lt;br&gt;
Recon false positives: zero (after the month-end fix).&lt;br&gt;
Fraud scoring gaps: zero.&lt;br&gt;
Cost: ~40% down at current volume. Crossover point modeled, being tracked.&lt;br&gt;
Capacity: about a third of my week back, same for the other engineer who'd been half-time Kafka admin. That time went to fraud rules and a merchant self-service feature we'd been deprioritizing for a year.&lt;br&gt;
Ravi's consumer framework is archived in the repo. I left a README explaining what it was and why it's no longer needed. Felt right. His rebalance listener code was genuinely clever — I learned things from reading it. The problem it solved just doesn't exist anymore.&lt;/p&gt;

&lt;p&gt;What I'd Actually Tell You&lt;br&gt;
I'm not going to say "audit your topics" like it's a revelation. You already know whether your Kafka cluster is well-operated or a liability. You know whether you have the team to run it properly. You know whether you're using it as a log or as a queue.&lt;/p&gt;

&lt;p&gt;What I will say: the decision isn't "Kafka vs. SQS." It's "what are we actually paying for — in engineering time, in operational risk, in cognitive overhead — and is that cost justified by our actual usage patterns?"&lt;/p&gt;

&lt;p&gt;For us it wasn't. For you it might be. If you've got a dedicated platform team, high-throughput event sourcing, multi-consumer replay requirements, Kafka is genuinely the right tool. Don't rip it out because some guy on LinkedIn (me) told a story about how it went well for his team.&lt;/p&gt;

&lt;p&gt;But if you're in the position I was in — small team, payments-grade reliability requirements, Kafka expertise concentrated in two people, entire rest of your stack on AWS — at least do the audit. Look at what you're actually using versus what you're maintaining the capability to theoretically use.&lt;/p&gt;

&lt;p&gt;The gap between those two things was embarrassingly large for us. I suspect it might be for you too. But I could be wrong about your situation, and only your spreadsheet will tell you.&lt;/p&gt;

&lt;p&gt;Anyway — that's the story. Five months, a bunch of Jira tickets, one schema incident that still annoys me, and a month-end ordering bug that keeps me humble about how thorough my original audit actually was. Make of it what you will.&lt;/p&gt;

&lt;p&gt;Before - &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%2Fjxvn7pbfl6h0h4kxqwds.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%2Fjxvn7pbfl6h0h4kxqwds.png" alt=" " width="799" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After - &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%2Fou71zwmaeiuas83nkuku.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%2Fou71zwmaeiuas83nkuku.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Why Almost Every Search Feature Eventually Ends Up Using Elasticsearch</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Wed, 29 Jul 2026 15:30:25 +0000</pubDate>
      <link>https://dev.to/bsknath/why-almost-every-search-feature-eventually-ends-up-using-elasticsearch-5ad3</link>
      <guid>https://dev.to/bsknath/why-almost-every-search-feature-eventually-ends-up-using-elasticsearch-5ad3</guid>
      <description>&lt;p&gt;There's a ticket that every product team gets eventually. It says something like "search is broken" or "why can't users find anything?" or my favorite, "can we make search not suck?"&lt;/p&gt;

&lt;p&gt;The search box was built on PostgreSQL. It worked fine for two years. Then the catalog grew, users started expecting Google-level typo tolerance, and the PM asked for faceted filters. Now the &lt;code&gt;LIKE&lt;/code&gt; query takes 4 seconds and someone's googling "Elasticsearch vs Solr."&lt;/p&gt;

&lt;p&gt;I've been on the team that migrates. It's not fun, but it's predictable. Here's why it happens and what you're actually signing up for.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Moment Your Database Stops Being Good Enough
&lt;/h2&gt;

&lt;p&gt;Every search feature starts the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%headset%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works. With 10,000 products and a reasonable index, it returns in milliseconds. Nobody complains.&lt;/p&gt;

&lt;p&gt;Then the requirements compound:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Can we add typo tolerance? Users type 'wireles' and get nothing back."&lt;/li&gt;
&lt;li&gt;"Can we search across name, description, brand, and category simultaneously?"&lt;/li&gt;
&lt;li&gt;"Can we rank results by relevance instead of returning them in insertion order?"&lt;/li&gt;
&lt;li&gt;"Can we add filters (price range, brand, in-stock) that work &lt;em&gt;with&lt;/em&gt; the text search?"&lt;/li&gt;
&lt;li&gt;"Can we show facet counts (42 results in Electronics, 18 in Gaming)?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is solvable individually in PostgreSQL. Together, at scale, they compound into a query that no amount of indexing will save. I ran &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; on our compound search query once it hit a million records — the planner was doing a sequential scan because the trigram GIN index couldn't handle the combined text + filter + sort. Response time: 3.8 seconds. The PM's exact words were "that's not search, that's a loading screen."&lt;/p&gt;

&lt;p&gt;PostgreSQL's full-text search (&lt;code&gt;tsvector&lt;/code&gt; / &lt;code&gt;tsquery&lt;/code&gt;) buys you time. It handles tokenization, stemming, and ranking. It's genuinely good for moderate-scale use cases — maybe up to a few million documents if your queries aren't too complex. But the moment you need fuzzy matching, synonym expansion, language-aware analysis, and relevance tuning &lt;em&gt;together&lt;/em&gt;, you've outgrown what a relational database was designed to do.&lt;/p&gt;

&lt;p&gt;The progression almost always looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%term%'&lt;/span&gt;  &lt;span class="err"&gt;→&lt;/span&gt;  &lt;span class="n"&gt;PostgreSQL&lt;/span&gt; &lt;span class="n"&gt;FTS&lt;/span&gt;  &lt;span class="err"&gt;→&lt;/span&gt;  &lt;span class="n"&gt;Elasticsearch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The question isn't whether you'll make this jump. It's when your product requirements force it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Elasticsearch Actually Does Differently
&lt;/h2&gt;

&lt;p&gt;Databases scan documents looking for matches. Elasticsearch flips this — it pre-builds a map from &lt;em&gt;term → documents that contain it&lt;/em&gt;. So instead of asking "does this document contain 'headset'?" a million times, it asks "which documents contain 'headset'?" once.&lt;/p&gt;

&lt;p&gt;Three documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Doc 1: "Wireless gaming headset with noise cancellation"
Doc 2: "Gaming keyboard with RGB lighting"  
Doc 3: "Wireless ergonomic mouse"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ES stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wireless      → [1, 3]
gaming        → [1, 2]
headset       → [1]
keyboard      → [2]
mouse         → [3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search for "wireless gaming headset" = intersect the posting lists: &lt;code&gt;[1,3] ∩ [1,2] ∩ [1]&lt;/code&gt; = Doc 1. Done. No scanning. This is why it stays fast at 100 million documents — you're looking up terms in a map, not scanning rows.&lt;/p&gt;

&lt;p&gt;The tricky part that took me a while to internalize: all this work happens at &lt;em&gt;write time&lt;/em&gt;. When a document is indexed, ES tokenizes it, stems it, lowercases it, and slots every term into the inverted index. Queries are fast because indexing was slow (relatively). You're trading write cost for read speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Analyzers — Why Search Feels Smarter Than It Is
&lt;/h2&gt;

&lt;p&gt;The reason "running shoes" matches a document containing "run" isn't magic. It's the analyzer pipeline that runs during indexing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tokenization&lt;/strong&gt;: split text into individual terms&lt;br&gt;
&lt;strong&gt;Lowercasing&lt;/strong&gt;: "Running" → "running"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Stop word removal&lt;/strong&gt;: drop "with", "the", "a"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Stemming&lt;/strong&gt;: "running" → "run", "cancellation" → "cancel"&lt;/p&gt;

&lt;p&gt;Both the indexed document AND the search query pass through the same analyzer. So when you search for "running," it's stemmed to "run," which matches documents that were also stemmed to "run" at index time.&lt;/p&gt;

&lt;p&gt;This is also why Elasticsearch handles typos — Levenshtein distance (edit distance) allows fuzzy matching. "elsticsearch" is 1 edit away from "elasticsearch," so the engine can suggest or match the correct term.&lt;/p&gt;

&lt;p&gt;The analyzer is the most underrated configuration in Elasticsearch, and honestly the documentation around custom analyzers is frustratingly sparse for how important they are. A poorly configured analyzer is the #1 reason search results feel bad — I spent two days debugging "why don't users find products by brand" before realizing the analyzer was stripping the brand name as a stop word. Getting it right requires understanding your data: English prose needs stemming, product SKUs need keyword (no analysis), and multi-language catalogs need per-field language analyzers.&lt;/p&gt;


&lt;h2&gt;
  
  
  Relevance Scoring — Where Databases Fully Give Up
&lt;/h2&gt;

&lt;p&gt;Finding matches is easy. Ranking them is where search gets genuinely hard.&lt;/p&gt;

&lt;p&gt;Three documents match "python performance":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Doc A: "Python Performance Optimization Guide"
Doc B: "Python Python Python Performance"
Doc C: "Performance Tuning for Large-Scale Python Applications"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which one should rank first? ES uses BM25, which basically asks: how often does the term appear in this document, how rare is it across all documents, and how long is the document? A match in a short, focused title beats the same match buried in a 10,000-word page. And keyword stuffing (Doc B) actually gets penalized because BM25 applies diminishing returns to repeated terms.&lt;/p&gt;

&lt;p&gt;You can't do this in SQL. Not without pulling every matching row into memory and rescoring in application code — at which point your database is just an expensive &lt;code&gt;SELECT *&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The one thing nobody tells you: BM25 is the &lt;em&gt;default&lt;/em&gt; and it works well out of the box, but the moment you need business-specific ranking (boost products that are in-stock, penalize items with bad reviews, promote sponsored listings), you're writing custom function_score queries. Those get complex fast and are genuinely hard to debug when results feel "off." We had a ranking bug once where out-of-stock items ranked first because the boost function was multiplicative on a field that happened to be zero for in-stock products. Took a full day to figure out.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: ES Sits Beside Your Database, Not Instead Of It
&lt;/h2&gt;

&lt;p&gt;This trips up newcomers. ES is not your primary datastore. It's a read-optimized copy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Writes → PostgreSQL (source of truth)
           ↓
         CDC / Events / Dual writes
           ↓
         Elasticsearch (searchable view)
           ↓
Reads ← Search API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your database handles transactions and correctness. ES handles discovery and ranking. Data flows into ES through CDC (Debezium), event pipelines (Kafka consumers), or direct application writes (simple but you'll eventually hit inconsistency bugs when one write succeeds and the other fails).&lt;/p&gt;

&lt;p&gt;One gotcha: ES is near-real-time with a default 1-second refresh interval. Write a document, query immediately — it might not be there yet. For most search UIs nobody notices. But if you have a flow where a user creates something and lands on a page that searches for it, you'll get a bug report within the first week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it scales:&lt;/strong&gt; a single Lucene index gets too big, so ES splits it into &lt;strong&gt;shards&lt;/strong&gt; — each an independent Lucene index on potentially different nodes. Search hits all shards in parallel, a coordinator merges the top results. Horizontal scaling = add nodes, redistribute shards. Underneath, Lucene uses immutable segments (new data → new segment, never modify old ones, merge in background). This is why reads are fast (no locking on immutable data) and crash recovery is clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Queries Actually Look Like
&lt;/h2&gt;

&lt;p&gt;This is the part most "intro to ES" articles skip. Here's what you're actually writing when you use Elasticsearch:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple multi-field search:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;GET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/products/_search&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"multi_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wireless gaming headset"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name^3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"brand^2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"best_fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fuzziness"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUTO"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;^3&lt;/code&gt; boosts matches in the name field 3x over description. &lt;code&gt;fuzziness: AUTO&lt;/code&gt; handles typos. This single query does what would take a complex stored procedure in PostgreSQL — multi-field search, fuzzy matching, field-weighted relevance scoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search with filters and facets:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;GET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/products/_search&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"must"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"multi_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"headset"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name^3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"in_stock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lte"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"brands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"terms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"brand.keyword"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;bool&lt;/code&gt; query separates &lt;em&gt;relevance&lt;/em&gt; (&lt;code&gt;must&lt;/code&gt; — affects scoring) from &lt;em&gt;filtering&lt;/em&gt; (&lt;code&gt;filter&lt;/code&gt; — yes/no, no scoring, cached). Aggregations give you facet counts in the same request. This is the "show me wireless headsets, $50-200, in stock, with brand and price facets on the sidebar" query — one round trip.&lt;/p&gt;

&lt;p&gt;One thing that tripped me up early: that &lt;code&gt;brand.keyword&lt;/code&gt; field. If you try to aggregate on an analyzed text field, ES throws an error or gives garbage results. You need the &lt;code&gt;.keyword&lt;/code&gt; sub-field (not analyzed, exact match) for terms aggregations. The mapping needs to define both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"keyword"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyword"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Text for searching, keyword for filtering and aggregating. Once you know this it's obvious, but I lost a few hours to it the first time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom ranking with function_score:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;GET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/products/_search&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"function_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"multi_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"headset"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"functions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"is_sponsored"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"field_value_factor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"review_score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"modifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"log1p"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"missing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"boost_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"multiply"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This boosts sponsored products by 1.5x and factors in review scores with logarithmic dampening (so a product with 4.8 stars doesn't infinitely dominate over 4.5). This is where relevance tuning lives — and where it gets hard to debug when results feel wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Alternatives Landscape (2025+)
&lt;/h2&gt;

&lt;p&gt;Elasticsearch isn't the only option anymore. The landscape has shifted, and for some use cases the newer tools are genuinely better:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typesense&lt;/strong&gt; — we used this for internal documentation search and it was running in an afternoon. Typo tolerance and relevance work out of the box. No JVM, no cluster management, single binary. Great for product/site search under 100M documents. Falls short on complex aggregations and log analytics — it's not trying to be ES, and that's fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meilisearch&lt;/strong&gt; — similar positioning to Typesense. Rust-based, developer-friendly, sub-50ms search without tuning. Good until you need sharding or serious multi-tenancy. I'd pick it for a startup's product search without hesitation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenSearch&lt;/strong&gt; — AWS's fork after Elastic changed the license. Functionally near-identical to ES 7.x, API-compatible. If you're already on AWS and want managed infrastructure (OpenSearch Service) without licensing headaches, this is the pragmatic choice. Migration from ES is mostly a find-and-replace in your client config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Algolia&lt;/strong&gt; — fully managed, zero infrastructure. Response times are absurd (single-digit milliseconds). But pricing is per search operation, which gets expensive fast at scale. Good for teams that value DX and speed-to-market over cost efficiency. Less control over relevance tuning than self-hosted ES.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL FTS&lt;/strong&gt; — don't dismiss it. For under 2-3M documents with straightforward requirements (tokenized search + basic ranking, no fuzzy), it's genuinely sufficient. You avoid an entire operational category. We should've stuck with it longer on one project instead of jumping to ES prematurely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My take:&lt;/strong&gt; if your dataset is under 10M documents and your requirements are "text search + filters + typo tolerance" — look at Typesense or Meilisearch first. You'll be in production in a day instead of a week. If you need complex aggregations, log analytics, custom scoring pipelines, or you're north of 100M documents — Elasticsearch (or OpenSearch) still wins. Nothing else handles that combination at that scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cost
&lt;/h2&gt;

&lt;p&gt;Nobody talks about this in the "why Elasticsearch is amazing" posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're now running two data stores. Two things to monitor, two things that can fail independently, and sync logic between them that &lt;em&gt;will&lt;/em&gt; have bugs.&lt;/li&gt;
&lt;li&gt;Mapping changes require reindexing. Change an analyzer on a field? Every existing document keeps the old index structure. You need to build a new index from scratch and swap.&lt;/li&gt;
&lt;li&gt;At 500M documents, that reindex takes hours. Hope you set up the alias pattern (&lt;code&gt;products_v1&lt;/code&gt; → &lt;code&gt;products_v2&lt;/code&gt; swap) from the beginning. Retrofitting it into a system that assumed a single index name is a bad week.&lt;/li&gt;
&lt;li&gt;Shard management is a dark art. Too few shards = can't distribute load. Too many = overhead per shard kills performance. The default of 1 shard is almost never right for production, and the "correct" number depends on your document count, document size, and query patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's worth the cost if your product genuinely needs good search. Just know what you're signing up for operationally.&lt;/p&gt;




&lt;h2&gt;
  
  
  When You Don't Need Elasticsearch
&lt;/h2&gt;

&lt;p&gt;Not every search box justifies a cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&amp;lt; 100K documents with simple queries&lt;/strong&gt;: PostgreSQL FTS handles this fine. Seriously, don't add infrastructure for a problem you don't have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact-match lookups only&lt;/strong&gt;: if users search by SKU, order ID, or email — that's a database index, not a search engine. A B-tree does this in microseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong transactional consistency required&lt;/strong&gt;: if a write &lt;em&gt;must&lt;/em&gt; be visible in search results within the same request, ES's 1-second refresh lag will hurt you. We learned this one the hard way with a status update flow where users would update a record and immediately search for it with the new status — and not find it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny product catalog&lt;/strong&gt;: 500 products? Load them into memory on startup, filter in Java. You don't need distributed infrastructure for this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decision should be: "have we outgrown what our database can do for this specific access pattern?" If yes, add ES. If you're speculating that you might need it someday — wait until you actually do.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Progression Every Team Goes Through
&lt;/h2&gt;

&lt;p&gt;Elasticsearch isn't the best database (it isn't one), and it isn't even the search algorithm (Lucene does that part). It's infrastructure that bridges the gap between what users expect from a search box and what a relational database can deliver.&lt;/p&gt;

&lt;p&gt;That gap widens with every feature request. And at some point, probably around the third PM ticket about "search quality," you'll start evaluating ES. When you do, plan for the alias-based reindexing pattern from day one, budget for the operational overhead, and don't index more fields than you actually search on. The cluster will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Further reading:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.elastic.co/guide/en/elasticsearch/guide/current/index.html" rel="noopener noreferrer"&gt;Elasticsearch: The Definitive Guide&lt;/a&gt; (official, somewhat dated but conceptually solid)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.manning.com/books/relevant-search" rel="noopener noreferrer"&gt;Relevant Search&lt;/a&gt; — Turnbull &amp;amp; Berryman (the best book on search relevance engineering)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lucene.apache.org/" rel="noopener noreferrer"&gt;Apache Lucene&lt;/a&gt; (the actual engine underneath)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Okapi_BM25" rel="noopener noreferrer"&gt;BM25 scoring&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>elasticsearch</category>
      <category>distributedsystems</category>
      <category>uxdesign</category>
    </item>
    <item>
      <title>Why Elasticsearch Should Not Be Preferred for Writing Data</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Tue, 17 Dec 2024 18:26:18 +0000</pubDate>
      <link>https://dev.to/bsknath/why-elasticsearch-should-not-be-preferred-for-writing-data-107d</link>
      <guid>https://dev.to/bsknath/why-elasticsearch-should-not-be-preferred-for-writing-data-107d</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2F8h2l0ujd0xscaklp08do.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F8h2l0ujd0xscaklp08do.jpg" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Elasticsearch is one of the most popular tools for searching and analyzing data. Its ability to process massive datasets and deliver near real-time search results has made it a cornerstone for applications ranging from e-commerce platforms to monitoring systems. However, when it comes to writing data, Elasticsearch falls short in several critical areas. In this article, we’ll explore the challenges of writing data to Elasticsearch, explain why it’s not ideal for write-heavy use cases, and discuss better alternatives. 📊💾🚫&lt;/p&gt;

&lt;p&gt;Understanding Elasticsearch’s Core Strength: Read-Heavy Workloads&lt;/p&gt;

&lt;p&gt;Elasticsearch is optimized for fast querying and analytics, powered by its inverted index architecture. This makes it a perfect choice for use cases where:&lt;/p&gt;

&lt;p&gt;Data is read and searched frequently.&lt;/p&gt;

&lt;p&gt;Queries involve complex filtering, aggregations, or full-text search.&lt;/p&gt;

&lt;p&gt;However, Elasticsearch was not built for heavy or continuous data writing. Its architecture and design decisions, while excellent for search, introduce inefficiencies for write-intensive applications. 🧠🔍💡&lt;/p&gt;

&lt;p&gt;Challenges of Writing Data to Elasticsearch&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High Resource Consumption&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Indexing Overhead: Every time data is written to Elasticsearch, it goes through a series of processes:&lt;/p&gt;

&lt;p&gt;Analysis: Text fields are broken into tokens using analyzers.&lt;/p&gt;

&lt;p&gt;Inverted Index Creation: Tokens are mapped to their locations in documents.&lt;/p&gt;

&lt;p&gt;Segment Management: Data is written to immutable segments on disk.&lt;/p&gt;

&lt;p&gt;These steps consume significant CPU, memory, and disk resources. 📈🛠️🔄&lt;/p&gt;

&lt;p&gt;Write Amplification: Elasticsearch continuously creates and merges segments to optimize queries. These operations amplify disk I/O, leading to slower writes and higher infrastructure costs. 🚀📂📉&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Eventual Consistency&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Elasticsearch is eventually consistent. This means that after writing data to one node, it may take time for the data to replicate across the cluster and become available for querying.&lt;/p&gt;

&lt;p&gt;For applications requiring immediate consistency (e.g., financial transactions or inventory updates), this delay is unacceptable. ⏱️⚠️🔄&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Frequent Updates Are Costly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unlike traditional databases, Elasticsearch doesn’t modify existing documents directly.&lt;/p&gt;

&lt;p&gt;Instead, it marks the old document as deleted and writes a new version of the document to a new segment. This process:&lt;/p&gt;

&lt;p&gt;Consumes more disk space.&lt;/p&gt;

&lt;p&gt;Triggers reindexing and segment merges, further taxing the system. 🗂️🖋️🔄&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Limited Transactional Capabilities&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Elasticsearch does not support ACID (Atomicity, Consistency, Isolation, Durability) transactions.&lt;/p&gt;

&lt;p&gt;Concurrent writes or updates can lead to data conflicts or inconsistencies, making it unsuitable for applications requiring strict transactional guarantees. ❌🔒🔗&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Performance Degradation Under Heavy Write Load&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Write-heavy workloads can overwhelm Elasticsearch, causing cluster instability. Symptoms include:&lt;/p&gt;

&lt;p&gt;High latencies for both writes and reads.&lt;/p&gt;

&lt;p&gt;Increased memory usage leading to out-of-memory errors.&lt;/p&gt;

&lt;p&gt;Node failures and slower query performance. 🐢🔥💥&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Disk Usage Overhead&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The inverted index and additional metadata (e.g., for replicas and segments) result in significant disk usage. Frequent writes, updates, and deletes exacerbate this problem, leading to higher storage requirements and costs. 💾📉📛&lt;/p&gt;

&lt;p&gt;Example: Writing Data to Elasticsearch&lt;/p&gt;

&lt;p&gt;Scenario&lt;/p&gt;

&lt;p&gt;Imagine a real-time analytics system for tracking user interactions on a website. The system logs every page view, button click, and transaction as a separate document in Elasticsearch. With millions of interactions recorded daily, the following challenges arise: 🌐📱📊&lt;/p&gt;

&lt;p&gt;High Write Throughput:&lt;/p&gt;

&lt;p&gt;Each interaction generates a new document.&lt;/p&gt;

&lt;p&gt;Elasticsearch’s indexing process struggles to keep up, resulting in slower writes and increased resource usage.&lt;/p&gt;

&lt;p&gt;Frequent Updates:&lt;/p&gt;

&lt;p&gt;If user interactions need updates (e.g., adding session details), Elasticsearch marks the old document as deleted and writes a new version. This doubles the write effort and bloats disk usage. 📈📝📛&lt;/p&gt;

&lt;p&gt;Delayed Availability:&lt;/p&gt;

&lt;p&gt;Newly indexed data isn’t immediately available for queries due to the refresh interval (default: 1 second). For real-time applications, this delay is problematic. 🕒⚡🚫&lt;/p&gt;

&lt;p&gt;Better Alternatives for Write-Heavy Workloads&lt;/p&gt;

&lt;p&gt;If your application involves high write throughput or frequent updates, consider these alternatives: 💡🔄💼&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Relational Databases&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Examples: MySQL, PostgreSQL&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;ACID-compliant transactions ensure data consistency.&lt;/p&gt;

&lt;p&gt;Optimized for frequent updates and transactional writes.&lt;/p&gt;

&lt;p&gt;Use Cases: Financial systems, inventory management, and applications requiring strong consistency.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;NoSQL Databases&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Examples: MongoDB, Apache Cassandra, DynamoDB&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;High scalability and fault tolerance.&lt;/p&gt;

&lt;p&gt;Efficient for high write throughput.&lt;/p&gt;

&lt;p&gt;Use Cases: Real-time analytics, distributed systems, and high-availability applications.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Message Queues or Streaming Systems&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Examples: Apache Kafka, Amazon Kinesis&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;Handle massive write loads efficiently.&lt;/p&gt;

&lt;p&gt;Provide durability and scalability for event-driven architectures.&lt;/p&gt;

&lt;p&gt;Use Cases: Event logging, real-time data pipelines, and buffering writes before processing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Time-Series Databases&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Examples: InfluxDB, TimescaleDB&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;Designed for time-series data with high write speeds.&lt;/p&gt;

&lt;p&gt;Built-in support for time-based queries and retention policies.&lt;/p&gt;

&lt;p&gt;Use Cases: IoT data, monitoring systems, and performance analytics. 📊📆🚀&lt;/p&gt;

&lt;p&gt;Optimizing Writes to Elasticsearch (If You Must)&lt;/p&gt;

&lt;p&gt;For scenarios where Elasticsearch is necessary for search and analytics but still requires frequent data writes, consider the following optimizations: 🛠️📊✨&lt;/p&gt;

&lt;p&gt;Use Bulk API:&lt;/p&gt;

&lt;p&gt;Batch multiple write operations into a single request to reduce indexing overhead.&lt;/p&gt;

&lt;p&gt;Adjust Refresh Interval:&lt;/p&gt;

&lt;p&gt;Increase the refresh interval to delay making new data searchable, reducing resource usage during writes.&lt;/p&gt;

&lt;p&gt;Shard Configuration:&lt;/p&gt;

&lt;p&gt;Optimize the number of shards and replicas to balance performance and storage.&lt;/p&gt;

&lt;p&gt;Pre-process Data:&lt;/p&gt;

&lt;p&gt;Use tools like Apache Kafka or AWS Lambda to aggregate and transform data before writing to Elasticsearch.&lt;/p&gt;

&lt;p&gt;Monitor and Scale:&lt;/p&gt;

&lt;p&gt;Use Elasticsearch’s monitoring tools to identify bottlenecks and scale resources as needed. 📊📈🖥️&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Elasticsearch is an exceptional tool for search and analytics, but it is not designed to handle write-heavy workloads efficiently. Its resource-intensive indexing process, eventual consistency model, and limited transactional capabilities make it unsuitable for applications that prioritize high write throughput or frequent updates. 📈🚀❌&lt;/p&gt;

&lt;p&gt;Instead, consider using purpose-built databases and systems for writing data, and use Elasticsearch as a secondary layer for search and analytics. This approach ensures better performance, scalability, and cost-efficiency for your application. 💼💡✔️&lt;/p&gt;

&lt;p&gt;Have you faced challenges with writing data to Elasticsearch? Share your experiences in the comments! 💬📣🤔&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Records in java</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Tue, 26 Mar 2024 05:27:59 +0000</pubDate>
      <link>https://dev.to/bsknath/records-in-java-3bc9</link>
      <guid>https://dev.to/bsknath/records-in-java-3bc9</guid>
      <description>&lt;p&gt;Whenever we talk about boilerplate code in Java, there is always an argument that now IDEs are intelligent enough to generate sources. The problem comes when there is an addition in the fields. &lt;code&gt;Record&lt;/code&gt; in Java 14 solves this problem at least for the Data Classes.&lt;br&gt;
A record class in Java automatically has:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A private final field for each component of the record.&lt;/li&gt;
&lt;li&gt;A public constructor whose signature matches the record components.&lt;/li&gt;
&lt;li&gt;Public read accessor methods for each component of the record, with the same name and type as the component.&lt;/li&gt;
&lt;li&gt;Implementations of equals(), hashCode(), and toString() methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example of how to declare and use a record:&lt;br&gt;
&lt;code&gt;public record Employee(String name, int age) {}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This record class Employee has two components: name and age. You can create an instance of Employee and access its components like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Employee employee = new Employee("John Doe", 30);&lt;br&gt;
System.out.println(employee.name()); // prints "John Doe"&lt;br&gt;
System.out.println(employee.age()); // prints 30&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Note that records are intended to be used as simple data carriers, so they are not suitable for every situation. If you need more complex behavior, you should use a regular class.&lt;/p&gt;

&lt;p&gt;Records in Java, while useful for creating simple data carrier classes, do have some limitations:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Inheritance: Records cannot extend any other class, and they are implicitly final, so no other class can extend a record.
&lt;/li&gt;
&lt;li&gt;Limited Instance Variables: All instance variables must be declared in the record header, and these variables are automatically final. You cannot declare additional fields in the body of the record.
&lt;/li&gt;
&lt;li&gt;No Argument-less Constructor: Records automatically provide a constructor with arguments for all fields, but they do not provide a no-argument constructor. This can be a limitation when working with frameworks that rely on no-argument constructors.
&lt;/li&gt;
&lt;li&gt;No Mutability: Since all fields in a record are final, records are inherently immutable. This means once a record object is created, you cannot change its state.
&lt;/li&gt;
&lt;li&gt;Limited Use of Annotations: Annotations that are applicable to classes, methods, or fields may not be applicable to record components.
&lt;/li&gt;
&lt;li&gt;Limited Scope: Records are intended to be used as simple data carriers. If you need more complex behavior, such as methods that operate on the fields, you should use a regular class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6u1e6ynr7ijq1l8t5xdg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6u1e6ynr7ijq1l8t5xdg.jpg" alt="Image description" width="736" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;few good reads - &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/javase/17/language/records.html#GUID-6699E26F-4A9B-4393-A08B-1E47D4B2D263"&gt;https://docs.oracle.com/en/java/javase/17/language/records.html#GUID-6699E26F-4A9B-4393-A08B-1E47D4B2D263&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/java-record-keyword"&gt;https://www.baeldung.com/java-record-keyword&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Records in java</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Tue, 26 Mar 2024 05:25:54 +0000</pubDate>
      <link>https://dev.to/bsknath/records-in-java-3f7f</link>
      <guid>https://dev.to/bsknath/records-in-java-3f7f</guid>
      <description>&lt;p&gt;Whenever we talk about boilerplate code in Java, there is always an argument that now IDEs are intelligent enough to generate sources. The problem comes when there is an addition in the fields. &lt;code&gt;Record&lt;/code&gt; in Java 14 solves this problem at least for the Data Classes.&lt;br&gt;
A record class in Java automatically has:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A private final field for each component of the record.&lt;/li&gt;
&lt;li&gt;A public constructor whose signature matches the record components.&lt;/li&gt;
&lt;li&gt;Public read accessor methods for each component of the record, with the same name and type as the component.&lt;/li&gt;
&lt;li&gt;Implementations of equals(), hashCode(), and toString() methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example of how to declare and use a record:&lt;br&gt;
&lt;code&gt;public record Employee(String name, int age) {}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This record class Employee has two components: name and age. You can create an instance of Employee and access its components like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Employee employee = new Employee("John Doe", 30);&lt;br&gt;
System.out.println(employee.name()); // prints "John Doe"&lt;br&gt;
System.out.println(employee.age()); // prints 30&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Note that records are intended to be used as simple data carriers, so they are not suitable for every situation. If you need more complex behavior, you should use a regular class.&lt;/p&gt;

&lt;p&gt;Records in Java, while useful for creating simple data carrier classes, do have some limitations:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Inheritance: Records cannot extend any other class, and they are implicitly final, so no other class can extend a record.
&lt;/li&gt;
&lt;li&gt;Limited Instance Variables: All instance variables must be declared in the record header, and these variables are automatically final. You cannot declare additional fields in the body of the record.
&lt;/li&gt;
&lt;li&gt;No Argument-less Constructor: Records automatically provide a constructor with arguments for all fields, but they do not provide a no-argument constructor. This can be a limitation when working with frameworks that rely on no-argument constructors.
&lt;/li&gt;
&lt;li&gt;No Mutability: Since all fields in a record are final, records are inherently immutable. This means once a record object is created, you cannot change its state.
&lt;/li&gt;
&lt;li&gt;Limited Use of Annotations: Annotations that are applicable to classes, methods, or fields may not be applicable to record components.
&lt;/li&gt;
&lt;li&gt;Limited Scope: Records are intended to be used as simple data carriers. If you need more complex behavior, such as methods that operate on the fields, you should use a regular class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;few good reads - &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/javase/17/language/records.html#GUID-6699E26F-4A9B-4393-A08B-1E47D4B2D263"&gt;https://docs.oracle.com/en/java/javase/17/language/records.html#GUID-6699E26F-4A9B-4393-A08B-1E47D4B2D263&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;htt
ps://&lt;a href="http://www.baeldung.com/java-record-keyword"&gt;www.baeldung.com/java-record-keyword&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>development</category>
    </item>
    <item>
      <title>Java groupingBy, Mapping, Counting</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Sun, 24 Mar 2024 09:32:22 +0000</pubDate>
      <link>https://dev.to/bsknath/java-groupingby-mapping-counting-4n34</link>
      <guid>https://dev.to/bsknath/java-groupingby-mapping-counting-4n34</guid>
      <description>&lt;p&gt;In my earlier post, we talked about various methods provided by &lt;code&gt;java.util.stream.Collectors&lt;/code&gt;.&lt;br&gt;
This post, I am going to talk about few other methods which are very powerful and useful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4aq3ggwoyq53ceomuo5c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4aq3ggwoyq53ceomuo5c.jpg" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is my previous post - &lt;br&gt;
&lt;a href="https://dev.to/bsknath/exploring-java-collectors-utilities-10ne"&gt;https://dev.to/bsknath/exploring-java-collectors-utilities-10ne&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>development</category>
    </item>
    <item>
      <title>Exploring Java Collectors utilities</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Fri, 22 Mar 2024 12:22:56 +0000</pubDate>
      <link>https://dev.to/bsknath/exploring-java-collectors-utilities-10ne</link>
      <guid>https://dev.to/bsknath/exploring-java-collectors-utilities-10ne</guid>
      <description>&lt;p&gt;In Java, the &lt;code&gt;java.util.stream.Collectors&lt;/code&gt; class provides a number of useful methods for performing reductions on streams. Here are some commonly used ones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;toList()&lt;/code&gt;: Collects all Stream elements into a List instance.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n62vrvhigpth8uyvtml.jpg" alt="Image description" width="800" height="562"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;toSet()&lt;/code&gt;: Collects all Stream elements into a Set instance.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmy0zr8fc5xnfk9m7cfr7.jpg" alt="Image description" width="800" height="544"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toSet&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;toMap()&lt;/code&gt;: Creates a Map instance from the Stream elements, with the first function serving as the map's keys and the second function as the values.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg59da4w6515xngip0xfv.jpg" alt="Image description" width="800" height="504"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nl"&gt;String:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;joining()&lt;/code&gt;: Concatenates all Stream elements into a String.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffll6rhvdxx5ymxf9uiqn.jpg" alt="Image description" width="800" height="553"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;joining&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;counting()&lt;/code&gt;: Counts the number of elements in the Stream.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxkrm4s2et9tas4uvf0p.jpg" alt="Image description" width="800" height="600"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;counting&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;summingInt()&lt;/code&gt;, &lt;code&gt;summingLong()&lt;/code&gt;, &lt;code&gt;summingDouble()&lt;/code&gt;: Sums the Stream elements.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj02739yx0tgqx5om19lg.jpg" alt="Image description" width="800" height="635"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;summingInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Integer:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;intValue&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;maxBy()&lt;/code&gt;, &lt;code&gt;minBy()&lt;/code&gt;: Finds the maximum or minimum Stream element according to a provided Comparator.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvb8vcye21ha90kqwmv68.jpg" alt="Image description" width="800" height="649"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxBy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Comparator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;naturalOrder&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;collectingAndThen()&lt;/code&gt;: Performs an additional finishing transformation.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94bdr9or7xxxlfz0xafe.jpg" alt="Image description" width="800" height="590"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;unmodifiableList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collectingAndThen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nl"&gt;Collections:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;unmodifiableList&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;partitioningBy()&lt;/code&gt;: Partitions the Stream elements into a Map according to a Predicate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy62u8ttumqedlv0mamuc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy62u8ttumqedlv0mamuc.jpg" alt="Image description" width="800" height="476"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;partitioned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;partitioningBy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are just a few examples. The &lt;code&gt;Collectors&lt;/code&gt; class provides many more utility methods for common tasks.&lt;/p&gt;

</description>
      <category>java</category>
      <category>java8</category>
      <category>programming</category>
      <category>coding</category>
    </item>
    <item>
      <title>Stop, think about it. Are you using the right Data Structure?</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Sat, 24 Jun 2023 04:40:19 +0000</pubDate>
      <link>https://dev.to/bsknath/stop-think-about-it-are-you-using-the-right-data-structure-580f</link>
      <guid>https://dev.to/bsknath/stop-think-about-it-are-you-using-the-right-data-structure-580f</guid>
      <description>&lt;p&gt;A very trivial thing but knowing which data structure or implementation maintains the order of insertion, allows duplicates, or does not accept null helps prevent unexpected outcomes. Had a similar first-hand experience a few months ago. 💀&lt;/p&gt;

&lt;p&gt;For example, lets take Map interface. There are three classes that implements Map in java - HashMap, LinkedHashMap and TreeMap.&lt;/p&gt;

&lt;p&gt;♨️ HashMap doesn't maintain insertion order, LinkedHashMap does, TreeMap maintains order in sorted manner.&lt;br&gt;
♨️ HashMap and LinkedHashMap allows one null key object (K,V), TreeMap doesn't (it may have multiple null values, never the keys).&lt;br&gt;
♨️ HashMap and LinkedHashMap both have O(1) insertion and search. TreeMap, has O(logN). Bonus - TreeMap is implemented using RB-Tree, HM - LinkedLists, LHM - Doubley-LL. These implementation are the reason for these TC.&lt;/p&gt;

</description>
      <category>java</category>
      <category>softwareengineering</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Coding standards that everyone must follow</title>
      <dc:creator>Basu</dc:creator>
      <pubDate>Thu, 15 Dec 2022 18:23:05 +0000</pubDate>
      <link>https://dev.to/bsknath/coding-standards-that-everyone-must-follow-207a</link>
      <guid>https://dev.to/bsknath/coding-standards-that-everyone-must-follow-207a</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;First rule of coding - write clean, beautiful and elegant code&lt;/li&gt;
&lt;li&gt;Second rule of coding - write clean, beautiful and elegant code&lt;/li&gt;
&lt;li&gt;Take time and effort to keep our code clean and pleasing for everyone to read&lt;/li&gt;
&lt;li&gt;Follow these basic coding principles:&lt;/li&gt;
&lt;li&gt;KISS – Keep It Simple, Stupid&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/SOLID"&gt;SOLID Principles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Inversion_of_control"&gt;Inversion of Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DRY – Don’t Repeat Yourself.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it"&gt;YAGNI – You Ain’t Gonna Need It.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Law of Demeter.&lt;/li&gt;
&lt;li&gt;Follow Boy’s Scout rule – leave code better than you found it.&lt;/li&gt;
&lt;li&gt;Use “&lt;a href="https://en.wikipedia.org/wiki/Software_design_pattern"&gt;Design Patterns&lt;/a&gt;” whenever it makes sense to do it.&lt;/li&gt;
&lt;li&gt;Avoid Premature Optimization – Premature Optimization is the root of all evil (or at least most of it) in programming. It adds unnecessary complexity in code and makes it hard to maintain in the long run. Always measure, analyze and then optimize.&lt;/li&gt;
&lt;li&gt;Pay attention to all IDE generated warnings/suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  General
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Favor composition over inheritance.&lt;/li&gt;
&lt;li&gt;Create abstraction that separate higher level concepts from lower level details.&lt;/li&gt;
&lt;li&gt;Variables and functions should be defined close to where they are used.&lt;/li&gt;
&lt;li&gt;When a method uses getter/setter excessively from other class, consider moving that logic to that class.&lt;/li&gt;
&lt;li&gt;Make sure all DTOs and Value Objects are immutable.&lt;/li&gt;
&lt;li&gt;Avoid magic numbers, Hungarian notations in code. Use named constant 
instead of magic numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core Java
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Leverage Java 8’s stream API as much as you can instead of processing collection/array explicitly. For simple operations see if makes sense to process the collection without using stream API. &lt;/li&gt;
&lt;li&gt;Use Lombok to generate boilerplate code. But do not overuse it to generate code that you don’t need&lt;/li&gt;
&lt;li&gt;Avoid annotations like @SneakyThrow, &lt;a class="mentioned-user" href="https://dev.to/data"&gt;@data&lt;/a&gt;, &lt;a class="mentioned-user" href="https://dev.to/synchronized"&gt;@synchronized&lt;/a&gt; from Lombok&lt;/li&gt;
&lt;li&gt;Before using @Value annotation make sure you really need all functionality provided by @Value&lt;/li&gt;
&lt;li&gt;Create new type instead of passing around complex collection types around e.g. instead of using List&amp;gt; create a type that represents that collection at higher abstraction&lt;/li&gt;
&lt;li&gt;Use enums instead of constants&lt;/li&gt;
&lt;li&gt;Investigate if there’s a reliable third-party library available before writing your own utility methods. Apache commons, Google Guava and Spring Utils are good example of third-party libraries that provides tons of useful functionality&lt;/li&gt;
&lt;li&gt;Use meaningful and explanatory name that reveals the intention of variable/class/method&lt;/li&gt;
&lt;li&gt;If name requires comment, it doesn’t reveal its intention. Consider re-naming such variables&lt;/li&gt;
&lt;li&gt;Use long and descriptive name but do not use very long variable name that it starting to look like sentence&lt;/li&gt;
&lt;li&gt;Avoid unnecessarily acronymization of variable name. A variable called “timeSheet” is much more descriptive and meaningful than “ts”&lt;/li&gt;
&lt;li&gt;Class Names – Classes and Objects should have noun or noun phrase names. E.g. TimeSheet, WorkEntry. It should not be a verb.&lt;/li&gt;
&lt;li&gt;Method Names – Method should have verb or verb phrase names. E.g. approveTimeSheet, submitWorkEntry&lt;/li&gt;
&lt;li&gt;Event Class Names - Should be verb in past tense E.g.TimeSheetSubmitted&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comments
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Avoid comments. We should strive at writing self-explanatory code that requires minimum to no comments. Ideally, our code should not have any comments. If your code needs commenting, you might be doing something wrong&lt;/li&gt;
&lt;li&gt;Do not comment change history, user story in source code – it should go as part of your commit message. All meta-data about code like author, last modified time, US number, Defect Numbers should not be part of code&lt;/li&gt;
&lt;li&gt;Do not use Javadoc comment that says nothing more than function signature&lt;/li&gt;
&lt;li&gt;Javadoc style comments which are mainly used for documentation should be limited for library functionality&lt;/li&gt;
&lt;li&gt;Never ever comment out code. Just delete it.&lt;/li&gt;
&lt;li&gt;Comment should be used only when code cannot say for itself clearly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Functions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Functions should be small. This applies to code blocks like if, while, switch blocks etc.&lt;/li&gt;
&lt;li&gt;Function should do one thing only&lt;/li&gt;
&lt;li&gt;If your function has more than a few arguments, see if your method has tight coupling with other classes. If you do need those arguments, try to wrap them in separate class&lt;/li&gt;
&lt;li&gt;Avoid Boolean flag as method parameter that is used as a flag to choose behavior in method&lt;/li&gt;
&lt;li&gt;Return java.util.Optional instead of "return null" from method&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Make sure your tests cover all scenarios specified. &lt;/li&gt;
&lt;li&gt;Write unit test for everything that could possibly break&lt;/li&gt;
&lt;li&gt;Use IDE coverage tool to make sure you are covering all critical code path and branches&lt;/li&gt;
&lt;li&gt;Test all boundary conditions&lt;/li&gt;
&lt;li&gt;Write test (or tests) for each reported bug&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
