DEV Community

Riley Xu
Riley Xu

Posted on

The Metered Migration: Moving an AI Worker to a Free Server Without Breaking Your SLO

The moment you decide to move an AI workload to free infrastructure, the real migration is not about the endpoint or the token grant; it is about which of your assumptions you are willing to give up. After two months of watching a paid gateway's monthly bill climb faster than my feature list, I stopped looking for a cheaper model and started measuring what my pipeline actually required. What follows is the cutover diary of moving a small extraction service to MonkeyCode's free model access and free server, with the leftovers that most tutorials skip.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The migration was not a drop-in replacement. My original service called a commercial API with a 10-second timeout, retried every failure up to three times, and stored the raw response in a database for audit. The new target was a free server running an open-source gateway, with a large but finite token budget. That difference forced me to think about failure modes that the monthly bill had previously hidden.

The Cutover Plan in Five Steps

I built the migration around five steps that you can reuse even if you never touch MonkeyCode. Each step is designed to be reversible, which is the only real protection against a free-tier surprise.

  1. Lock the contract. Define a thin client interface with three methods: complete(prompt), embed(text), and health(). Your application code must not import the provider's SDK directly; otherwise, you will end up rewriting business logic while debugging connection errors.

  2. Benchmark the latency envelope. Free servers often share compute with other tenants, so I measured p50, p95, and p99 latency for 200 requests before switching traffic. A single slow request is noise; a rising p99 across five minutes is a signal that the server is under pressure.

  3. Insert a token meter. You cannot manage a budget you do not measure. I added a tiny middleware that logs the prompt and completion token counts per request, then aggregates them hourly. Here is a minimal Python version that worked for me:

import time, json
from collections import defaultdict

class TokenMeter:
    def __init__(self):
        self._buckets = defaultdict(lambda: {"prompt": 0, "completion": 0})

    def record(self, usage: dict):
        hour = time.strftime("%Y-%m-%dT%H:00")
        self._buckets[hour]["prompt"] += usage.get("prompt_tokens", 0)
        self._buckets[hour]["completion"] += usage.get("completion_tokens", 0)

    def snapshot(self, hours=24) -> list:
        start = time.time() - hours * 3600
        return [{"hour": k, **v} for k, v in self._buckets.items()
                if time.mktime(time.strptime(k, "%Y-%m-%dT%H:%M")) > start]
Enter fullscreen mode Exit fullscreen mode
  1. Shadow traffic for one day. Send 10% of real requests to the new server while the old gateway still handles the rest. Compare not only the text output but also the retry counts and timeout rates. I found that the free endpoint was actually faster at p50, but had a noticeably higher p99.

  2. Flip the switch on a low-traffic day. I cut over on a Sunday morning, then watched the error rate for an hour before leaving the laptop. The downtime was zero, but I would not have bet on that without the shadow run.

The Leftovers Nobody Warns You About

The first leftover was retry semantics. My old gateway returned a specific HTTP 429 with a Retry-After header, and my client respected it. The new free server occasionally returned a 503 with a body that was empty, and my retry logic treated that as a permanent failure because the status code was not in the retry list. Adding 503 to the retry set fixed the issue, but only after a night of silent task drops.

The second leftover was response shaping. Free model endpoints sometimes omit fields like finish_reason or leave usage null for cached completions. My parser raised an exception on None values, so I had to introduce default values and a warning log. It is an ugly compromise, but it keeps the queue moving.

The third leftover was the server itself. The free server option is convenient, but you need to know whether your workload is CPU-bound or I/O-heavy. For my extraction jobs, most wall-clock time went into waiting for the model, so a small server was fine. If your workflow does a lot of local parsing or embedding, you will want to run a load test before promising any latency target.

A Reproducible Smoke Test for Migration

Instead of trusting a status page, I wrote a smoke test that you can run against any endpoint that speaks the OpenAI-compatible protocol. Save this as smoke_test.sh and call it with your endpoint and key:

#!/usr/bin/env bash
set -euo pipefail

ENDPOINT="${1:?usage: smoke_test.sh <endpoint> <api_key>}"
KEY="${2:?missing api key}"

for i in $(seq 1 10); do
  START=$(date +%s%N)
  RESP=$(curl -s -w "\n%{http_code}" \
    -X POST "$ENDPOINT/v1/chat/completions" \
    -H "Authorization: Bearer $KEY" \
    -H "Content-Type: application/json" \
    -d '{"model": "default", "messages": [{"role": "user", "content": "Reply with the word ok."}], "max_tokens": 5}')
  STATUS=$(echo "$RESP" | tail -n1)
  BODY=$(echo "$RESP" | head -n -1)
  END=$(date +%s%N)
  LATENCY_MS=$(( (END - START) / 1000000 ))
  COMPLETION=$(echo "$BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['choices'][0]['message']['content'])" 2>/dev/null || echo "PARSE_ERROR")
  echo "request $i: http=$STATUS latency=${LATENCY_MS}ms echo=$COMPLETION"
done
Enter fullscreen mode Exit fullscreen mode

The test is intentionally simple: it checks that the endpoint is alive, that responses are valid JSON, and that latency stays within a sane range. Run it five times over the day to see whether the free server degrades during peak hours.

Limitations and Who Should Not Use This Approach

This migration works well for batch jobs, internal tools, and low-traffic prototypes where a few seconds of variance is acceptable. You should not follow this path if your product has a customer-facing latency SLO under 500ms, if you process sensitive data that must not leave your VPC, or if you need guaranteed throughput during business hours. Free infrastructure, by its nature, is shared best-effort capacity; it rewards measurement and punishes hand-waving.

I still keep the old gateway credentials in a locked environment file. The meter is in place, the smoke test runs every Monday morning, and I know exactly what breaking the free tier would cost me. That, not the token balance, is the real outcome of the migration.

Top comments (0)