Skip to content

Function Services

A service normally runs as a container on AppMecca's hosts. A function service runs the same container image on AWS Lambda instead: no copy runs while nobody is calling it, and as many copies start as traffic needs.

It is meant for a server-rendered frontend (SvelteKit, Next.js, Nuxt and the like) whose traffic comes in bursts. Your API, workers and anything that talks to your database stay on the container runtime.

containers:
  - name: web
    dockerfile: Dockerfile

deploy:
  - name: web
    container: web
    port: 3000
    runtime: function
    function:
      memory_mb: 1024
      timeout_s: 30
    health_check:
      path: /

You do not change your Dockerfile. The build adds a small adapter to your image that receives each request from Lambda and forwards it to your server on port.

Settings

runtime: function needs a port. The function: block is optional.

Setting Range Default What it does
function.memory_mb 128 to 10240 1024 Memory for each copy. More memory also means more CPU, so a first render is faster.
function.timeout_s 1 to 60 30 The longest one request may run.
function.reserved_concurrency 1 to 100 none The most copies that may run at once. Leave it out to scale with traffic.

function.timeout_s cannot be above 60. Every request to a function passes through AppMecca's gateway, which stops waiting after 60 seconds.

All the function services on an environment share a total of 500 reserved concurrency. A deploy that would take the total above that waits in the queue with the reason function_concurrency. Lower the value, or remove it.

What a function service cannot do

A function runs outside AppMecca's private network. It reaches your other services over their public address, and it cannot reach your database or Redis.

Not allowed Why Instead
replicas, singleton Lambda decides how many copies run. function.reserved_concurrency caps it.
resources.memory A function is sized differently. function.memory_mb
type: static or type: spa Those are served from the edge, not run. Remove type, or remove runtime: function.
drives on the service A function has no drive to mount. Keep the service on the container runtime.
storage.buckets anywhere in the app Bucket credentials use AWS_ names, which Lambda reserves. Keep the service on the container runtime.
${service:NAME:internal} in its environment That address is inside the private network. ${service:NAME}
${database_url}, ${redis_url} and the other datastore templates in its environment A function receives no database or Redis credentials. Call your backend over ${service:NAME}.
A worker (command: celery …, ROLE=worker, ROLE=beat) A function only answers HTTP requests. Keep workers on the container runtime.

If your app has a database or Redis, its container services still receive DATABASE_URL and REDIS_URL. The function service does not, and the deploy records a function_no_datastore warning to say so.

The service name must be plain: letters, digits, ., _ and -, starting with a letter or a digit. The function's own name and log group are built from it. Container services have no such rule.

Environment variables

  • The whole environment of a function must fit in 4096 bytes. A deploy that goes over is refused and names the largest variables.
  • Names starting AWS_ or LAMBDA_, and the names _HANDLER and _X_AMZN_TRACE_ID, are reserved by Lambda. Rename yours, for example with an APP_ prefix.
  • A value that points at *.appmecca.internal is refused.
  • If you do not set ORIGIN, AppMecca sets it to your app's public address. Frameworks such as SvelteKit use it to check form posts.

Files

The filesystem is read-only, except /tmp. Anything written to /tmp lasts only as long as that copy does.

Request size

Lambda limits a request to 6 MB, measured after the request has been turned into the event your function receives. How much body fits depends on the request's Content-Type:

Content-Type How Lambda passes the body Largest body
text/* (for example text/plain, text/csv, text/html); application/json, application/xml, application/javascript, application/x-yaml, application/graphql; application/ld+json, application/problem+json, application/hal+json, application/geo+json, application/xhtml+xml, application/atom+xml, application/rss+xml as text 6,283,264 bytes (about 6 MB), less if it holds many quotes, backslashes or control characters
none; a form post (application/x-www-form-urlencoded); a file upload (multipart/form-data); image/* (including image/svg+xml), audio/*, video/*, font/*; application/octet-stream, application/pdf, application/zip, application/gzip, application/yaml, application/sql, application/x-ndjson, application/vnd.api+json base64-encoded, which adds a third 4,712,448 bytes (about 4.5 MB)

A request with a Content-Encoding other than identity (for example a gzip-compressed body) is always base64-encoded, whatever its Content-Type. Some types that look like JSON or YAML are binary to Lambda: application/vnd.api+json and application/yaml get the smaller limit while application/json and application/x-yaml get the larger one. A type not listed above is treated as text.

A larger body is answered 413 by AppMecca before it reaches your function. The message says which limit applied. Most file uploads are multipart/form-data, so they get the smaller limit.

A body sent with a text Content-Type is passed to your function as text. Bytes in it that are not valid UTF-8 are replaced by Lambda before your function sees them; they are not refused and not base64-encoded. Send binary data with a binary Content-Type.

To accept larger uploads, receive them in a service that runs on the container runtime. Container services do not have this limit.

Slow uploads

A request body must keep arriving. AppMecca answers 408 when:

  • no part of the body arrives for 10 seconds, or the upload averages less than about 16 KB per second, or
  • the whole body takes longer than 60 seconds.

At 150 KB per second a 4.5 MB upload takes about 30 seconds. A connection that pauses for 10 seconds partway through an upload, for example on a mobile network, gets a 408.

Query strings

Lambda refuses some characters in a query string. In a parameter's name it refuses, for example, the / in a SvelteKit form action (?/login) or the [] in items[]=1. In a value it refuses { } | ^, a backtick, a backslash, and a % that is not part of an escape such as %20. AppMecca percent-encodes those characters before the request reaches your function, so ?/login arrives as ?%2Flogin and ?filter={...} as ?filter=%7B...%7D. Read the query through your framework's parsed parameters (for example url.searchParams), which decode them back, and not from the raw query string. If your app checks a signature over the raw query string (some webhook providers sign it), the signature will not match on a function; run that endpoint on the container runtime instead.

Cold starts

When no copy is running, the first request waits for one to start. These are measurements of AppMecca's own dashboard (a SvelteKit app), 20 requests per row:

Request Memory Cold, typical Cold, slowest Warm, typical
First signed-in page 512 MB 1686 ms 2011 ms 156 ms
First signed-in page 1024 MB 1054 ms 1159 ms 78 ms

Starting a copy takes about 305 ms at either size. The rest of a cold request is your app's first render, which more memory makes faster. That is why the default is 1024 MB.

Health check

After a deploy publishes the new version, AppMecca requests health_check.path from it before sending it any traffic.

  • The answer must be a 2xx.
  • A redirect inside your app is followed, up to 5 times. / answering 303 to /login, which answers 200, passes.
  • A redirect to another host fails the deploy. The usual cause is an ORIGIN that names the wrong address.

The first deploy of a function service can wait up to six minutes for its permissions to take effect. If it runs out of time the deploy fails with the reason function_role_not_ready, and retrying it works.

Day to day

Status. The service tile reads Function. Before its first successful deploy it reads Status unknown — not yet published. There is no replica count. mecca app show labels the service (function), or (function, not yet published). mecca status <app> lists the function services under its health table.

Restart, stop, start. A function has no container, so these are refused for it: "runs as a function; it has no container to restart, stop or start. Redeploy the app to publish a change." On an app that also has container services, mecca app restart <app> restarts those and skips the function. It prints the services it skipped and why.

Scaling. mecca app scale refuses a function service with function_not_scalable. Use function.reserved_concurrency.

Logs.

mecca logs myapp --type container --service web

This reads the function's own log group. Without --service you get the container services and the function services together. If some function logs could not be read, mecca logs prints a note saying which and why.

Usage and cost. Function services are not metered yet. Their compute is missing from every total, so a total for an app with a function service is a lower bound. The dashboard's Usage page lists them under Not metered. mecca usage show, summary and report name them under the totals, in a not metered block. Their --json output carries the list as not_metered_services.

Stats. Stats are container memory and CPU, so there are none for a function. The dashboard's Stats tab says not available for function services. mecca app stats prints no row for a function, and names the function services above the table. Its --json output names them under unavailable.

When a request fails

If your function fails before it starts its response, the caller gets 502 with the body upstream unavailable. This happens when your server crashes, exits, or drops the request without answering. It also happens when the gateway has waited 60 seconds without an answer. AppMecca does not cache a 502, and monitoring sees it as a failure.

If your function fails after it has started its response, the caller already has the status and headers. The response ends early.

To see what went wrong, read the function's log:

mecca logs myapp --type container --service web

A failed invocation reaches AppMecca as a 200 whose body is a JSON object with errorType and errorMessage, sent with no Content-Type or as application/octet-stream. Do not send that shape yourself: the caller would get 502. Give your JSON responses an application/json content type.

Moving a service back to a container

Remove runtime: function (and the function: block) and deploy. The service runs as a container from that deploy on.

The old function is removed after that deploy succeeds. Two deploy warnings can follow:

Warning Meaning
function_reclaim_left_behind The function was removed. A few leftover items could not be removed with it. They serve nothing, and they go when the app is deleted.
function_reclaim_failed The function could not be removed. It serves no traffic. The next deploy tries again.

When two deploys of the app are queued back to back, the function can be removed one deploy late.

Next Steps