안녕하세요! 그로스로그 입니다!😊 🌱
this가 왜 매번 다른지를 밑바닥부터 따라간 기록, 그리고 돌아가는 서버를 숫자로 지켜보는 시스템을 만든 기록. 3기 6회차 첫 번째입니다.
성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 매 회차 운영진이 모든 글을 읽고, 그중 특히 마음에 남은 글을 함께 골라 큐레이션합니다.
이번 회차는 큐레이션이 네 편이라 두 번에 나눠 소개합니다. 먼저 관찰하는 방식에 대한 두 편이에요. 하나는 안에서, 하나는 밖에서 봅니다.
01
🧩 실행 컨텍스트란?
이종경 님
실행 컨텍스트(Execution Context) — 자바스크립트 코드가 실행되는 환경 정보의 모음입니다.
글은 이걸 세 요소로 나눠 설명해요. Variable Environment · Lexical Environment · This Binding. 그리고 각각이 무엇을 담고 왜 나뉘어 있는지를 짚습니다.
여기에
this 바인딩 6가지 규칙과 클로저가 왜 메모리에 남는지까지 이어집니다. 자바스크립트 시리즈의 11번째, 마지막 글이에요.2회차의 런타임 글에서 "콜 스택에 실행 컨텍스트가 쌓인다"고 했었죠. 이번엔 그 실행 컨텍스트 안이 어떻게 생겼는지입니다.
조금 더 들어가 보면
이 글에서 가장 값진 부분은 왜 환경이 두 개인가를 설명한 대목입니다.
Variable Environment와 Lexical Environment는 처음엔 똑같은 내용으로 시작합니다. 그런데 왜 굳이 둘로 나눠뒀을까요? 글의 답이 명확합니다.
let/const 변수가 생길 때 새 렉시컬 환경을 만들어도, var 선언은 여전히 기존 함수 레벨 환경에 추가하기 위함입니다."var와 let의 스코프가 다르기 때문이에요. var는 함수 단위, let은 블록 단위. 이 두 규칙을 동시에 지키려면 저장소가 두 개여야 합니다.
function scopeTest() {
var x = 1; // 함수 스코프 (VariableEnvironment에 등록)
if (true) {
var x = 2; // 같은 함수 스코프의 x를 다시 선언 (기존 var x를 변경)
let y = 3; // 블록 스코프 (새 LexicalEnvironment에 등록)
console.log(x); // 2
console.log(y); // 3
}
console.log(x); // 2 -> 블록에서 바꾼 값이 유지된다
// console.log(y); // ReferenceError -> 블록을 벗어나면 접근 불가
}
블록에 들어가는 순간 새 Lexical Environment가 만들어집니다. let y는 거기 들어가요. 그런데 var x는 그 새 환경을 무시하고 함수의 Variable Environment로 찾아 올라갑니다. 그래서 블록 밖에서도 값이 유지되죠.
"var는 블록을 무시한다"를 외우는 것과, 저장소가 다르다는 걸 아는 건 다릅니다. 후자를 알면 처음 보는 상황에서도 예측할 수 있어요.
🧭 this가 매번 다른 이유
글이 this 바인딩을 여섯 가지로 정리한 부분이 실용적입니다. 그런데 관통하는 원리가 하나 있어요.
this는 함수가 선언된 곳이 아니라 호출된 방식으로 정해집니다.
· 일반 함수 호출 — 전역 객체 (엄격 모드에서는 undefined)
· 메서드 호출 — 호출한 객체
· 생성자 호출(new) — 새로 만들어진 객체
· call/apply/bind — 지정한 객체
· 화살표 함수 — 감싸고 있는 외부 컨텍스트의 this
마지막 하나만 규칙이 다릅니다. 그래서 글의 예제가 좋아요.
const arrowFunc = () => console.log(this); obj.arrowMethod = arrowFunc; obj.arrowMethod(); // window (화살표 함수는 obj에 바인딩되지 않음)
객체의 메서드로 붙였는데도 this가 obj가 아닙니다. 화살표 함수는 정의될 때 이미 this가 정해지기 때문이에요.
이게 실무에서 갈립니다. setTimeout이나 이벤트 핸들러 안에서 this가 엉뚱한 걸 가리키는 문제는 화살표 함수로 풀리고, 반대로 객체 메서드를 화살표 함수로 쓰면 오히려 깨집니다. 규칙이 다르다는 걸 알아야 골라 쓸 수 있어요.
🔒 클로저가 메모리에 남는 이유
글의 마지막 부분이 앞의 설명과 정확히 이어집니다.
보통 함수 실행이 끝나면 그 실행 컨텍스트와 지역 변수는 스택에서 사라지고 GC 대상이 됩니다. 그런데 클로저는 다릅니다.
function createCounter() {
let count = 0;
return function () {
count++;
console.log(`현재 count 값: ${count}`);
};
}
const counterA = createCounter();
counterA(); // 현재 count 값: 1
counterA(); // 현재 count 값: 2
const counterB = createCounter(); // 새로운 카운터 (별도 렉시컬 환경)
counterB(); // 현재 count 값: 1
createCounter()는 이미 끝났는데 count가 살아 있습니다. 반환된 함수가 그 환경을 참조하고 있기 때문이에요. 그리고 counterB는 완전히 다른 환경을 갖습니다.
그런데 글이 여기에 경고를 붙입니다.
클로저는 기능이자 메모리 누수의 원인입니다. 이벤트 핸들러를 등록하고 안 지우면, 그 핸들러가 붙들고 있는 환경 전체가 남거든요. 5회차의 removeEventListener 이야기와 정확히 이어지는 지점입니다.
큐레이터 노트
한 주제를 열한 편까지 밀고 간 시리즈의 마지막이라 뽑았습니다.
이 저자의 자바스크립트 시리즈는 2회차의 런타임에서 시작해 클래스를 거쳐 실행 컨텍스트로 끝납니다. 바깥에서 안으로 들어가는 순서예요. 엔진과 호스트 환경 → 콜 스택 → 그 안의 실행 컨텍스트 → 그 안의 환경 레코드.
그리고 Variable Environment와 Lexical Environment가 왜 둘인지를 짚은 게 이 글의 값어치입니다. 대부분의 자료는 둘을 나열만 하고 넘어가거든요. var와 let의 스코프 차이를 지키기 위해서라는 답을 찾은 건, 실제로 궁금해했다는 뜻이에요.
참고 자료 세 개를 밝힌 것도 좋았습니다. 영상, 기술 블로그, 개인 정리를 두루 읽고 자기 순서로 재구성한 글입니다.
02
📊 Spring Boot Actuator, Grafana & Prometheus 모니터링 구축
오연수 님
돌아가고 있는 애플리케이션을 어떻게 지켜볼 것인가에 대한 글입니다.
Spring Boot Actuator가 무엇을 주는지, Micrometer가 어디에 끼는지, Prometheus가 어떻게 수집하는지, Grafana가 어떻게 그리는지를 네 조각으로 나눠 설명해요.
그리고 실제 구축 과정이 이어집니다. 의존성 추가, yml 설정, Spring Security 예외 처리, 멀티 모듈에서 어디에 넣을지까지요.
2기 내내 프레임워크 내부를 파고들던 그분이 3기에는 운영으로 넘어오셨습니다.
조금 더 들어가 보면
이 글이 잘한 건 네 도구의 역할을 명확히 갈라놓은 것입니다. 다이어그램이 전부를 말해줘요.
[Spring Boot App]
│
└── Actuator
│
└── Micrometer (메트릭 수집기)
│
└── Prometheus Registry (형식 맞춤)
↓
[Prometheus 서버가 /actuator/prometheus 엔드포인트를 긁어서 수집]
각자가 딱 한 가지씩만 합니다.
· Actuator — 진단 엔드포인트를 열어줍니다. 다만 저장도 분석도 그래프도 안 합니다
· Micrometer — 실제로 수치를 모읍니다. 역시 수집만 하고 저장은 안 합니다
· micrometer-registry-prometheus — 그 수치를 Prometheus가 읽을 수 있는 형식으로 바꿔 내놓습니다
· Prometheus — 주기적으로 긁어가서 저장하고 쿼리(PromQL)할 수 있게 합니다
· Grafana — 저장된 데이터를 그래프로 그립니다
글이 각 단계마다 "➡️ 하지만 이건 안 함"을 붙여둔 게 좋습니다. 역할 경계가 선명해지거든요.
Prometheus가 "긁어간다(pull)"는 것도 짚어둘 만합니다. 애플리케이션이 데이터를 보내는 게 아니라, Prometheus가 주기적으로 찾아와 읽어갑니다. 그래서 애플리케이션 쪽은 그냥 엔드포인트만 열어두면 되고, 모니터링 서버가 죽어도 애플리케이션엔 영향이 없어요.
📝 원문에서는 이렇게 설정합니다
의존성 두 줄로 시작합니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
}
그리고 yml 설정이 두 개인데, 글이 이 둘의 차이를 정확히 갈라놓습니다.
management:
endpoints:
web:
exposure:
include: prometheus
management:
prometheus:
metrics:
export:
enabled: true
"문을 여는 설정"과 "데이터를 만드는 설정" — 둘 다 있어야 동작합니다. 하나만 하면 문은 열렸는데 안에 아무것도 없거나, 데이터는 만들어졌는데 문이 잠긴 상태가 되죠. 설정이 왜 두 개인지 모르면 하나만 넣고 헤매기 쉬운 지점입니다.
그리고 실무에서 반드시 걸리는 부분도 짚습니다. "Spring Security 사용 시 /actuator/prometheus 경로를 허용해야 한다." 시큐리티가 붙어 있으면 Prometheus의 요청이 401로 막히거든요.
멀티 모듈에서 어디에 의존성을 넣을지도 명확합니다. @SpringBootApplication이 있는 모듈이요. 실제로 실행되는 주체여야 메트릭을 노출할 수 있으니까요.
🧭 그리고 무엇을 봐야 하는가
글이 정리한 주요 메트릭이 실전에서 그대로 쓰입니다.
| 메트릭 | 의미 |
|---|---|
jvm.memory.used |
JVM이 현재 사용 중인 메모리 |
jvm.gc.pause |
GC가 걸린 시간 |
process.cpu.usage |
애플리케이션의 CPU 비율 |
http.server.requests |
요청 수, 처리 시간, 상태 코드 |
logback.events |
로그 이벤트 수 (info, error 등) |
이 중 jvm.gc.pause가 특히 흥미롭습니다. 2회차에서 다뤘던 Stop-The-World를 실제로 재는 숫자거든요. "가끔 응답이 튄다"는 증상이 GC 때문인지 여기서 확인됩니다. 개념으로 알던 것을 측정 가능한 값으로 바꾼 셈이에요.
logback.events도 실용적입니다. 에러 로그 수가 갑자기 늘면 알림을 보내게 할 수 있죠. 사람이 로그를 계속 들여다보지 않아도 됩니다.
큐레이터 노트
"만드는 일"에서 "운영하는 일"로 넘어간 기록이라 뽑았습니다.
개발을 배울 때는 기능을 만드는 데 집중합니다. 그런데 서비스를 실제로 굴리기 시작하면 "지금 잘 돌고 있나"가 더 중요해져요. 메모리가 새고 있지는 않은지, 응답이 느려지고 있지는 않은지를 숫자로 볼 수 있어야 합니다.
그리고 이 글은 역할 경계를 정확히 그었습니다. Actuator·Micrometer·Prometheus·Grafana는 이름이 비슷비슷해서 처음엔 뭐가 뭔지 헷갈리는데, "각각 무엇을 하고 무엇을 안 하는지"를 나눠주니 구조가 한 번에 들어옵니다.
막히는 지점을 미리 적어둔 것도 좋았어요. 시큐리티 경로 허용, 멀티 모듈에서의 위치, 설정 두 개의 차이. 전부 실제로 해봐야 알 수 있는 것들입니다.
이 저자는 1기부터 3기까지 꾸준히 큐레이션되고 계세요. 스프링 내부 → 코틀린 컴파일 → 데이터 정합성 → 모니터링으로, 다루는 층이 계속 넓어지고 있습니다.
💡 두 편을 겹쳐 읽으며
한 편은 자바스크립트 엔진 내부, 한 편은 서버 운영 도구입니다. 접점이 없어 보이죠.
그런데 두 글은 같은 것을 다릅니다. 둘 다 "지금 무슨 일이 일어나고 있는지 보는 방법"입니다. 다만 보는 위치가 정반대예요.
이종경 님은 안에서 봅니다. 코드 한 줄이 실행될 때 어떤 환경이 만들어지고, 변수가 어디에 저장되고, this가 무엇으로 정해지는지를요. 원리를 알면 결과를 예측할 수 있게 됩니다.
오연수 님은 밖에서 봅니다. 이미 돌아가고 있는 애플리케이션의 메모리, CPU, 요청 수를 숫자로 지켜보죠. 안을 몰라도 이상 징후를 알아챌 수 있게 됩니다.
둘 다 필요합니다. 그리고 실은 서로를 필요로 해요.
모니터링 그래프에서 메모리가 계속 우상향하는 걸 봤다고 합시다. 그래프는 "새고 있다"까지만 알려줍니다. 어디서 새는지는 안 나와요. 그때 클로저가 환경을 붙들고 있다는 걸 아는 사람이 원인을 찾습니다.
반대로 원리만 알고 관측이 없으면 문제가 생긴 줄도 모릅니다. 클로저를 잘 알아도, 실제로 어느 코드가 메모리를 붙들고 있는지는 재봐야 알거든요.
아는 것과 보는 것 — 둘이 만나야 고칠 수 있습니다.
이번 회차에서 가져갈 것
1. var와 let은 저장소가 다르다. 함수 스코프(Variable Environment)와 블록 스코프(Lexical Environment)요. "var는 블록을 무시한다"의 이유가 여기 있습니다.
2. this는 호출 방식이 정한다. 선언된 위치가 아니라요. 단 화살표 함수만 예외로, 정의될 때 정해집니다. 객체 메서드를 화살표 함수로 쓰면 깨져요.
3. 클로저는 기능이자 누수 원인. 내부 함수가 살아 있으면 외부 환경 전체가 메모리에 남습니다. 안 쓰는 핸들러와 참조는 정리하세요.
4. 모니터링 설정은 두 개가 한 쌍. 엔드포인트를 여는 설정과 Prometheus 형식으로 만드는 설정이 따로입니다. 하나만 하면 동작하지 않아요.
5. jvm.gc.pause를 대시보드에 올려두기. "가끔 응답이 튄다"의 원인이 GC인지 아닌지가 이 숫자로 갈립니다. 개념을 측정값으로 바꿔두면 추측이 확인으로 바뀝니다.
좋은 글 남겨주신 이종경 님, 오연수 님께 감사드립니다! 😊
6회차 두 번째 큐레이션으로 이어집니다! 🌱
- 에디터 · 성장일지 큐레이터 -
※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.