Jongin Kim

한국어

Backend and systems software engineer · Portfolio

Education
B.S. in Computer Science, KAIST (expected February 2027)
Experience
Lucentblock, Dec 2018 – Dec 2023 · Backend for a financial platform, including about two years (2020 – 2021) building a matching engine in Rust
Sahoipyoungnon, Jan – Mar 2026 · Freelance backend developer (Java/Spring, rebuilding a learning platform)
Languages
The work below was done in Rust, TypeScript, Java, JavaScript (Node.js) and Python. I used C in university coursework.
Case 1

Choosing the orderbook data structure: no reallocation at runtime

2020 · Rust · Orderbook design and implementation

Order price
→
Tick index
→
Pre-sized Vec
price slots 0 … N
→
Per-price order queue
first in, first matched
① Array + order queue chosen
② B+ tree + flyweight
③ Dense hash set
A price maps to an index into an array slot, and orders at the same price are handled in arrival order. Below are the three options compared.

Problem

  • A matching engine reads and updates the orderbook on every order, so the data structure directly affects latency.
  • Rebuilding the engine in Rust meant choosing the orderbook structure again.
  • Reallocation at runtime or scattered memory access can make latency unpredictable.

Solution

  • Studied C++ orderbook implementations and compared three options: array + order queue, B+ tree + flyweight, and dense hash set.
  • Investigated how a local allocator affects insert, delete, cancel and reduce, and raised memory fragmentation as a team discussion item.
  • Because hardware fetches memory in cache lines, a contiguous array was the better fit. Prices map to tick indices, and a pre-sized Rust Vec removes reallocation at runtime.

Result

  • Wrote tests for the orderbook's public functions and scenario tests, and merged them with the implementation.
  • Those tests caught two defects, which I fixed: the best price not updating after an order was removed, and zero-quantity entries leaking into change messages.
Case 2

Designing a benchmark tool to find the engine's limits

2021 · TypeScript, Python, Kubernetes, Kafka, Elasticsearch · Tool design and implementation

Test config
→
Benchmark tool
controls Kubernetes
→
Load workers
→
Kafka
→
Matching engine
CPU and memory limits
↓
Per-stage log collection
→
Automatic verdict
throughput, availability, restarts, errors
→
Report and dashboards
The tool generates load and varies the engine's resources, then collects logs, checks them against criteria and writes a report.

Problem

  • There was no way to check repeatedly, under the same conditions, how much load the engine could take or what a code change made better or worse.
  • Manual measurement changed the data, resources and measured window every time, so results were hard to compare.

Solution

  • Designed a tool that runs on Kubernetes. Load tests add workers step by step, stress tests vary the engine's CPU and memory limits, and performance tests pre-load random orders for the engine to process.
  • Throughput, availability, restarts and critical errors are judged automatically against predefined criteria, and each run leaves a report linked to dashboards.
  • When some latencies came out negative, I checked the measurement data before suspecting the engine. The causes were lost start or end logs for a stage, duplicates from the engine re-processing the same Kafka messages after a restart, and the aggregation query picking the wrong timestamp among several logs. I filtered out samples whose start and end log counts or uniqueness did not match, and changed the query to use the earliest log as the start time. On re-checking, the negative stages I had found were gone.

Result

  • Each deployment's changes and results were recorded together, so runs could be compared.
  • Compared repeated runs before and after changing the async runtime's channel with a t-test, and reported both the stages that improved and the stage that got worse. The two runs had different log-loss rates, so I did not draw a throughput conclusion from that data.
  • Later the same method caught a performance regression, and re-running under different resource settings ruled out the environment first.
  • Measuring repeatedly under the same conditions with this tool and removing bottlenecks one at a time, I raised throughput from around 100 to about 38,000 per second by the end of the 2021 optimization work.
Case 3

A compliance ledger module and DB-based migration

2022 · Java, Spring Kafka · Module design, implementation and migration

Service events
→
Message broker
→
Ledger module
Spring Kafka listener
→
Internal blockchain ledger
↑
Historical data in DB
→
Migration
rebuilds messages
→
On failure, stop ordered processing
Live events come from the broker. Historical data no longer in the broker is rebuilt from the DB and sent through the same module.

Problem

  • For compliance, key transaction history had to be recorded on an internal blockchain ledger.
  • Existing data had to be recorded too, but older events had already passed the broker's retention period.

Solution

  • Studied how Spring Kafka listener containers, partition assignment and offset reset work, then built a handler per event type.
  • Designed a migration that rebuilds messages from the DB, triggered when partitions are assigned.
  • If the migration fails, ordered processing stops, so records never continue out of order.

Result

  • Testing surfaced two problems: circuit breaking did not work when partitions were assigned across multiple threads, and the migration could run twice.
  • Discussed the migration strategy with the team, documented it, and rolled it out to production.
Case 4

Switching deposit notifications from a process per event to ZMQ subscriptions

2019 · Node.js, ZeroMQ, MySQL, Docker · Design, implementation and testing

Blockchain node daemon
ZMQ publisher
→
Notifier
subscriber on the same host
→
DB
deposits, blocks
→
Internal server
New block arrives
→
Compare with stored hashes
→
Find the fork point
unconfirmed blocks only
→
Re-sync blocks and deposits
Top: subscribe to the daemon's transactions and blocks and forward only what's needed. Bottom: when the chain forks and diverges from the DB, find that range and repair it.

Problem

  • Each deposit event launched a new process running a script that sent an HTTP request. As the number of managed addresses grew, spawning processes could use a lot of memory.
  • The server had to make many RPC calls to the daemon to exchange information.
  • A chain fork could leave the blocks stored in the DB out of sync with the actual chain.

Solution

  • Ran a Notifier on the daemon's host that subscribes to transactions and blocks over ZMQ pub/sub, cutting traffic between servers.
  • Implemented four features: recording and forwarding deposits, forwarding blocks that reached the confirmation count, recording which block a deposit was mined in, and repairing divergence between the DB and the chain.
  • Repair loads only the blocks within the confirmation count from the DB and stops calling RPC once it finds the matching point. I also fixed a hash mismatch caused by the network buffer's little-endian byte order.

Result

  • Tested each of the four features against a test daemon and server in Docker, and documented the results.
  • Reproduced a chain fork and confirmed that, on the next block, the Notifier found the diverged blocks and re-synced their hashes and deposits.