◆ 起動時にデータをロードしてそれを参照する
◆ 基本はリクエストのたびに DB 問い合わせしない
◆ 更新時のみ DB にアクセスして保存
◆ 外部からの DB データ変更があると整合性が取れない
◆ 更新通知を受け取って更新する

都度 DB アクセスしないつくり

Node.js だと Web サーバを起動したとき 各リクエストを処理するスレッドが同じなので 全リクエストで同じ変数にアクセスできます
つまり 最初に DB のデータを変数上に取得してしまえば リクエストごとに一々 DB にアクセスしなくて済みます
プロセス起動時にメモリに乗せれる種類の DB のデータは最初にロードしてしまって リクエスト処理時には DB を参照しないことが多いです
変数上のデータを見てレスポンスを返せるのでかなり高速です
PHP みたいにリクエストごとに別プロセスになっていて リクエストごとに必要なデータを DB から取得していたら時間がかかって仕方ないです

この方法だと 読み取りメインなサービスの場合 ほとんど DB アクセスしないのでわざわざ DB を使うのがもったいないと思えるくらいです
極端に言うとローカルに JSON ファイル置いておくだけでも十分なくらい

そこが Node.js のメリットのひとつなのですが デメリットでもあります
DB の更新をするのがそのプロセスだけでない場合 別の手段で更新されたデータは変数上のデータに反映されないので DB と変数上のデータに差異が生じます
別の Node.js プロセスで更新したり GUI ツールで更新するとアウトです

実際に動かすときは外部からアクセス一切ないことも多いのですが 開発中にデータを手動で変えたくて GUI ツールから操作 なんてことは少なくないです
まぁ開発中なのでサーバ自体も再起動すればそれでいいといえばいいのですけど
それ以外では あまり詳しくないのですが最近の規模が大きいものだとコンテナでクラスタ組んだりするみたいです
コンテナひとつひとつが Node.js のサーバを起動するものだと 複数の Node.js プロセスが同じデータベースを更新するので整合性が取れなくなります
完全にひとつの Node.js プロセス専用としてしまえればいいのですが そうはいかないこともあってこのメリットを完全に活かせるシーンは多いとは言いづらいです

対策例

外部で変更されたことを知ることができれば再取得することでデータの不整合をなくせます
通知を受けとるというと複雑そうですが PostgreSQL の場合は結構簡単です
Notify/Listen 機能があって 通知を送れます

Notify で指定したチャンネルにペイロード(送信したいデータ)を送ると そのチャンネルを Listen していたコネクションで通知を受信できます
データ操作と無関係に通知を送信することもできますが トリガを設定しておいてテーブルのデータが更新されたら通知を送信するよう設定することができます


普通に毎回 DB から取得する例

まずは普通に毎回 DB からデータを取得する例です
users テーブルからユーザを取得します
ちなみに全部がメモリに余裕で乗ることを想定してるので このユーザテーブルのデータは多くて 50 件くらいです
ユーザ数◯千万人みたいな規模を考えるなら 都度取得で SQL レベルでフィルタすべきです

[db.js]
const getUsers = async () => {
const res = await pg.query("SELECT * FROM users")
return res.rows
}

最初に(初回呼び出し時に)保存しておく例

本当に起動時にロードして変数に入れておいてもいいですけど ほぼ使われないデータもあったりするので最初の呼び出し時でいいかなとも思います
起動の待ち時間も減りますし

[db.js]
let users
const getUsers = async () => {
if (!users) {
const res = await pg.query("SELECT * FROM users")
users = res.rows
}
return users
}

通知で更新する例

これが目的のやつです
memo.js を用意して保存と再取得部分の仕組みをまとめてます

register メソッドで参照とデータを取得する関数と依存テーブル名をセットします
value メソッドでは引数で渡された参照から それに対応するデータを取得する関数を見つけて 実行結果を返します
結果は保存されるので二度目以降に同じ参照に対しては関数実行なしに同じ結果を返します
変化通知で再度取得するために reset メソッドを使います
変更があったテーブル名を引数に受け取って そのテーブルに依存する参照の保存されてるデータをクリアします
データがクリアされていると value メソッドで取得するときに関数を再実行して再取得します

notify は trigger で insert/update/delete があったらテーブル名をペイロードに送信するように設定しておきます
これは SQL でテーブル定義と合わせて設定しておく部分です
listen は Node.js のプロセス内で設定して notification イベントで受け取ったテーブル名を reset メソッドに渡します

[memo.js]
const reset_symbol = Symbol()

module.exports = () => {
const name_to_refs = {}
const ref_values = new Map()
return {
async value(ref) {
const values = ref_values.get(ref)
if (!values) throw new Error("ref value is not registered")
if (values.value === reset_symbol) {
values.value = await values.fn()
}
return values.value
},
reset(name) {
for (const ref of name_to_refs[name] || []) {
const values = ref_values.get(ref)
if (values) {
values.value = reset_symbol
}
}
},
register(ref, deps, fn) {
ref_values.set(ref, { fn, value: reset_symbol })
for (const dep of deps) {
if (dep in name_to_refs) {
name_to_refs[dep].add(fn)
} else {
name_to_refs[dep] = new Set([fn])
}
}
},
}
}

[db.js]
const memo = require("./memo.js")()

pg.on("notification", msg => {
memo.reset(msg.payload)
})
pg.query("LISTEN changed_tablename")

const getUsers = async () => {
const value = await memo.value(getUsers)
return value
}

memo.register(getUsers, ["users"], async () => {
const res = await pg.query("SELECT * FROM users")
return res.rows
})

依存テーブルをもとにリセットする作りなので テーブル内の全件再取得になってます
差分更新じゃないので頻繁に更新がくるとちょっとつらいかもしれません
ただ それが辛いほどなら常に最新データをメモリに乗せるのより DB から逐次検索でいいかもしれません

全件取得の関数自体を再実行という方針じゃなくて最初に取得したデータを部分的に変更できるようにして 主キーを通知してそこを更新するほうが無駄が少ない気がしますね
差分更新の処理を作らないといけないのがちょっと面倒ですけど
テーブル全件 SELECT をオブジェクトの配列で持つだけなら 大した処理ではありませんが 扱いやすい形式にフォーマットしてるとそれに合わせないとですから

※ ところで今回のコードは思いつきで作ってみただけなので動作は未確認です(DB 使うのは準備が面倒で……)