Last active
March 14, 2023 18:09
-
-
Save cgiosy/26a140cfc3e2f021ba7b672e7651813a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fs = require('fs'); | |
const filenum = 30; // or 100 | |
const lines = 2000000; // or 1000000 | |
const errors = 100000; | |
const ratio = lines / errors | 0; | |
const count = lines / ratio | 0; | |
for (let i = 1; i <= filenum; i += 1) { | |
const stream = fs.createWriteStream(`test${i}-log.xml`, { flags: 'a' }); | |
let text = `<error><!\[CDATA\[${i}#${Math.random()}\]\]><\/error>\n`; | |
for (let j = 2; j <= ratio; j += 1) | |
text += `<info><!\[CDATA\[${i}#${Math.random()}\]\]><\/info>\n`; | |
text | 0; | |
for (let j = 1; j <= count; j += 1) | |
stream.write(text); | |
stream.end(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { resolve } = require('path'); | |
const { writeFile, readFile, readdir } = require('fs').promises; | |
const walkDir = (fn, startingDir = '.') => { | |
const _walkDir = (currentDir) => readdir(currentDir, { withFileTypes: true }).then((children) => Promise.all( | |
children.map((child) => { | |
const res = resolve(currentDir, child.name); | |
if (child.isDirectory()) return _walkDir(res); | |
return Promise.resolve(fn(res)); | |
}) | |
)); | |
return _walkDir(startingDir); | |
}; | |
const flatten = (str) => { | |
str | 0; | |
return str; | |
}; | |
const extractErrors = async () => { | |
const filenames = []; | |
await walkDir((filename) => { | |
if (!filename.toLowerCase().endsWith('-log.xml')) return; | |
filenames.push(filename); | |
}); | |
let errors = []; | |
for (const filename of filenames) { | |
console.log(filename); | |
const text = await readFile(filename, { encoding: 'utf-8' }); // Out Of Memory! mem usage 97~99% | |
errors = errors.concat( | |
(text.match(/<error><!\[CDATA\[(.+?)\]\]><\/error>/g) || []) | |
.filter(str => !str.includes(' ')) | |
.map(str => str.slice(16, -11)) | |
// .map(flatten) | |
); | |
} | |
await writeFile('errors.txt', JSON.stringify([...new Set(errors)])); | |
}; | |
setTimeout(extractErrors, 0 * 1000); |
삽질 과정:
- 100~200MB 정도의 로그 파일 30 ~ 100개에서 특정 문자열을 찾아 분석해야 했다.
- 적당히 짜고 노드로 실행시켰더니 메모리 초과가 떴다.
substr
때문에 GC가 안 도는구나 싶어 flatten 함수를 추가했다. (기존에 알려져 있던 대로라면str | 0
혹은Number(str)
처럼 하면 문자열이flatten
되어 slice에 대한 참조가 해제되어야 한다. #1 #2) - 다시 돌려봤으나 상황이 바뀌지 않았다.
flatten
이 잘못되었나 싶어 다른 방법으로 좀 수정해보거나((str += ' ') | 0
등),substr
을 지워봤다. 문제는 그대로였다. - inspect로 연결해 확인해 보니
toString
에서 메모리의 98%를 차지했다. UCS-2로의 변환이 문제거나 뭔가 내부적으로 비효율적인 게 아닐까 추측했다. - stream을 사용해보거나 노드 옵션을 통해 최대 메모리를 늘리는 방법을 고려해보았다. 하지만 근본적인 문제를 해결하지 않고 도피하는 선택이었고 일단 짜기 귀찮았다.
- 진전이 없었다. 질문 글을 올렸다. #
- 한동안 댓글에 달리는 추측을 실험해 보았고, 시도할 수록 더더욱 미궁에 빠졌다.
- 답변을 얻기 위해 상황 재현을 위한 로그 제네레이터를 짜서 올렸다.
- GC가 왜 안 도나 싶어 삽질을 해보던 도중
errors
의 길이를 제한하니 (당연하게도) 원하는 답은 안 구해지지만 돌긴 한다는 것을 확인했다.toString
등 변환 과정이 아니라 어디에선가 참조가 해제되지 않고 있다는 생각으로 다시 기울었다. flatten
함수와substr
에 대한 검증을 마친 상태였기 때문에, 해당 부분은 문제되지 않을 것이라 생각했다. 그런데 저기를 빼놓고 보니 문제될만한 부분이 없었다. 여전히 답은 보이지 않았다.- slice 참조 해제 관련 문제일 것이란 댓글이 달렸다. substr이 아니라 정규식 match에서도 참조가 생긴단 점은 처음 알았지만, 일단은 앞서 했던 예상 범위 내였다.
flatten
함수로 해결되지 않고 있었기 때문에, 좀 다른 부분이 문제일 것이라 생각했다. - 해당 댓글에 다른 사람이 이게 문제가 맞고 여기 있는 방법을 적용했더니 된다는 답글이 달렸다.
- 화들짝 놀라서 호다닥 적용해봤다. 진짜 됐다.
- 뭐지 싶어서 기존에 안 되던 코드에서
flatten
함수의str | 0
부분만str = (' ' + str).slice(1)
로 바꿨다. 됐다. - 범인은 믿고 있던
flatten
이었다...!!! 노드가 버전업돼서 그런 것인지 아무 작동도 수행하지 않았고, 따라서 내부적으로 slice에 대한 참조 역시 해제되지 않아 GC가 문자열을 수집하지 못하는 것이었다...!
배운 점:
- V8의 특징을 이용한 최적화는 버전이나 환경, 상황에 따라 언제든 무력화될 수 있다.
- 직접적으로 substr 계열을 호출하는 것뿐만 아니라 match와 같은 함수도 문자열의 slice를 반환해 참조를 만든다.
- 인터넷에 올라와 있는 글, 라이브러리 등을 너무 맹신하지 말자. 틀릴 수도 있고, 맞았지만 최근에 바뀐 내용일 수도 있다.
- 고언어나 쓰자
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
V8 error log: