[트러블 슈팅] 멀티프로세스 도입 테스트 Value is not an object: null - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 문제 상황
a. 테스트 환경
- 단일 프로세스 → 다중 프로세스 전환에서 속도 향상 테스트
- 시나리오
- 10명의 vu가 20장의 사진에 대해 embedding→categories 요청 보냄
- iteration 간 3~5초 sleep
b. 테스트 결과
- k6 로그에서 위와 같은 에러 발생
- 다만 cpu, gpu 서버에서는 에러 로그 발견되지 않음
2. 상황 식별 및 가설 설정
a. 상황 식별
- TypeError: Value is not an object: null
- JavaScript에서 null 값을 객체처럼 다루려 할 때 발생.
- 예상 원인
- 서버에서 응답을 정상적으로 반환하지 못했는데 그걸 json으로 파싱하려 해서 null 반환됨.
- 해당 값을 객체처럼 이용하려 할 때, 문제 발생.
import http from 'k6/http';
import { sleep } from 'k6';
import { randomSeed, randomIntBetween } from 'k6';
randomSeed(__VU);
export const options = {
vus: 10,
duration: '10m',
};
const TOTAL_IMAGES = 600;
const BATCH_SIZE = 20;
const embeddingUrl = 'http://localhost:8000/api/albums/embedding';
const params = {
headers: {
'Content-Type': 'application/json',
},
};
export default function () {
const vu = __VU;
const iter = __ITER;
const groupIndex = (vu - 1) * 1000 + iter;
const start = (groupIndex * BATCH_SIZE) % TOTAL_IMAGES + 1;
const end = start + BATCH_SIZE - 1;
const images = [];
for (let i = start; i <= end; i++) {
const idx = ((i - 1) % TOTAL_IMAGES) + 1;
images.push(`img${String(idx).padStart(3, '0')}.jpg`);
}
const payload = JSON.stringify({ images });
// ✅ Embedding 요청만 전송
const embedRes = http.post(embeddingUrl, payload, params);
if (!embedRes || embedRes.status === 0) {
console.error(`❌ [VU:${vu} ITER:${iter}] 임베딩 요청 실패 - 연결 거부됨`);
} else if (!embedRes.body) {
console.error(`❌ [VU:${vu} ITER:${iter}] 임베딩 응답 body 없음`);
} else {
let embedBody = {};
try {
embedBody = embedRes.json();
} catch (err) {
console.error(`❌ [VU:${vu} ITER:${iter}] embedRes JSON 파싱 실패: ${err.message}`);
}
if (embedRes.status !== 201) {
console.warn(`[WARN] [VU:${vu} ITER:${iter}] 임베딩 실패 - status: ${embedRes.status}`);
} else {
console.log(`[OK] [VU:${vu} ITER:${iter}] 임베딩 응답 - message: ${embedBody?.message}`);
}
}
sleep(randomIntBetween(3, 5));
}
b. 가설 설정
- 요청이 어떠한 이유로 서버에 전달되지 못한 상태에서 응답을 가져와 res.status와 같이 이용하려 해서 null은 객체가 아니라는 에러가 발생했을 것이다.
3. 가설 검증
import http from 'k6/http';
import { sleep } from 'k6';
import { randomSeed, randomIntBetween } from 'k6';
randomSeed(__VU);
export const options = {
vus: 10,
duration: '10m',
};
const TOTAL_IMAGES = 600;
const BATCH_SIZE = 20;
const embeddingUrl = 'http://localhost:8000/api/albums/embedding';
const params = {
headers: {
'Content-Type': 'application/json',
},
};
export default function () {
const vu = __VU;
const iter = __ITER;
const groupIndex = (vu - 1) * 1000 + iter;
const start = (groupIndex * BATCH_SIZE) % TOTAL_IMAGES + 1;
const end = start + BATCH_SIZE - 1;
const images = [];
for (let i = start; i <= end; i++) {
const idx = ((i - 1) % TOTAL_IMAGES) + 1;
images.push(`img${String(idx).padStart(3, '0')}.jpg`);
}
const payload = JSON.stringify({ images });
// ✅ embedRes가 null인지 여부만 확인
const embedRes = http.post(embeddingUrl, payload, params);
const isNull = embedRes === null;
console.log(`[VU:${vu} ITER:${iter}] embedRes is null: ${isNull}`);
sleep(randomIntBetween(3, 5));
}
- 위와 같이 객체의 필드를 이용하는 부분을 모두 제거하고 테스트를 재진행했다. 그러나 동일한 에러가 발생했다.
4. 문제 재식별
ERRO[0001] TypeError: Value is not an object: null
running at default (file:///Users/kimdahyun/dahyun1008/k6_script/test2.js:44:27(106)) executor=constant-vus scenario=default source=stacktrace
INFO[0001] [VU:3 ITER:0] embedRes is null: false source=console
ERRO[0001] TypeError: Value is not an object: null
running at default (file:///Users/kimdahyun/dahyun1008/k6_script/test2.js:44:27(106)) executor=constant-vus scenario=default source=stacktrace
INFO[0002] [VU:7 ITER:0] embedRes is null: false source=console
ERRO[0002] TypeError: Value is not an object: null
running at default (file:///Users/kimdahyun/dahyun1008/k6_script/test2.js:44:27(106)) executor=constant-vus scenario=default source=stacktrace
INFO[0002] [VU:4 ITER:0] embedRes is null: false source=console
ERRO[0002] TypeError: Value is not an object: null
running at default (file:///Users/kimdahyun/dahyun1008/k6_script/test2.js:44:27(106)) executor=constant-vus scenario=default source=stacktrace
-
위 에러 로그에서 44번 줄에 문제가 있다고 되어 있는것을 뒤늦게 발견했다.
-
코드를 vscode에 옮겨 확인해보니
sleep(randomIntBetween(3, 5));
이 부분이었다. randomIntBetween가 문제의 원인이었고 sleep(3)과 같이 수정해서 문제를 해결했다.
-
grafana 공식 문서에서
import { sleep } from 'k6'; import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; export default function () { // code ... sleep(randomIntBetween(1, 5)); // sleep between 1 and 5 seconds. }
해당 함수를 이용하는 부분이 있는데 null에 대한 설명은 없다.
→ import해온 라이브러리가 다른게 원인이었을 것 같다.
5. 가설 재검증
import http from 'k6/http';
import { sleep } from 'k6';
import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
export const options = {
vus: 10,
duration: '10m',
};
const TOTAL_IMAGES = 600;
const BATCH_SIZE = 20;
const embeddingUrl = 'http://localhost:8000/api/albums/embedding';
const params = {
headers: {
'Content-Type': 'application/json',
},
};
export default function () {
const vu = __VU;
const iter = __ITER;
const groupIndex = (vu - 1) * 1000 + iter;
const start = (groupIndex * BATCH_SIZE) % TOTAL_IMAGES + 1;
const end = start + BATCH_SIZE - 1;
const images = [];
for (let i = start; i <= end; i++) {
const idx = ((i - 1) % TOTAL_IMAGES) + 1;
images.push(`img${String(idx).padStart(3, '0')}.jpg`);
}
const payload = JSON.stringify({ images });
// ✅ embedRes가 null인지 여부만 확인
const embedRes = http.post(embeddingUrl, payload, params);
const isNull = embedRes === null;
console.log(`[VU:${vu} ITER:${iter}] embedRes is null: ${isNull}`);
sleep(randomIntBetween(3, 5));
}
- 임포트 경로를 수정하니 정상 동작한다.
6. 정리
- randonIntBetween은 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; 에서 임포트해와야 한다.
7. 참고자료
https://grafana.com/docs/k6/latest/javascript-api/jslib/utils/randomintbetween/