Open to opportunities Batumi, Georgia · remote, open to relocation

Magomed Bonkurov

Senior Java Developer 6 years 5 months of commercial backend development

I write and ship backend services in Java and Spring. For the last three and a half years that has been Sberclass, an education platform where almost all the load lands in the first minutes of a lesson. I work on the things that usually hurt on projects like this: async integration over Kafka, heavy PostgreSQL queries, load testing, and digging into what broke in production. The change I point to most often: assignment delivery went from 1.9 s to 260 ms at p95.

  • 6.5 years in commercial Java
  • ~4 years at senior level
  • ~700 RPS after optimisation
  • Java 8→17 took part in the migration
Scroll down

01 About

About

I am a backend developer working in Java. I started with CRUD modules and reports. These days a service is mine from end to end: the data model and contracts, how it behaves under load, and figuring out what broke overnight.

What I enjoy most is digging in when a system suddenly behaves in a way I did not expect. A downstream service goes down — what happens to my endpoint now. p95 triples after a release that looked harmless. Memory leaks somewhere, and a consumer processes the same event twice. Almost every backend decision is a trade-off, and I try to know up front what I am paying: sometimes I give up strict consistency for availability, sometimes simplicity for speed. I would rather say the downsides out loud early than have them show up in production.

I have worked on legacy systems and on projects from scratch. When I join an unfamiliar codebase I move carefully — tests and metrics first, changes after — instead of rewriting everything. I review code, mentor junior and middle developers, and take part in technical interviews.

My English is a solid B1. I speak and write it, read documentation and source code freely, and handle written communication and code review in English.

02 Technology stack

Technology stack

Language & platform

Frameworks

Data & storage

Integration & APIs

Testing & load

Infrastructure & observability

Hover over a technology or tap it — I explain briefly what it is and how I use it.

03 Experience

Experience

  1. March 2025 — Present

    Senior Java Developer @ Kub

    Software development and systems integration company, remote. Engineering department of ~25 people. I own the backend of the request and billing platform, 3 services in my area of responsibility, ~450 RPS at peak.

    • Replaced synchronous REST calls between the orders and billing services with asynchronous messaging over Kafka: transactional outbox on the producer side, idempotent consumer with deduplication by event key. Events are no longer lost when a downstream service is unavailable. I deliberately traded strong consistency for eventual consistency — a distributed transaction was rejected because of the coupling and the cost of maintaining it.
    • Made the main endpoint faster: using pg_stat_statements and EXPLAIN (ANALYZE, BUFFERS) I found an N+1 query and a missing index, replaced the loop with a batch fetch and added a composite index — p95 dropped from 820 ms to 190 ms, and peak database CPU fell by ~35%.
    • Wrapped calls to external systems in Resilience4j (circuit breaker, timeout, retry with exponential backoff): a downstream outage no longer showed up as hung threads and a growing request queue — the service now fails fast instead of holding the connection open.
    • Designed monthly partitioning for the events table (~300 million rows) and moved nightly aggregates into a materialized view — reporting queries stopped competing with OLTP traffic and report generation went from 4 minutes to 40 seconds.
    • Review code, mentor junior and middle developers, and take part in technical interviews.
    Stack
    • Java 17
    • Spring Boot
    • Kafka
    • PostgreSQL
    • Redis
    • Docker
    • REST
    • GraphQL
    • Resilience4j
    • JMeter
    • Grafana
    • Kibana
    • GitLab CI/CD
    • Gradle
    • Flyway
  2. October 2022 — February 2025

    Senior Java Developer @ Sberobrazovanie

    SberClass, a national education platform. I moved from the contractor (Haiku dev) into the client's own product team, staying on the same product with a wider area of responsibility.

    • Moved 5 heavy queries from JPA to JdbcTemplate where mapping into an entity graph added no value; I located the hot spots with pg_stat_statements and EXPLAIN (ANALYZE, BUFFERS), added composite indexes and moved nightly aggregates into a materialized view — building a class performance report went from 90 s to 12 s.
    • Cached user profiles and reference data in Redis (cache-aside, 15-minute TTL with invalidation on change events) — this removed ~40% of reads from PostgreSQL during school hours and made p95 on profile endpoints stable regardless of the peak.
    • Ran load testing in JMeter: the ceiling was ~350 RPS, and the first things to fail were the HikariCP pool and a synchronous call to the content service. After enlarging the pool and making the call asynchronous the service held ~700 RPS at p95 below 400 ms.
    • Built the GraphQL layer for client applications: removed N+1 in resolvers by batching through DataLoader and limited query depth and complexity — database round-trips per card dropped from dozens to 2–3.
    • Implemented REST APIs for the assignments, progress and test-results modules, and moved authentication to Keycloak (OAuth2/OIDC, JWT) with a student / teacher / administrator role model; contracts were described in OpenAPI.
    • Covered the module with integration tests on Testcontainers (real PostgreSQL and Kafka) to ~75% of the critical code; reviewed ~15 pull requests a week and onboarded two developers.
    Stack
    • Java 11 → 17
    • Spring Boot
    • Spring Data JPA
    • JdbcTemplate
    • PostgreSQL
    • Kafka
    • Redis
    • Keycloak
    • GraphQL
    • REST
    • Docker
    • Bitbucket
    • Bitbucket Pipelines
    • Testcontainers
    • JMeter
    • Grafana
    • Kibana
    • Flyway
  3. October 2021 — September 2022

    Java Developer @ Haiku dev

    Contract development on the SberClass product: an outstaffed team of 6 people working inside the content and assignments module.

    • Found a memory leak in the content service from a heap dump: an unbounded cache was holding references to user session objects. After the fix the service stopped hitting OOM roughly every two days and the nightly restarts were removed.
    • Delivered 8 features in the assignments module end to end: REST controllers and GraphQL resolvers, schema migrations, tests, deployment to dev and staging, and acceptance with the analyst.
    • Investigated production defects through Kibana logs. The hardest one: a double submission by a student created a duplicate result — I closed it with a unique index on the business key and an idempotency key on the request, so a repeated submission now returns a conflict with the current state.
    • Wrote ~80 unit and integration tests with JUnit and Mockito and removed ~15 flaky tests — the CI pipeline stopped failing for no reason and releases were no longer delayed by a red build.
    Stack
    • Java 8/11
    • Spring Boot 2.x
    • Spring Data JPA
    • JDBC
    • Caffeine
    • Eclipse MAT
    • PostgreSQL
    • GraphQL
    • REST
    • Maven
    • Bitbucket
    • JUnit
    • Mockito
    • JMeter
    • Kibana
  4. November 2019 — April 2021

    Java Developer @ ChemCool

    An education portal for secondary school students (knowledge checks and subject tests): 4 services, a team of 5 people, running in production with a few thousand users.

    • Implemented the backend of the client-facing service in Spring Boot: ~30 REST endpoints (topic catalogue, taking a test, returning results) with PostgreSQL access through Spring Data JPA and Hibernate.
    • Set up event exchange between services through Apache Kafka: test results were sent to the statistics service asynchronously, so the calculation no longer blocked the user response and traffic spikes after lessons stopped taking the main service down.
    • Implemented authentication and authorisation with Spring Security: JWT login, role separation, and restricted access to other students' results.
    • Covered my modules with unit and integration tests to ~65% of the critical code — a recurring regression disappeared from releases.
    Stack
    • Java 8
    • Spring Boot
    • Spring Security
    • Hibernate / JPA
    • HQL
    • PostgreSQL
    • Apache Kafka
    • REST
    • Maven
    • Git
    • JUnit
    • Mockito
    • OpenAPI
    • React (basic)

04 Case studies

Case studies

Three problems I go through in the most detail during interviews. What matters here is not which tools I used, but what the options were, what I weighed, and what I paid for the decision.

A class performance report took a minute and a half

SberClass · Sberobrazovanie · 2023

Problem

A teacher opened the class summary and waited a minute and a half. During school hours these requests came in bursts and ate CPU on the main database, and assignment delivery — the main thing students actually use — slowed down along with it. The complaints were vague: the platform is laggy, with nobody pointing at the report.

What I did

  • Found the real source of load instead of guessing: I took pg_stat_statements and sorted by total time. The top rows were not the report itself but 5 queries around it.
  • Ran them through EXPLAIN (ANALYZE, BUFFERS). A classic N+1: every student triggered a separate query for results, and on top of that Hibernate loaded a full entity graph when only three fields were needed.
  • Where mapping into entities added nothing, I moved the queries to JdbcTemplate with a flat projection. This was not “dropping the ORM” — I replaced exactly the places where the ORM was in the way.
  • Added composite indexes matching the actual predicates, and verified that the planner really used them instead of ignoring them.
  • Moved nightly aggregates into a materialized view refreshed on a schedule. Data that is up to a day old is an acceptable price here: a report about a past period does not have to be real-time.

Result

The report now builds in 12 seconds instead of 90. The bigger win was not the report itself: the heavy analytical queries stopped competing with live traffic, and the complaints about lag during school hours went away.

Events were lost whenever a downstream service was down

Kub · 2025

Problem

The orders service called the billing service synchronously over REST. When billing was down, the order was saved but the charge was not, and the mismatch only surfaced a day later in reconciliation, where someone fixed it by hand. Retrying inside the same method did not help: the service could die between committing the order and the retry.

What I did

  • Rejected a distributed transaction: it would have tied the two services into one release cycle, and the cost of maintaining two-phase commit was not justified here.
  • Used the transactional outbox pattern: the event is written to an outbox table in the same transaction as the order. Atomicity comes from the database itself, not from application code.
  • A separate relay reads the outbox and publishes to Kafka. The message key is the entity id, so all events for one order land in the same partition and keep their order.
  • Kafka delivers at-least-once, so duplicates are unavoidable. The consumer keeps a processed_events table and drops repeats by event key, which makes processing idempotent.
  • Anything still failing after 5 attempts goes to a dead letter queue for manual review, instead of looping forever and blocking the partition.

Result

Now a billing outage no longer corrupts the data: events sit in the outbox and arrive once the service comes back. The manual fixes after reconciliation stopped. What we pay for this is that the data is consistent with a delay rather than instantly — we agreed that with the business up front, and for this case the delay is fine.

The service hit OutOfMemoryError every couple of days

SberClass · Haiku dev · 2022

Problem

The content service crashed with OutOfMemoryError about every two days. The team just restarted it on a nightly schedule and lived with that; nobody was chasing down why it leaked. It could not be reproduced on dev, where the traffic simply was not there.

What I did

  • Enabled -XX:+HeapDumpOnOutOfMemoryError, waited for the next crash and got a dump with real data instead of guesses.
  • Analysed the dump in Eclipse MAT: the dominator was a hand-written cache built on ConcurrentHashMap, with no size limit and no TTL. It held on to user session objects, so the garbage collector could never reclaim them.
  • Replaced it with Caffeine using maximumSize and expireAfterWrite, and checked that eviction left no strong references behind in listeners.
  • Ran the service under load and watched the heap after several full GCs: previously the baseline crept upwards between collections, now it returns to the same level. I also added an alert on old gen usage to catch any recurrence early.

Result

The crashes stopped, and we dropped the scheduled nightly restart. GC pauses got shorter and steadier too, because the collector no longer had to walk a live set that kept growing.

05 System architecture

System architecture

The two diagrams I draw most often in interviews. Both are about how the system behaves when something breaks — not when everything works.

Service topology of the education platform
JWT validation REST REST events subscribes Web client React Mobile app API Gateway routing, rate limit Keycloak OAuth2 / OIDC BFF GraphQL Tasks service Spring Boot Progress service Spring Boot Kafka learning events PostgreSQL monthly partitions Redis profile cache Statistics service computes progress
  • Client
  • Service
  • Storage
  • Broker (async)
  • External system

Why a BFF instead of direct service calls. Web and mobile need different shapes of data: the client assembled a lesson card from 4–6 calls and paid for it in network latency. The GraphQL layer composes the response server-side, so the client makes one request. The cost is another layer to version and to protect from expensive queries (depth and complexity limits). Why progress goes through Kafka rather than a synchronous write. Recording progress must never break the lesson: if analytics is down, the student should not notice. I traded strong consistency for eventual — which means the consumer needs idempotency, and that is the diagram below.

Reliable event delivery: transactional outbox
commit same commit poll publish at-least-once check after N retries Producer service Domain tables PostgreSQL Outbox table same transaction Relay polls and publishes Kafka topic key = entity id Consumer processed_events event key DLQ manual review
  • Service
  • Storage
  • Broker (async)

The problem. Persist the data and emit the event so you never end up with “in the database but not in Kafka”, or the reverse. Publishing right after commit does not help: the service can die between the commit and the publish. The solution. The event is written to an outbox table inside the same transaction as the data — the database itself provides atomicity. A separate relay reads the outbox and publishes to Kafka. The cost. Kafka gives at-least-once delivery, so duplicates are unavoidable: the consumer keeps a processed_events table and drops repeats by event key. Anything still failing after N retries goes to a DLQ instead of looping forever and blocking the partition.

06 Contact

Open to senior Java roles

I work remotely and I am open to relocation. I am based in Batumi but keep Moscow time, so I overlap with teams in Russia through the whole working day. The fastest way to reach me is Telegram — I usually reply within an hour. Happy to do a take-home task and a technical interview.