whoami

I am Panos,

a software engineer and systems architect in Athens, Greece. For 20+ years I've done one kind of work: replacing the foundation of a system while the building stays occupied, customers and all.

That kind of work is rarely small or palatable. It's the scope a team would normally split across half a dozen people for half a year, and I take it on whole. Sometimes that means heads-down, delivering it alone. Sometimes it means joining the team and building it with them. Sometimes it means owning the architecture: setting the direction everyone else builds from there on. The role fits the problem; the appetite for scope doesn't change.

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 shapes of my work:

Memory-backed tenant cache

Recurring production incidents traced to Postgres being hammered on hot-path reads. Caching was the fix, with invalidation the obvious risk: the team was cautious, and for good reason. Heavier machinery (e.g. Kafka) was floated, which seemed overkill to me, but talk is cheap, napkin math only goes so far, and few plans survive contact with production. So I built a prototype the team could reason over, across deep technical sessions: the dataset, small and stable, held in memory, with invalidation driven straight off the Postgres replication log. It beat every alternative on cost, scalability, and maintainability. 300k RPS on two commodity vCPUs. The incidents stopped.

Backend platform

It began as a monolith: many TypeScript services bundled in one repo, shippable only as a unit. I ported it to Go as a monorepo and made it the substrate the team now builds every new backend service on. The cherry on top: the same code ships two ways. The services deploy independently, as we run them, and also compose into a single monobinary a customer runs on bare metal. One codebase, hosted or on-prem, with no second build to keep in sync. And leaner: each small Go pod retired five large TypeScript ones, so the platform got cheaper to run as it grew.

Composition framework

You describe a program as a set of named components, and the framework brings them together: running each, wiring their health, startup & readiness probes, and owning 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 spans HTTP and gRPC, server and client alike, instrumenting either side, so a request stays traced across every hop, the client end included, which most setups leave dark. Nobody reasons over wiring or observability anymore; the cost is conformity, which was the entire point.

gRPC contract management

The services in a distributed stack had drifted onto incompatible gRPC contracts. I built one repository as the source of truth for every protobuf definition, with a git-based pipeline that generates each language's client library and publishes it to its own repo; consumers pull them as ordinary git dependencies, with no registry to run. A service now ships by committing its proto, and every caller has a matching client on the next CI run.

IoT vehicle abstraction layer

The fleet was live: a mix of vehicle hardware, each speaking its own protocol, and a Rails backend wired into every one of them. It didn't have to be. I realized the backend could speak a single language regardless of what was on the road, and built 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, blind to the hardware underneath. All of it landed without taking the fleet down.

Developer experience CLI

Every engineer develops against their own Kubernetes environment. The old tool made you tear down the whole loop to change which version of a service you were running, so trying a colleague's branch meant stopping your own work and starting over. I rewrote it in Go from scratch to make switching live: name a branch, and it applies at once. Someone in Slack wants their branch tried? Send the name. No checkout, no clone, no restart.

On the way past

I fix what I trip over, asked or not.

  • A production query taking 75 seconds. I rewrote it around merge anti-joins and a partial index; it returns in 400ms.
  • A database with no answer to how long its records should live, so it grew without bound. I set the retention policy and built the GC. In two months it cleared 41.8 million rows from each of the two heaviest tables (280 and 897 million rows), and it keeps ticking every minute.
  • Go services getting OOM-killed under load, because the runtime couldn't see the container's memory limit. Kubernetes can't express "90% of the pod's limit" as a value, so I added an entrypoint shim that computes it and exports $GOMEMLIMIT before the binary starts. The kills stopped.

The work I take on is usually the hard-to-staff kind: the migration nobody wants to own, the system that has to change without going down, the architecture that needs deciding. If you think there's a fit here, tell me what you're building or fixing at [email protected].