빌드노트
JSON 파일을 바꿨는데 require가 이전 설정을 반환한다
JSON 설정을 바꿔도 같은 Node 프로세스의 require는 기존 객체를 재사용한다. 읽는 경로와 캐시를 확인하고, 직접 읽기·파싱과 새 프로세스의 결과를 비교해 실행 중 갱신과 시작 시 고정 중 적용 방식을 정한다.
Node.js에서 JSON 설정 파일을 수정했는데 같은 프로세스의 require('./settings.json')가 이전 값을 계속 반환한다면 모듈 캐시를 확인한다. CommonJS의 require는 같은 파일 이름으로 해석되는 요청에 이미 로드한 객체를 재사용한다. 캐시를 건드리지 않았다면 파일을 수정하는 것만으로 그 객체가 새 JSON 내용으로 바뀌지는 않는다. Node v22.23.2의 캐시 규칙 (새 탭에서 열림)
설정을 시작할 때 한 번 읽어 유지할지, 실행 중 다시 읽을지에 따라 수정할 곳이 달라진다. 먼저 읽는 파일을 확인하고, 실행 중 변경을 반영해야 한다면 명시적으로 읽고 파싱하는 경로를 둔다.
문제가 나는 호출에서 경로와 캐시를 확인한다
기존 CommonJS 파일의 해당 require 호출 바로 앞에 다음 조회를 넣는다. ./settings.json은 그 호출이 사용하는 실제 경로로 바꾼다. 설정 값 전체를 출력할 필요는 없다.
const id = require.resolve('./settings.json');
console.log('resolved:', id);
console.log('cached:', Object.hasOwn(require.cache, id));
resolved가 편집한 파일의 절대 경로인지 먼저 본다. 다르면 읽는 대상부터 맞춘다. require.resolve는 모듈을 로드하지 않고 선택될 파일 이름만 반환한다. cached: true는 그 경로의 항목이 현재 캐시에 있다는 뜻이며, 첫 로드 전이라면 false일 수 있다. require.resolve 문서 (새 탭에서 열림), require.cache 문서 (새 탭에서 열림)
cached: true만으로 오래된 값이라고 단정하지 않는다. 같은 경로의 파일 내용과 앱이 받은 값을 비교해야 한다. 새 터미널에서 실행한 별도 Node 프로세스로는 기존 앱의 캐시를 조회할 수 없다.
같은 Node를 켜 둔 채 JSON만 바꿔 본다
Windows 11, PowerShell 7.6.6, Node.js v22.23.2에서 합성 JSON으로 비교했다. 실제 앱 장애 기록은 아니다. 직접 확인하려면 새 빈 폴더에 settings.json을 만들고 {"mode":"before"}를 UTF-8(BOM 없음)로 저장한다. 별도 패키지는 필요 없다. 그 폴더의 PowerShell에서 node를 실행해 대화형 콘솔을 연다.
이 콘솔에서 아래를 입력하면 첫 로드의 값을 확인할 수 있다.
const id = require.resolve('./settings.json');
const first = require(id);
console.log(first.mode);
출력은 before였다. Node 콘솔은 켜 둔 채, 편집기에서 같은 settings.json의 내용을 {"mode":"after"}로 바꾸고 저장을 마친다. 이어서 같은 콘솔에서 두 번째 require와 직접 읽기의 값을 비교한다.
const second = require(id);
const { readFile } = require('node:fs/promises');
console.log('require:', first.mode, second.mode, 'same:', first === second);
console.log('disk:', JSON.parse(await readFile(id, 'utf8')).mode);
조회 결과에서 판별에 쓰는 두 줄은 다음과 같았다.
require: before before same: true
disk: after
파일의 새 내용은 after로 읽혔지만, 두 번째 require는 여전히 before를 반환했다. first === second도 true였으므로 내용만 같은 복사본이 아니라 동일한 객체를 받은 것이다. 파일을 바꾸지 않은 별도 대조에서도 두 require는 before인 같은 객체를 반환했다. 비교 중 캐시 항목은 변경하지 않았다.
기존 콘솔을 유지하고 같은 폴더에 두 번째 PowerShell을 열면 새 프로세스의 첫 로드도 비교할 수 있다.
node -p "require('./settings.json').mode"
이 명령은 after를 출력하고 종료 코드 0으로 끝났다. 새 프로세스가 새 값을 읽었다고 기존 프로세스의 객체까지 갱신된 것은 아니다. 실습을 마치면 켜 둔 Node 콘솔에서 .exit를 입력한다.
disk도 이전 값이라면 id의 경로와 저장 결과부터 다시 확인한다. 두 require의 객체가 다르다면 각 호출의 해석 경로와 중간 캐시 변경 여부를 본다. 공식 문서상 캐시 기준은 해석된 파일 이름이므로, 다른 파일 이름으로 해석된 요청까지 같은 객체라고 일반화하지 않는다. 캐시의 파일 이름 기준 (새 탭에서 열림)
다시 읽을 시점과 새 값을 사용할 곳을 정한다
실행 중 설정을 바꿔야 한다면 JSON을 직접 require하던 곳에 읽기 함수를 둔다. 앞에서 구한 id와 node:fs/promises의 readFile을 사용하는 함수는 다음처럼 둘 수 있다.
async function loadSettings() {
return JSON.parse(await readFile(id, 'utf8'));
}
앱의 설정 갱신을 담당하는 비동기 함수 안에서 const settings = await loadSettings();로 값을 받아 사용한다. 호출할 때마다 파일을 읽고 문자열을 파싱하므로 함수 모듈이 캐시되어도 읽기는 다시 실행된다. 'utf8'은 파일 내용을 문자열로 받기 위한 인코딩 지정이다. fsPromises.readFile 문서 (새 탭에서 열림)
위 비교의 await는 Node 대화형 콘솔이 지원하는 문법 (새 탭에서 열림)이다. 일반 CommonJS 파일에서는 await를 비동기 함수 안에 둔다. id는 그 파일에서 실제 JSON 경로로 구하고, readFile도 앞의 코드처럼 가져와야 한다.
이 함수도 실제 실행했다. after를 받은 뒤 파일을 latest로 바꾸자 다음 호출은 latest인 새 객체를 반환했다. 이미 받아 둔 객체는 여전히 after, 처음 JSON을 require한 객체는 여전히 before였다. 다시 읽는 함수가 생겨도 기존 변수가 자동으로 교체되지는 않는다. 호출부가 새 반환값을 사용하도록 연결해야 한다.
모든 요청에서 파일을 읽을 필요는 없다. 변경을 적용하는 지점에서 읽고 필요한 필드와 타입을 검증한 뒤, 앱이 사용하는 설정 참조를 교체하는 방식을 정할 수 있다. 잘못된 JSON을 넣은 추가 실행에서는 loadSettings()가 SyntaxError로 실패했다. 읽기·파싱 실패 때 갱신을 거부할지 기존 설정을 유지할지도 호출부에서 정해야 한다. 위 함수에는 필드 검증이나 이전 설정 유지 기능이 없다.
시작할 때 고정할 설정이라면 require를 유지하고 변경 적용을 새 프로세스 시작 경로로 관리할 수 있다. 이번 비교는 새 프로세스가 변경된 합성 파일을 읽는 것까지 확인했다. 서비스의 안전한 재시작이나 여러 프로세스의 설정 일치는 검증하지 않았다.
require.cache의 항목 삭제를 일반적인 설정 갱신 해법으로 삼지는 않는다. 문서는 삭제 뒤 다음 require의 재로드를 설명하지만, 기존 코드가 보관한 객체 참조까지 교체하는 기능은 아니다. 이 글에서는 삭제를 실행하지 않았다. 다른 Node 버전, 사용자 정의 로더, 동시 쓰기 중 읽기, 파일 감시를 통한 자동 갱신도 확인하지 않았다.