Skip to content
View sahilkalgutkar's full-sized avatar
🎯
Focusing
🎯
Focusing

Block or report sahilkalgutkar

Block user

Prevent this user from interacting with your repositories and sending you notifications. Learn more about blocking users.

You must be logged in to block users.

Content in all repositories owned by your account will be closed.
Maximum 250 characters. Please don’t include any personal information such as legal names or email addresses. Markdown is supported. This note will only be visible to you.
Report abuse

Contact GitHub support about this user’s behavior. Learn more about reporting abuse.

Report abuse
sahilkalgutkar/README.md

👋 Hey, I'm Sahil Kalgutkar

Software EngineerBackend & Distributed SystemsCloud-Native Platforms

Typing SVG

LinkedIn Email GitHub

GitHub followers GitHub stars Profile views


🚀 About Me

I'm a Software Engineer with 5+ years designing, building, and operating scalable, cloud-native backend services — from greenfield platform components to migrating legacy systems into resilient microservices. I own features end-to-end: clarifying ambiguous requirements, designing the solution, implementing and testing it, and running it in production, while partnering closely with Product, Compliance, and cross-functional stakeholders.

Currently building the Identity & Trust Platform at Ascensus, where I design data models and APIs that serve as the single system of record for customers, entities, and users across the platform.

Comfortable communicating technical designs and tradeoffs to both technical and non-technical audiences, and mentoring engineers to build depth and ownership.


💼 Experience

Software Engineer — Ascensus                                    Mar 2024 – Present
  → Architecting the Identity & Trust Platform: data models, entitlements,
    and shared compliance-context components across resilient microservices
  → Leading migration of identity & customer-lifecycle logic off legacy systems
  → Java · Spring Boot · Python · REST · gRPC · AWS · Kafka · Kubernetes

Software Engineer Intern, AI/ML — Guidepoint                     Oct 2023 – Jan 2024
  → Built a PyTorch/Hugging Face matching system — +25% match relevance, 90% accuracy
  → Elasticsearch-backed retrieval pipeline for real-time matching
  → PyTorch · CUDA · Transformers · Elasticsearch · Docker · Kubernetes

Software Engineer — Wells Fargo                                  Apr 2021 – Aug 2022
  → Backend services for regulated financial applications in production
  → Refactored legacy components into cloud-native, high-availability services
  → Java · Spring · Python · SQL · AWS · Docker · Kubernetes

Software Engineer, Production Support — Infosys                  Aug 2019 – Mar 2021
  → Diagnosed & resolved production incidents; drove root-cause fixes
  → Java · J2EE · SQL · Shell Scripting · Linux · Control-M · Nagios

🛠️ Tech Stack

Languages Java Python Go C# JavaScript TypeScript

Frameworks & Platforms Spring Boot Django Flask NestJS Node.js React .NET

Data & Messaging PostgreSQL MySQL MongoDB Cassandra DynamoDB Redis Kafka RabbitMQ

Cloud & DevOps AWS Azure GCP Docker Kubernetes Jenkins GitHub Actions Prometheus Grafana

AI / Data Tooling PyTorch Hugging Face Elasticsearch Pandas NumPy


📌 Featured Projects

These are meant to be cloned and run, not just read. A green CI badge only proves what CI runs, so I work through the README's own commands too — that is how several of the fixes below were found — and every host port these Compose stacks publish is overridable, so a stack still comes up on a laptop that already has Postgres, Redis or Elasticsearch bound.

modelforge — A model-serving platform in Go, with an XGBoost scorer verified against XGBoost itself

CI codecov

My other ML projects call a model. This one is the layer underneath them: the thing that decides which version answers a request, groups concurrent requests into one forward pass, and notices when the inputs stop looking like the training data. I wrote the scorer too — it loads XGBoost's own save_model(*.json) and reproduces its predictions in pure Go, with no cgo, so a model version stays a self-contained artifact you can hash and roll back.

  • The fixtures are generated by XGBoost, and that differential test found a bug I would never have found reading the format: XGBoost compares split thresholds in float32 and writes them to JSON at float32 precision, so a threshold reads back as -0.3775961 while the value that produced it is -0.37759611010551453. In float64 they differ and the row goes left; in float32 they are equal and it goes right. Thresholds are chosen from training values, so rows land exactly on them constantly — one row in 64 — and the symptom is predictions correct for 99% of traffic and quietly wrong for the rest
  • base_score turns out to live in two different spaces: single-output models store it in prediction space, so the intercept is the objective's transform run backwards, while multi-class stores a margin-space vector. The binary fixture uses a non-default base_score on purpose, because logit(0.5) is 0 and would make an intercept applied in the wrong space look correct
  • Dynamic batching amortises per-call overhead: 59.5µs/op at batch size 1 against 1.38µs/op at 64, a 43x difference with 320 concurrent callers. The window opens when the first request of a batch arrives rather than on a fixed tick, which is what makes the added latency a genuine ceiling instead of a property of the clock
  • Canary assignment is a deterministic hash of an entity key, so a user cannot flip between control and candidate between requests — otherwise every per-user metric mixes both models and the experiment measures nothing. The model name is mixed in too, so experiments on different models stay independent
  • The rollout guard zeroes a failing version's weight rather than deleting the route, so an operator can see it was pulled — and it will never remove the last serving version, because turning "this version is failing" into "this model serves nothing" is strictly worse
  • Drift is PSI over quantile bins on a window of counts, not retained samples, so watching a service does not get more expensive as it gets busier. Writing the empty-bin test found a second real bug: a quantile edge at the minimum value creates a bin nothing can fall into, which is the ordinary shape for any feature that is zero for most rows
  • The API is authenticated with scoped bearer tokens, and the server stores only their SHA-256 digests. Plain SHA-256 rather than bcrypt, deliberately: password hashes are slow to defeat brute force over a small guessable space, and a 256-bit random token has no such space, so a slow hash buys nothing and costs every request. It is timing-safe because the guess is hashed before the lookup — I wrote a constant-time compare after it, then removed it, because it compared a value against itself
  • Startup fails closed: no tokens and no explicit opt-out is refused, because a server that comes up, serves, and looks healthy with an open control plane is the worst kind of silent failure. Adding auth introduced a bug the smoke run caught — audit logging keyed on the HTTP method, and scoring is a POST, so every prediction was logged as a change
  • A dashboard at / — server-rendered HTML with zero JavaScript, which is the decision rather than a shortcut: html/template contextual escaping is the XSS defence, and no JS is what lets the CSP be default-src 'none' with no script-src, so "we escaped it correctly" becomes something the browser enforces. Deploys, canaries and rollbacks are two-step — plan, then apply — and apply refuses if somebody else changed the policy while you were looking at the confirmation. A browser gets a sign-in redirect where an API client gets a 401
  • Browsers can sign in and hold a session cookie — opaque id rather than a signed identity, so logout can actually revoke it; HttpOnly/Secure/SameSite/no-Domain each closing a named hole; and CSRF double-submit as a second lock on the door SameSite already closes, because SameSite is a browser behaviour and a token the attacker cannot read is not. A session is never a weaker front door: the token is verified through the same path an API call takes, and a bearer token beats an ambient cookie
  • Sessions renew themselves and logout actually revokes at the provider. The subtlety worth naming is concurrency: two commands at once both refresh, and against a rotating provider the second invalidates the first — so refresh runs under a file lock that re-reads inside it, and eight concurrent workers produce exactly one refresh. Verified revocation by restoring the deleted credential and watching the provider refuse it
  • modelforgectl login signs a person in through the RFC 8252 native-app flow — public client with PKCE instead of a secret, state and nonce, redirect to 127.0.0.1 rather than localhost so a hostile resolver cannot capture the code, credential stored 0600. Smoke-testing it found a bug I would not have predicted: leaving the OAuth client-auth style unset makes x/oauth2 probe by retrying, and since an authorization code is single-use the probe burns it — so the error you see points at the code rather than at the real failure
  • People authenticate with OIDC, so the audit log names a person rather than a shared credential — the readable identity and the stable subject, because emails change and subjects do not. Service tokens and user logins coexist, told apart by shape. I deliberately did not write the JWT verification: a bug in the scorer is a wrong number a differential test catches, a bug in JWT verification is a total auth bypass where everything looks fine while anybody forges an admin identity. The tests prove the wiring — real JWKS over HTTP, and forged signatures, alg:none, swapped claims and wrong audiences each refused
  • Tokens rotate without a restart — read from a file re-read on SIGHUP and on a timer, swapped through an atomic pointer so the read path takes no lock and a request sees exactly one generation of the set. 400 requests across 40 live reloads, all 200, same PID. A malformed file leaves the running set in force rather than locking everyone out, and entries can carry an expiry enforced per request
  • Repeated auth failures are throttled, and scoping that claim honestly was the work: it is not brute-force protection against 256-bit tokens, it bounds the cost of failures — 200 bad-token requests now produce 6 log lines instead of 200. I first checked the limit before authenticating; the test showed that refuses a correct credential when an address is shared behind a NAT, so authentication runs first and a valid token is never throttled. The tracking table fails open when saturated, so an attacker cannot fill it to lock everyone out
  • Every command and number in the README was run against a live server before being written down, including a known limitations section — 89.9% coverage, and the suite fails rather than skips when Postgres is missing
  • Go · XGBoost · Model Serving · Canary Deployments · Shadow Traffic · Drift Detection (PSI) · Scoped Token Auth · Postgres · Prometheus

queryforge — A columnar SQL query engine written from the wire up in Rust

CI codecov

I wanted to know what actually happens between typing a query and getting rows back, so I wrote every stage of it: lexer, parser, binder, cost-based optimiser, vectorised execution engine, and my own columnar file format underneath. No sqlparser, no DataFusion, no Arrow, no Parquet — the whole workspace builds against the standard library alone, because an engine that wraps someone else's parser and someone else's format has skipped the two places the interesting decisions live.

  • The .qfc format puts a footer at the end holding each column chunk's byte range and its zone map, so a reader learns the whole layout in two seeks — which is what turns predicate pushdown from a plan rewrite into skipped I/O: 155x faster on a selective range over 500k rows, reading 3 row groups out of 62
  • Every chunk picks its own encoding by measuring the data rather than guessing from its type, because the right answer changes between row groups of the same column — RLE comes out 10x smaller than plain on a sorted column, a dictionary 4x smaller on a shuffled four-value string column
  • Constant folding deliberately stops at division by zero and integer overflow: folding those would let the plan-time answer differ from the run-time one, which is worse than not folding at all
  • A predicate is never pushed into the padded side of an outer join — doing so deletes rows that should have come back NULL-padded — and join reordering prefers a relation that has a join key over a smaller unrelated one, because an accidental cross product is not something a later choice recovers from
  • The benchmark runs every query twice, on the bound plan and the optimised one, and fails outright if the two disagree on the row count; a flattering speedup from a wrong answer is worse than no number
  • Sorting spills sorted runs to disk and merges them when the input outgrows memory, with a test asserting both paths return byte-identical output — the answer must not depend on whether the data fitted
  • 542 tests at 97% line coverage, and the README records the three bugs I actually hit, including a LEFT JOIN that dropped rows because its ON condition was applied after deciding what matched
  • Rust · SQL · Columnar Storage · Query Optimisation · Vectorised Execution · Zone Maps · Zero Dependencies

tenant-operator — A Kubernetes operator that provisions and continuously reconciles per-tenant namespaces

CI codecov

Every other project here deploys onto Kubernetes. This one extends it: a custom resource, a reconcile loop, admission webhooks and a finalizer, so that "create a tenant" becomes something the cluster API itself understands — eleven lines of YAML become a namespace, a tier-sized quota, a default-deny network policy, generated credentials, a Deployment and a Service, all kept that way.

  • The loop takes its finalizer before it creates anything, and provisions the quota and network policy before the workload — otherwise a crash orphans the namespace, or a tenant briefly runs unquota'd and reachable from every other namespace
  • Deletion blocks until the namespace is genuinely gone; owner references would delete the same objects, but asynchronously, so the Tenant would vanish from the API while its namespace was still terminating
  • It refuses to adopt anything it did not create — and inherits that rule on teardown, so a tenant pointed at another team's namespace will never delete it
  • Admission rejects a :latest image outright: a mutable tag means the spec no longer describes what is running, and no amount of reconciliation can detect that drift
  • The control loop is tested against a real kube-apiserver via envtest — provisioning, drift correction, tier upgrades, suspension, teardown — because a fake client accepts objects a real API server rejects
  • Go · controller-runtime · CRDs · Admission Webhooks · Finalizers · Kustomize · cert-manager · envtest

trust-platform — Multi-tenant identity, entitlements, and a tamper-evident audit log

CI codecov

I build identity and entitlements infrastructure for a living, so I wrote a version of it I can actually show: an OpenID Connect provider, a Zanzibar-style authorization service, and a hash-chained audit log — implemented from the protocol up, not by configuring Spring Security's OAuth support or an authorization SDK.

  • Refresh tokens rotate and carry a family id: presenting one that has already been rotated revokes the whole lineage, because the provider cannot tell the thief from the victim and shouldn't guess
  • Permissions aren't stored, they're derived — a namespace declares that viewers are editors plus whoever can view the parent folder, and a check walks that at query time, so re-parenting a document changes its access with no write against the document
  • Multi-tenancy is enforced in the generated SQL via Hibernate's tenant discriminator, and the isolation suite gives both tenants the same client id and user email so a missing predicate returns the wrong row instead of nothing
  • The audit chain catches four distinct kinds of tampering, including editing only an indexed column — verified by UPDATE-ing a real Postgres table and asserting the verifier names the exact row
  • Making the stack's host ports configurable surfaced a real coupling: the issuer was a hardcoded localhost:8081, so on a moved port the provider minted tokens claiming an issuer nothing was listening on and the authorization service refused them — correctly, since it compares iss. Static checks passed; only running the end-to-end flow caught it
  • 431 tests (92% line / 86% branch); the adversarial half covers alg:none, HS256 key-confusion forgery, PKCE downgrade, confused-deputy code redemption, and cross-tenant replay
  • Java 21 · Spring Boot · OAuth 2.0 / OIDC · PostgreSQL · Redis · Kafka · Testcontainers · Docker Compose

raftlite — Raft consensus implemented from scratch in Go

CI codecov

I built the Raft consensus algorithm from the paper — elections, log replication, snapshots, dynamic membership — and put a replicated key-value store on top, with no third-party dependencies: the wire codec, the write-ahead log and the metrics registry are all hand-written.

  • The algorithm is a pure state machine with no sockets, files or clock, so elections, log repair and snapshot installation are tested deterministically instead of by racing real processes
  • Pre-vote and a leader lease, with paired tests running the same partition both ways: with them an isolated node's term never moves, without them it climbs without bound and disrupts a healthy cluster on return
  • A chaos suite runs real nodes against real directories through random crashes, restarts and partitions, asserting one invariant — around 5,000 acknowledged writes across 40 rounds of failures, none lost
  • Two bugs only real sockets could find: a message field threaded through the algorithm but never encoded, and a lost snapshot stranding a follower forever — both fixed with tests that fail without the fix
  • A third failure was the test's fault, not the code's: the helper every test wrote through gave up on the first error from Propose, which quietly made all of them assert something Raft never promises — a leader deposed before its entry commits is an ordinary outcome the HTTP API already reports as a 503 — so it now re-resolves the leader and retries, like a real client
  • Go · Raft · Distributed Systems · Custom Binary Protocol · Prometheus · Docker Compose · GitHub Actions

PipelineOps — Polyglot job-monitoring & alerting platform

CI codecov

I built a dead-man's-switch monitoring platform for scheduled/batch jobs, across three services I chose for what each does best: a React/TypeScript dashboard, a Django REST Framework API, and a Go/Gin heartbeat-ingestion service.

  • Concurrent heartbeat ingestion in Go with context timeouts, structured logging, and Prometheus metrics
  • Replaced localStorage token auth with httpOnly session cookies + CSRF protection, closing an XSS token-theft vector
  • CI across all three services (lint + tests on every push), Docker Compose locally, Kubernetes manifests for EKS
  • React · TypeScript · Django · Go · Gin · PostgreSQL · Redis · Celery · Docker · Kubernetes

kvforge — In-memory key-value store engine, built from scratch in Rust

CI codecov

I built a Redis-shaped storage engine — RESP-inspired wire protocol, TTLs, an append-only log for crash durability — on nothing but the standard library and tokio, to understand a key-value store from the network layer down rather than wrap an existing one.

  • The AOF durability format reuses the wire protocol itself: each logged write is the same bytes a client would send, so the streaming decoder doubles as the replay parser and a crash mid-write naturally stops replay at the last whole command
  • Async tokio TCP server handling concurrent connections against one shared store; verified end-to-end by running a real server, writing over a real socket, killing it, and confirming a second server replays the data back
  • kvforge-cli (REPL + one-shot modes) routes every command through the exact same parser the server uses, so client and server can't drift apart on what a command means
  • Rust · Tokio · Async I/O · Custom Binary Protocol · GitHub Actions

SplitEasy — Expense-splitting app with automated settle-up

CI codecov

I built a group expense-splitting app (NestJS/Prisma API, React/TypeScript frontend) supporting recurring expenses, invites, and multi-type splits (equal, exact, percentage).

  • Minimum-cash-flow settle-up algorithm built from scratch with a binary max-heap
  • Refresh-token auth: short-lived JWT in memory + hashed refresh token in an httpOnly cookie
  • End-to-end test suite (Supertest against real Postgres in CI) plus unit tests across every service
  • React · TypeScript · NestJS · Prisma · PostgreSQL · Jest · Vitest · GitHub Actions · Docker

Portfolio — Personal site backed by a self-hosted GraphQL API

CI codecov

I built this site itself on Next.js (App Router), serving project data through an Apollo Server route handler backed by Supabase, with a seed-data fallback so it runs fully offline.

  • Next.js · Apollo Server · Supabase · Tailwind CSS · GitHub Actions · Vercel

DigestBot — RAG chatbot over a rolling window of RSS/changelog feeds

CI codecov

I built a retrieval-augmented chatbot that answers questions over live RSS/changelog feeds instead of a static corpus, to explore the parts of RAG most tutorials skip: freshness, dedup, and incremental indexing.

  • Hybrid retrieval blending vector similarity with a recency-decay weight, so answers favor fresh articles without ignoring older ones
  • Forced citations on every generated answer, checked against a hand-built evaluation set to catch regressions
  • Incremental ingestion pipeline: poll → dedup by GUID/URL → chunk → embed → index, run continuously against a rolling feed window
  • Python · RAG · Vector Search · Anthropic · pytest · GitHub Actions

Risk Signal Platform — Event-driven transaction risk-scoring platform

CI codecov

I built three Spring Boot services (transaction-api, risk-scoring-service, alert-service) that talk to each other only through Kafka, each owning its own MySQL database, with a full observability stack (Prometheus/Grafana, ELK) actually wired up and working.

  • Kafka-native retry/DLT for failed alert dispatch via @RetryableTopic, verified by forcing a failure and asserting it lands on the dead-letter topic
  • A real custom Micrometer business metric (risk_scores_total) driving the main Grafana dashboard panel, not just generic JVM stats
  • Idempotent, durable-write-then-best-effort-publish event handling across all three services, backed by Testcontainers integration tests
  • Java · Spring Boot · Kafka · MySQL · Flyway · Prometheus · Grafana · Elasticsearch · Kibana · Docker · Kubernetes

Order Processing Platform — Event-driven order processing on Go

CI codecov

I built a REST API (order-service) that persists to Postgres and publishes to SNS, fanning out over independent SQS queues to two consumers — one writing reservations to MongoDB, one logging simulated notifications — neither aware the other exists.

  • Handler tests run against interfaces the code defines itself (OrderStore/EventPublisher), not concrete Postgres/SNS types — zero network calls, including a test asserting a publish failure still returns 201
  • Raw SNS→SQS delivery and at-least-once handling: consumers only delete their SQS message after the write succeeds
  • Terraform for the AWS ECS Fargate path (reusable service module + least-privilege IAM); Docker Compose with LocalStack for local dev
  • Go · PostgreSQL · MongoDB · AWS SNS · AWS SQS · Terraform · Docker Compose · Prometheus · GitHub Actions

gRPC Catalog Platform — Two Go services over gRPC and REST from one implementation

CI codecov

I built catalog-service to serve one ProductService implementation over both native gRPC and REST (via an in-process grpc-gateway mount, no self-loopback network hop), calling pricing-service's internal-only PricingService over gRPC for quantity-based pricing.

  • Both services share generated code from one buf-managed proto module so the wire contract can't drift between them
  • Request-ID propagation across the gRPC boundary so both services' logs correlate for one end-to-end request
  • Tests run the gRPC server on an in-memory bufconn listener, including a fake PricingServiceServer double for testing the cross-service call without a real network
  • Go · gRPC · Protocol Buffers · grpc-gateway · buf · Docker Compose · GitHub Actions

Ledger Strangler Platform — Legacy monolith strangled into microservices behind a YARP facade

CI codecov

I built a legacy core-banking monolith and strangled it into microservices one route at a time behind a YARP facade — AccountsService (Cassandra-backed) is already peeled off, Statements deliberately isn't yet, and I left that gap honest instead of faking a seamless migration.

  • Balance updates go through a Cassandra lightweight transaction with jittered backoff, verified under real concurrent writers, not just the happy path
  • NotificationsService reacts to a RabbitMQ event the legacy code was never able to produce — the first behavior that only exists because of the migration
  • Serilog → Filebeat → Logstash → Elasticsearch → Kibana across all four services; Terraform for AKS/ACR, ArgoCD watching the manifests directly for GitOps deploys
  • Shipped with a real git-flow history — feature branches merged via PR, a tagged release, and two hotfixes for issues found while testing and in CI, not one commit on main
  • C# · .NET Core · YARP · PostgreSQL · Cassandra · RabbitMQ · Serilog · ELK Stack · Terraform · Kubernetes · ArgoCD · GitHub Actions

advisor-match-service — AI-based client-advisor matching API

CI codecov

I built this to work back through an AI-based matching system the way I first built one during an internship, this time with real infrastructure behind it: a pandas/numpy cleaning pipeline, PyTorch/Hugging Face sentence embeddings, and hybrid Elasticsearch search (kNN blended with BM25) behind a Flask API.

  • A hand-labeled eval harness measures real relevance against the current index — 90% hit@5, MRR 0.90 on the seed dataset, not an assumed number
  • Caught a genuine race condition smoke-testing the real docker-compose stack: two Gunicorn workers both trying to create the Elasticsearch index on boot
  • Terraform for GCP Cloud Run + Artifact Registry
  • Python · Flask · PyTorch · Hugging Face Transformers · pandas · NumPy · Elasticsearch · Docker · Terraform · GitHub Actions

🎓 Certifications & Education

  • AWS Certified Solutions Architect – Associate
  • Certified Kubernetes Application Developer (CKAD)
  • Oracle Certified Professional, Java SE Programmer

M.S. Computer Science — University of Massachusetts, Boston (2022 – 2024) B.E. Computer Engineering — University of Mumbai (2017 – 2021)


📊 GitHub Stats & Activity

Sahil's GitHub stats Sahil's GitHub streak stats
Top languages
Contribution snake animation

📫 Reach me at sahilkal717@gmail.com or on LinkedIn

Pinned Loading

  1. digest-bot digest-bot Public

    RAG chatbot over a rolling window of RSS/changelog feeds — hybrid recency-aware retrieval, forced citations, and a hand-built eval set

    Python

  2. expense-splitter expense-splitter Public

    SplitEasy — group expense splitter with equal/exact/percentage splits, a greedy min-cash-flow settle-up algorithm, and self-posting recurring bills — NestJS, Prisma, Postgres, React

    TypeScript

  3. kvforge kvforge Public

    In-memory key-value store engine in Rust with a Redis-inspired wire protocol, AOF persistence, and a CLI client

    Rust

  4. PipelineOps PipelineOps Public

    Polyglot job-monitoring and alerting platform — Django control plane, Go ingestion service, React dashboard, Prometheus metrics on Kubernetes

    TypeScript

  5. raftlite raftlite Public

    Raft consensus implemented from scratch in Go — leader election, pre-vote, log replication, snapshots, and dynamic membership behind a replicated KV store with an HTTP API and cluster CLI

    Go

  6. risk-signal-platform risk-signal-platform Public

    Event-driven transaction risk-scoring platform on Java, Spring Boot, and Kafka

    Java