anbaric 1.65.3 → 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 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.65.3",
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.65.3",
28
- "anbaric-data-store": "^1.65.3",
29
- "anbaric-state-machine": "^1.65.3",
30
- "anbaric-tsapi": "^1.65.3"
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
  }