> I am Panos,
a software engineer and systems architect in Athens, Greece. For 20+ years I've built software that stays up and stays fast. Most of it went into systems that were already running and couldn't stop to make room for it, so the work tends to look like replacing the foundation while the building stays occupied, customers and all.
The work is rarely small. It's usually what a small team would spend half a year on, and I'd rather take it on whole. How that goes depends on the job. Some of it I deliver on my own, head down. Other times I join the team and we build it together, or I own the architecture and set the direction everyone else builds from. The role changes with the problem, but the size of it is never the part that puts me off.
I work through my consulting firm, NEFELOMA.
You may have used software I've worked on if you've:
- secured your organization's PKI via Smallstep's open source, on-prem, or hosted products.
- deployed on Fly.io.
- run a site on Pagely's managed WordPress hosting (acquired by GoDaddy).
- ridden a Spin scooter or e-bike.
Some samples of my work:
A common language ridetime
A micro-mobility operator tasked me with bringing their rebel scooter nation to heel, which was no small feat: the rebels were already out on the streets, tens of thousands of them, in several makes of hardware, each speaking its own protocol.
With the Rails backend swamped with the noise and wired deep into every rebel's make, model & protocol, I saw an opening to simplify. The backend could speak one language no matter the controller on the other end, so I wrote a set of Go servers, one per protocol, each translating a vehicle's native chatter into it. Then I refactored Rails to speak only that and stay blind to the hardware underneath.
All of it landed with the fleet roaming the US.
A memory-backed tenant cache
A production Postgres kept breaking, hammered with reads on its hot path. Caching was the obvious fix, with invalidation the scary beast to manage, so the team was careful about it and with good reason. There was talk of heavier machinery, Kafka and friends, which felt like too much to me. Talk is easy though, so I built a prototype the team could pull apart, and we went through it over a few technical sessions. The dataset was small and stable enough to sit in memory, so invalidation could be driven straight off the Postgres replication log. It came out ahead of every alternative on cost, on how far it scaled, and on how much there was to maintain.
Peaked at 300k RPS on two commodity vCPUs, eliminating the incidents.
A port of a TypeScript monolith
A small pile of legacy TypeScript services, bundled into one NestJS monolith, misbehaved under load and was hard to run on-prem. I laid the groundwork to port them into a fully instrumented and optimized Go monorepo: a simple data access layer that minimized roundtrips and eliminated excess queries, plain hand-crafted SQL that both the query planner and people could reason about, and bare-metal CLI tooling that makes them a joy to manage.
I ported the services one at a time. Each went out first as a passthrough proxy that forwarded every call to its TypeScript upstream. That step alone gave those services their first metrics & traces, taken from production traffic before a line of logic moved. The Go implementation landed behind the proxy later, in increments.
The callers never knew the port happened.
A composable & observable Go framework
I was tasked with bringing end-to-end observability to a distributed system spanning around 30 gRPC & HTTP Go services that exported nothing more than stock Kubernetes metrics (CPU, RAM, etc.) and non-uniform logs. Instrumenting them one by one would have given thirty different answers, drifting apart with every release. So instead of instrumenting services, I changed how they get built.
I implemented a framework that builds a program out of named components. It runs each one, wires up their health, startup & readiness probes, and owns the lifecycle down to graceful shutdown. One import and a few lines, and that program traces, measures, logs, and behaves like every other one in the system. It covers HTTP and gRPC on both the server and the client side, so a request stays traced across every hop, the client end included, which most setups leave dark.
Nobody wires OpenTelemetry in or thinks about observability anymore: both were solved once, in the framework.
A registry-free protobuf pipeline
The services in a distributed stack had drifted onto contracts that no longer
matched. I put every protobuf definition in one repository, then built a pipeline on
top of git and GitHub Actions that generates each language's client
library and publishes it to its own repo, so consumers pull them like any other
git dependency and there's no registry to run.
Contracts now ship as commits to that one repository, and every caller has a
matching client on the next CI run, git log included.
A homegrown developer experience toolkit
Every engineer on the team develops against their own Kubernetes cluster. The Python tool that set one up made you tear the whole loop down to change which version of a service you were running, so trying a colleague's branch cost you whatever you had going.
I replaced it with a Go one, on a new design. Each project can be pointed at its catalog default, at a branch, or at a directory on your machine, and only that project rebuilds while the rest of the stack keeps serving. Projects load and build on their own too, so a slow one holds nobody up and a broken one doesn't take the session down with it.
Trying a feature now costs a terminal command:
λ [redacted] link <project> git|dir <REF|DIR>
$ Happy to take on the service that's failing under load, the migration that keeps getting deferred as it's never the right time, the new system that has to tie in with everything else, or whatever else I can help you get done. I'm at [email protected].