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_orLAMBDA_, and the names_HANDLERand_X_AMZN_TRACE_ID, are reserved by Lambda. Rename yours, for example with anAPP_prefix. - A value that points at
*.appmecca.internalis 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.
/answering303to/login, which answers200, passes. - A redirect to another host fails the deploy. The usual cause is an
ORIGINthat 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.
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:
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¶
- Services — every per-service setting
- Inter-Service Communication —
${service:NAME}addresses - Usage & Cost — what is metered