Node-RED で Maximum call stack size exceeded エラーが一定時間経過すると発生することを解決したメモです。
Node-RED 以外でも役に立つと思う
最初にお伝えしておくと、今回のナレッジは、最終的には JSON データの扱い方という話だったので、Node.js やブラウザで JavaScript で JSON データを扱う際も役立つと思います。
今回の状況
今回のフローは、Nature Remo のデータを集めて UI で表示する中で、現在のデータと過去のデータと比較するフローで起きていました。
状況としては、根が深そうな挙動。
- フローをデプロイした直後はサクサク動く
- 1時間程度放置していても問題はない
- しかし数日放置していると、UIの表示も debug ノードの表示もどんどん重くなる
そして、最終的には
RangeError : Maximum call stack size exceeded
が出てしまい、操作不能になって Node-RED が落ちたり、勝手に再起動がかかったりする状況でした。どうも RangeError: encodeObject Error: [Invalid string length]
というのも起きるときがある。
いろいろと模索
- フローを整理して少なくしても、起きるタイミングは変わらない。
- JSONata の複雑な計算がまずいのかと思い削っても、起きるタイミングは変わらない。
- debug ノードで検証した時にエラーが出るように見えたので、できるだけ OFF にしてみたが、起きるタイミングは変わらない。
- Nature Remo の API が遅いので巻き込まれている気がしたが、濡れ衣。そうではなかった。
- inject ノードで取得する頻度が早いんじゃないかと思い遅くしても変化なし。
などなど、やってみたんですが、変わらず。一番、疑心暗鬼になってたときは、 Node-RED そのもののバグも疑いました。
けど、ちがいました!
ようやく原因が分かった
原因は、前の値を payload.previous に入れ子でぶら下げて記録してたのが、入れ子を無限に深くしてデータを増殖させてしまいまずかったということが分かりました。
どういうことかというと、こんなことをしていました。
- まず Nature Remo の API からデータを取得する
- 使いやすいように JSONata で加工
- UIに値を表示させる
- 上記をやり終わったら、payload 全体を過去データをして flow.currentNatureRemoRoomEnvironments として保存する
- inject ノードで次のタイマーが1分後に発動する
- Nature Remo の API からデータを取得前に、change ノードで過去データ flow.currentNatureRemoRoomEnvironments を payload.previous に戻して比較しやすくする。
- 以降、繰り返し。
これ、よく考えると分かるんですが、これ1つ前のデータ群を payload.previous に戻してしまうと、どんどん過去のデータが入れ子で貯まっていってしまうんですよね。
これを1度だけの流れで見てると問題なさそうなんですけど。
2回繰り返されるとよりイメージが湧きます。payload の値の中に previous を作っていくと無限に入れ子でデータがぶら下がっていってしまうんですね。
だんだん分かってきて、さらに 7 回繰り返した状況はこちら。やばいですね。なので、これが時間経過で何度も繰り返されるとデータがパンクしてしまう。(むしろ、今まで、数日間よく持ってくれてたなあ・・・)
結局どうしたかというと
payload の中ではない外の値( msg.payload.previous ではなく msg.previous )に、1つ前のデータだけ持ってくるようにしたら、無限にデータがぶら下がっていく状況が回避でき、ずっと稼働しても安定して動き続けるようになりました!
ただ、ここまでくると、単純に flow.currentNatureRemoRoomEnvironments を独立して見ればいい気もしますが、ひとつのフローの中で msg オブジェクトに全部入ってると、何かとやりやすいので悩ましいところです。このあたりは、また調整していこうと思っています。
とにもかくにも、自分の実装でヤバい展開になっていたことを、ちゃんと気づけて解決できてよかったです!