Poonkawin.dev - Engineering Journey — Poonkawin Saravanan — poonkawin.dev
Engineering journey · six chapters

Building a production platform, one problem at a time.

Three years inside one multi-tenant rebate platform. Each chapter is a problem I met, the decision I made, and what it measured out to.

3 yrs
one platform
6
chapters
60s → <5s
api response time
1
django engineer on it
Live in production

Multi-Tenant Rebate Platform

A group-buying SaaS handling rebate and incentive management across tenants. I am the only Django engineer on it.

Timeline
2023 → Present
Role
Sole backend owner
Tenancy
Schema-per-tenant
Stack
PythonDjangodjango-tenantsPostgreSQLRedisOpenSearchStripeDockerAWS
UsersCloudflareNginxDjangoTenant Middlewaresearch_path switchBusiness LogicRedisPostgreSQLBackground WorkersAI Services
The chapters

Each problem was harder than the last.

Many clients, one codebase, and a hard requirement that no tenant ever sees another tenant's data.

Architecture · request routing
tenant-atenant-btenant-cdjango-tenantsschema_aschema_bschema_c
Engineering decision
Decision

Schema-based tenancy on one PostgreSQL database, via django-tenants.

Reason

Isolation at the database level, without a database or deployment per client.

Trade-off

Every migration has to run across every schema. Migrations get heavier as tenants grow.

Outcome

One deployment, one codebase, and leakage prevented structurally rather than by review.

Challenges met along the way
Tenant isolation
A single missed filter in shared-table tenancy leaks another client's data.
Request routing
The right schema has to be selected before any query runs, on every request.
Migrations at width
Schema changes must apply across every tenant schema, not just one.
DB-level
Isolation boundary, not code-level
1 codebase
Serving every tenant
1 deployment
No per-client infrastructure
Lesson

Isolation you can see in the schema is easier to trust than isolation enforced in code.

Platform evolution

Not more features. More maturity.

Each stage changed what the platform could safely be asked to do next.

v12023
Core platform
Inherited a client-built backend with no standards and no consistency.
starting point
v22024
Standards
Standardized the codebase incrementally, alongside feature delivery.
unlocked: safe change
v32024
Payments
Stripe integration with verified, idempotent webhook handling.
unlocked: revenue
v42024
Incentives
The rebate engine, calculating inside atomic transactions.
unlocked: core business logic
v52025
Performance
Four root causes fixed together — 60s down to under 5s.
unlocked: usable at scale
v62025
Search
OpenSearch with one index per tenant, correctly sized.
unlocked: global search
v72026
AI workflows
AI-assisted transformation behind a validation gate.
unlocked: automation
Engineering timeline

Every stage, the problem it solved.

Jan — Jun 2023Intern

Trainee and employee records were tracked by hand.

Built the internal management system that replaced it — my first production Django codebase.

Jul 2023 — Jul 2024Junior Developer

Amazon sales data arrived faster than anyone could process it.

Built the SP-API integration and crawlers on FastAPI, moving large volumes of e-commerce data asynchronously.

Jul 2024 — PresentPython Developer

A live client platform nobody could safely change.

Standardized the entire backend as sole Django engineer, then built payments and incentives and took endpoints from ~60s to under 5s.

NowAI developer tooling

The team had no shared way to adopt AI tools.

Led the org-wide evaluation and set how the tooling gets used — every generated line reviewed.

Ecosystem

Four layers, one system.

BE
Backend
request handling
PythonDjangoFastAPIDRFCelery
IN
Infrastructure
delivery & runtime
DockerNginxGunicornAWSCloudflare
DB
Database
state & search
PostgreSQLRedisOpenSearchMongoDB
AI
AI
augmentation
ClaudeOpenAIMCPCursor
Lessons

What three years actually taught me.

01

Four things made one endpoint slow. Fixing three of them changed nothing.

Performance work is rarely a single fix. Until every contributing cause is gone the number barely moves — which is why you measure after each change, not only at the end.

02

Isolation you can see in the schema is easier to trust than isolation enforced in code.

A tenant filter is one forgotten WHERE clause away from a leak. A separate schema is a structural guarantee, and the migration cost is a fair price for it.

03

A standard only survives if adopting it does not require stopping feature work.

I proposed refactoring incrementally instead of a rewrite. That is why it shipped, and why the client kept it without changes.

04

Infrastructure sizing is part of the design, not an afterthought.

Search returned correct results in 90 seconds. The code was right; the heap was too small. A working feature on undersized hardware is still a broken feature.

05

Idempotency is not a payments detail — it is what every retry in the system assumes.

Once you have handled a webhook that arrives twice, you start seeing the same problem in queues, crawlers and background jobs.

06

Backward compatibility buys more goodwill than the feature you are shipping.

Adding payments to a live platform meant nothing existing was allowed to change behaviour. The constraint shaped the design more than the requirements did.

07

Developer experience is a product feature that ships to your team.

Cutting CI from 45 minutes to 5 changed how often people pushed. No user saw it; every engineer felt it.

08

Reviewing generated code is the work. Generation was never the bottleneck.

AI tooling made me faster at typing and no faster at deciding. The judgement still has to come from somewhere, so I read every line.