anbaric 1.65.2 → 1.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/README.md +1 -0
- package/docs/features/deploying.md +11 -0
- package/docs/patterns/memory.md +112 -0
- package/package.json +5 -5
package/docs/README.md
CHANGED
|
@@ -44,6 +44,7 @@ How to put the features together to build something real.
|
|
|
44
44
|
- [Integrating external systems](patterns/integrating-external-systems.md) — waiting on callbacks and webhooks
|
|
45
45
|
- [Authorization with actors and roles](patterns/authorization.md) — who is allowed to do what
|
|
46
46
|
- [UX practices](patterns/ux-practices.md) — feedback and polling for an asynchronous UI
|
|
47
|
+
- [Staying within memory](patterns/memory.md) — the 512 MB footprint, and not turning the process into a cache
|
|
47
48
|
- [Testing your app](patterns/testing.md) — driving a machine in memory
|
|
48
49
|
|
|
49
50
|
## API reference
|
|
@@ -48,6 +48,17 @@ anbaric jobs watch <job-id> # follow a job as it progresses
|
|
|
48
48
|
the factories at platform-backed persistence, queueing, stores and auditing —
|
|
49
49
|
so your code is identical locally and in the cloud. **Never set these
|
|
50
50
|
yourself.** See [Environment and factories](../api/environment.md).
|
|
51
|
+
- **A fixed footprint.** Each app runs in its own container with **0.25 vCPU
|
|
52
|
+
and 512 MB of memory**, shared between your process and the small launcher
|
|
53
|
+
beside it. Node sizes its heap from that limit, so your code gets a heap of
|
|
54
|
+
roughly **270 MB** before it is out of memory. That is plenty for a workflow
|
|
55
|
+
app whose steps call services and move a job on; it is not enough to hold
|
|
56
|
+
thousands of jobs, a whole dataset or a large file in memory at once. See
|
|
57
|
+
[Staying within memory](../patterns/memory.md) for what to do instead and
|
|
58
|
+
how an out-of-memory crash shows up. Every app starts small; one that needs
|
|
59
|
+
more can be switched to a **large** instance (faster compute, 4× the memory,
|
|
60
|
+
$50/£50 a month instead of $20/£20, and not covered by the free allowance)
|
|
61
|
+
on the console's Manage apps page. Deploying never asks about size.
|
|
51
62
|
|
|
52
63
|
## Updating and tearing down
|
|
53
64
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Staying within memory
|
|
2
|
+
|
|
3
|
+
A deployed app gets **512 MB** of memory and a quarter of a vCPU, and Node gives
|
|
4
|
+
your code a heap of roughly **270 MB** of that. This is a deliberate shape: an
|
|
5
|
+
Anbaric app is a set of small steps that each read a job, do one thing and
|
|
6
|
+
record the result. The job's data lives in the platform, not in your process,
|
|
7
|
+
so a well-shaped app rarely needs more than a few tens of megabytes however
|
|
8
|
+
many jobs it has. The crashes we see come from code that quietly turns the
|
|
9
|
+
process into a cache.
|
|
10
|
+
|
|
11
|
+
## How a crash looks
|
|
12
|
+
|
|
13
|
+
Two different limits, two different symptoms:
|
|
14
|
+
|
|
15
|
+
- **The heap limit.** Node aborts with
|
|
16
|
+
`FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory`
|
|
17
|
+
in `anbaric app tail <name>`, usually preceded by a run of
|
|
18
|
+
`Mark-Compact` lines as the garbage collector tries and fails to find room.
|
|
19
|
+
This is the common one and it is always retained objects, not a garbage
|
|
20
|
+
collector that stopped working: the collector only frees what nothing refers
|
|
21
|
+
to any more.
|
|
22
|
+
- **The container limit.** Buffers and native memory live outside the heap. If
|
|
23
|
+
they take the container past 512 MB the container is killed outright, with no
|
|
24
|
+
stack trace: `anbaric app status <name>` shows the task stopped with
|
|
25
|
+
`OutOfMemoryError: Container killed due to memory usage`.
|
|
26
|
+
|
|
27
|
+
Either way the platform restarts the app and the queue redelivers any job it
|
|
28
|
+
had not confirmed, so the damage is a gap, not lost work — unless the same job
|
|
29
|
+
takes the app down every time, which is what the rest of this page prevents.
|
|
30
|
+
|
|
31
|
+
## Do not hold jobs
|
|
32
|
+
|
|
33
|
+
The temptation is to load jobs into a module-level `Map` for quick lookup, or
|
|
34
|
+
to fetch every job in a state before deciding which to act on. Every job held is
|
|
35
|
+
a job that is never collected.
|
|
36
|
+
|
|
37
|
+
- An action is given its job. Read what it needs with `await job.properties.get(...)`
|
|
38
|
+
— properties are fetched lazily, one at a time, and only the ones you ask for
|
|
39
|
+
— and return a `Map` of changes. Keep nothing between steps.
|
|
40
|
+
- Anything that has to be found later belongs in a store, not a variable: the
|
|
41
|
+
[JSON store](../features/documents-and-secrets.md) for documents, the
|
|
42
|
+
[SQL store](../features/sql-store.md) for anything you would otherwise sort,
|
|
43
|
+
filter or aggregate in JavaScript. Let the database do the `WHERE`.
|
|
44
|
+
- Page. `store.list(actor, pageSize, page)` exists so that nobody has to fetch
|
|
45
|
+
a whole collection; a step that walks a collection should take one page, act,
|
|
46
|
+
and move on, ideally recording where it got to in the job so a restart resumes
|
|
47
|
+
rather than starts over.
|
|
48
|
+
|
|
49
|
+
## Keep steps small
|
|
50
|
+
|
|
51
|
+
Memory follows the biggest single step, not the number of jobs. A step that
|
|
52
|
+
builds a 50 MB report in a string before writing it needs 50 MB plus every
|
|
53
|
+
intermediate copy; a step that writes a line at a time needs a line.
|
|
54
|
+
|
|
55
|
+
- Several small states beat one large one: each gives the collector a point at
|
|
56
|
+
which nothing from the previous step is reachable, and each is a point the
|
|
57
|
+
job can resume from.
|
|
58
|
+
- When a step fans out — one job per row of an upload, say — start the child
|
|
59
|
+
jobs and finish; do not wait on them in memory. Use the parent's state and
|
|
60
|
+
the children's results in the store to decide when it is done.
|
|
61
|
+
- A model call's response is bounded by the model's output limit, but what you
|
|
62
|
+
send is not: do not stuff whole documents into a prompt from memory when a
|
|
63
|
+
retrieval step could pass the relevant part.
|
|
64
|
+
|
|
65
|
+
## Files are whole
|
|
66
|
+
|
|
67
|
+
`FileStorage.get` returns the file's bytes as one `Uint8Array`, and `put` takes
|
|
68
|
+
one: there is no streaming, and the bytes travel to and from the platform as a
|
|
69
|
+
single request. Treat the file store as a place for documents, not datasets.
|
|
70
|
+
|
|
71
|
+
- Keep individual files to a few megabytes. Anything that would be tens of
|
|
72
|
+
megabytes is better split into parts at a path prefix (`exports/2026-10/part-001`)
|
|
73
|
+
and processed part by part, one per step.
|
|
74
|
+
- Never `list` and then `get` every file under a prefix in one step. `list`
|
|
75
|
+
itself is cheap — metadata only, no contents — and is what tells you how many
|
|
76
|
+
parts there are to walk.
|
|
77
|
+
- Parse while you go. Decode a CSV part line by line and write rows to the SQL
|
|
78
|
+
store as they are read, rather than building the whole array first.
|
|
79
|
+
|
|
80
|
+
## Watch the web side too
|
|
81
|
+
|
|
82
|
+
Your app serves HTTP as well as running steps, and the two share the heap.
|
|
83
|
+
|
|
84
|
+
- Stream large responses with `response.write` as you produce them, rather
|
|
85
|
+
than building a body string. Do not read a file from storage to serve it if a
|
|
86
|
+
link to it would do.
|
|
87
|
+
- An in-memory session table, request log or rate limiter grows with traffic
|
|
88
|
+
and never shrinks. Put sessions behind the platform's auth (it already has
|
|
89
|
+
them) and counters in the store.
|
|
90
|
+
- Clear timers and listeners you create; an `EventEmitter` that keeps every
|
|
91
|
+
past request's handler is a leak that looks like load.
|
|
92
|
+
|
|
93
|
+
## When you need to know
|
|
94
|
+
|
|
95
|
+
`process.memoryUsage()` is the honest answer: `heapUsed` against the ~270 MB
|
|
96
|
+
heap, `rss` against 512 MB. Logging it at the start and end of a suspect step
|
|
97
|
+
tells you in one run whether the step is the problem. If `heapUsed` climbs
|
|
98
|
+
across steps and never comes back down, something is holding on; if a single
|
|
99
|
+
step spikes, that step needs breaking up.
|
|
100
|
+
|
|
101
|
+
If a workload genuinely needs more — a large model run, a heavy transform — an
|
|
102
|
+
app can be moved to a **large** instance from the console's Manage apps page:
|
|
103
|
+
faster compute and four times the memory (2 GB, so a heap of roughly 1 GB),
|
|
104
|
+
for $50/£50 a month instead of $20/£20, paid from its first day (the free
|
|
105
|
+
allowance covers small apps only). It is the right answer for one heavy app;
|
|
106
|
+
it is not the answer to a leak, which will fill 2 GB as surely as 512 MB.
|
|
107
|
+
|
|
108
|
+
## See also
|
|
109
|
+
|
|
110
|
+
- [Deploying](../features/deploying.md) — what a deployed app gets
|
|
111
|
+
- [Structuring an application](app-structure.md) — keeping actions small
|
|
112
|
+
- [File storage](../features/file-storage.md), [The SQL store](../features/sql-store.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anbaric",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.66.0",
|
|
4
4
|
"description": "Everything needed to write an Anbaric app: state machines, jobs, document and secret stores, local in-memory implementations and the Anbaric Cloud clients",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"anbaric-impl-cloud": "^1.
|
|
28
|
-
"anbaric-data-store": "^1.
|
|
29
|
-
"anbaric-state-machine": "^1.
|
|
30
|
-
"anbaric-tsapi": "^1.
|
|
27
|
+
"anbaric-impl-cloud": "^1.66.0",
|
|
28
|
+
"anbaric-data-store": "^1.66.0",
|
|
29
|
+
"anbaric-state-machine": "^1.66.0",
|
|
30
|
+
"anbaric-tsapi": "^1.66.0"
|
|
31
31
|
}
|
|
32
32
|
}
|