path | title |
---|---|
/learnings/ops_javascript |
Learnings: Ops: Javascript |
TL;DR:
- don't use
npm start
as a launcher / CMD - don't put Node.js as PID 1 <-- use an init process (see )
This is a multi faceted problem. Because npm
launches node by creating a bash
process and running through that (at least on *nix). This creates... well a fun bit of complication because of this parent process, having to pass signals through bash (which isn't set up to handle them anyway ?? See: Learning_Ops_Docker_PID1_Why_Not_Bash )
Second facet: Node doesn't (probably) know enough to clean up child processes that have been thrust upon them ( See: Learning_Ops_Unix_PID1_Responsibilies ).
Third facet: If a NPM module you're using launches some process to complete its work. This facet is commented on by someone in the node/build-containers repo
See also:
- blog post where author builds up PID story, explain what processes are launched by npm start (a bash wrapper over your node instance!!)
- the npm bug where they talk about this (and close it)
- where the node/build-containers team talks about this
- libuv team weights in. May 2018 ticket remains open
Theoretically you could handle the signals yourself in Javascript, if you didn't mind writing code coupling your application to Docker.
Elastic.io says SIGTERM not caught by Node.js
I'm not 100% sure I believe this. See: Learning_Ops_Docker_PID1_Signals where that author claims it works.
But:
- set up
ENTRYPOINT
, not justCMD
to insure no process accidentally in the middle. Aka maybe someone has "helpfully" set ENTRYPOINT tobash -c
. See: Learning_Ops_Docker_PID1_Why_Not_Bash
Also Node Process module documentation seems to say it can handle SIGTERM
, just not SIGKILL
.
Useful inspection flags listed v8-perf wiki
Dual space / generation memory model. Each space has their own garbage collector.
Memory pages 512kb / 512kb aligned.
By default memory limit for Node.js is 512MB (SOURCE?)
- [TODO]: source / validate this ^^^
Two garbage collectors, one for new space and one for old space.
- code <-- actual code executed
- stack <-- primitives, data types, pointers
- heap <-- objects, strings, closures
- new space
- old (pointer) space
- old (data) space
- large object space — large objects sit here, mmaped space
- code space
New space and old space. each space has memory pages.
node --max_old_space_size=2000
# <—- now 2 gb ish
- https://stackoverflow.com/q/42212416/224334 Docker and max_old_space_size
- https://v8project.blogspot.com/2018/04/improved-code-caching.html ^^ talking about code caching and how v8 Javascript isn’t really an interpreted language
- https://v8project.blogspot.com/2018/06/concurrent-marking.html ^^ concurrent mark and sweep coming to v8, and Node eventually.
Size of 1-8MB
GC algorithms: Scavenge and Mark and Sweep/compact
Scavenge
<-- concurrent! (especially past Node 8)
Mark / sweep / compact — pause
compaction paralleled on a page level.
Garbage Collector:(Incremental, Concurrent) Mark -> (lazy) Sweep (stop the world) -> Compact
- Node.js High Performance, v8-pref wiki
node --max-old-space-size=2042
<-- old space now 2GB
v8 garbage collector: periodic stop the world scans "cycle" - Node.js High Performance
"Shallow size" — size of object
This coupling of Mark/Sweep means gc pauses are (usually) minimal (5-50ms).
$ node --trace-gc
$ node --expose-gc # <-- debugging aid that allows you to call gc() to understand memory
Built in tools:
require v8-profiler in your JS code then connect to it.
Also heapdump module, where you can trigger a heap dump to disk
- https://github.com/thlorenz/v8-perf/blob/master/memory-profiling.md#taking-heap-snapshot-with-nodejs
$ node --tracegc
Will print out to console(??) every time GC happens (but not for long term usage)!
Package, emits and event every time things get weird / leak
Can also do heapdiffs
Node v8 removes "Crankshaft" JIT engine
- full-codegen
- Lithium <-- to machine code
- Crankshaft <-- JIT
"interpreter"
Uses Turbofan low level macroassembly instructions to generate bytecode for each opcode
Optimizes bytecode local state held in (physical!) registers maps slots to native machine stack memory keeps stack frame, program counter, etc
Compiler + Backend responsible for:
- instruction selection
- register allocation
- bytecode gen
- speculative optimization
Not an optimizing compiler (that fancy stuff runs on top of Turbofan)
$ node --print-opt-code # <-- prints optimized code generated by turbofan
$ node --trace-opt # <-- traces lazy optimizations
$