金沙娱乐境外稳定平台QQ 5370604
velog
缅甸大型赌场圣淘沙娱乐 主营 :百家乐 牛牛 龙虎 电子等多种游戏 支持 :支付宝 微信 银行卡 USDT等 pg注册官网: sts2516.com 百家乐官网 : 228950.com 客服QQ 5370604 客服飞机✈️ @stssy08 客服蝙蝠 149205082
Score: 54.4Confidence: 49%
View offerLoading the catalog…
THE AI OPPORTUNITY INDEX
Find your next AI tool. Explore free access, trials, and credits — all in one place.
velog
缅甸大型赌场圣淘沙娱乐 主营 :百家乐 牛牛 龙虎 电子等多种游戏 支持 :支付宝 微信 银行卡 USDT等 pg注册官网: sts2516.com 百家乐官网 : 228950.com 客服QQ 5370604 客服飞机✈️ @stssy08 客服蝙蝠 149205082
Score: 54.4Confidence: 49%
View offervelog
缅甸圣淘沙娱乐赌场 圣淘沙赌场官网 228950.com 经理QQ:5370604 圣淘沙PG官网 sts2516.com 经理飞机TG@stssy08 东南亚线上博彩网投首选平台✅ 公司位于缅甸 佤邦孟波 16年大型实体赌场 主营游戏:百家乐 牛牛 龙虎 大小 单双 炸金花 推筒子 网投、电投、现场三合一 操作简单、方便快捷、信誉度高 现场-线上1:1同步直播 支持24小时验证现场 资金安全干净 大额提款无忧 上下分支持:USDT 银行卡 支付宝 微信 缅币KBZpay
Score: 54.4Confidence: 49%
View offervelog
관리자 대시보드에 서버 로그아웃 API를 붙여 머지한 날, 브라우저 점검 스크립트로 로그인 흐름을 다시 돌렸습니다. A 탭에서 로그인하고, 그 뒤에 연 B 탭에서 로그아웃한 다음, A 탭에서 다시 로그인하는 순서였어요. 로그인 버튼을 눌렀는데 화면은 로그인 화면 그대로였고 에러 창이 하나 떴습니다. POST /auth/login 400 로그아웃된 토큰입니다. 토큰을 받으러 가는 요청이 토큰 때문에 거절당한 거죠. 같은 버튼을 한 번 더 누르니 그때는 로그인이 됐습니다. 결론부터 말하면 A 탭의 axios instance.defaults 에 남아 있던 옛 토큰이 로그인 요청에 실려 나갔고, 요청 인터셉터 한 줄로 막았습니다. 전날 같은 파일에서 고친 에러 창 버그도 instance.defaults 에서 나온 것이라 함께 적습니다. 로그인 요청 헤더에 옛 토큰이 있었다 처음엔 스크립트가 폼을 제대로 못 채운 줄 알았습니다. 폼 상태를 찍어 보니 두 칸 모두 값이 들어 있었고 검증 에러도 없었어요. 같은 계정으로 터미널에서 토큰 없이 curl 로그인을 보내니 200이 왔고요. 그래서 브라우저가 보낸 요청을 봤습니다. XMLHttpRequest.prototype.setRequestHeader 를 가로채 찍어 보니 로그인 요청에 Authorization: Bearer ... 가 붙어 있었습니다. 개발 서버에서 API 모듈을 동적 import 해 instance.defaults.headers.common.Authorization 도 찍어 봤더니, B 탭에서 방금 로그아웃한 바로 그 토큰이 들어 있더라고요. 토큰 없이 보낸 curl은 200, 옛 토큰을 실은 브라우저 요청은 400이었습니다. 게이트웨이는 로그인 요청이라도 헤더에 토큰이 있으면 그것부터 검사한다는 뜻이죠. localStorage는 탭끼리 나누고 defaults는 나누지 않는다 로그인과 요청 인터셉터를 줄여 옮기면 이렇습니다. // api.js (수정 전) export const instance = axios.create({ baseURL: '/api' }) export function onLoginSuccess(token) { localStorage.setItem('token', token) instance.defaults.headers.common.Authorization = `Bearer ${token}` // 이 탭의 메모리에만 남는다 } export function clearSession() { localStorage.removeItem('token') delete instance.defaults.headers.common.Authorization // 부른 탭의 기본값만 지운다 } instance.interceptors.request.use((config) => { if (config.url === '/auth/login' || config.url === '/auth/refresh') { // 토큰을 싣지 않는 요청 // delete config.headers.Authorization return config } const token = localStorage.getItem('token') if (token) config.headers.Authorization = `Bearer ${token}` return config }) localStorage 는 같은 출처의 탭끼리 공유하고, instance.defaults 는 그 탭의 메모리에만 있습니다. 인터셉터는 요청마다 localStorage 에서 토큰을 읽어 싣고, 로그인과 토큰 갱신 요청만 건너뜁니다. B 탭에서 로그아웃하면 A 탭의 instance.defaults 는 어떻게 될까요? 그대로입니다. B 탭의 로그아웃 버튼이 부르는 clearSession() 은 B 탭의 기본값만 지워요. 인터셉터는 로그인 요청에 헤더를 새로 붙이지 않을 뿐, 이미 붙어 있던 기본값을 걷어 내지는 않았고요. 걷어 내는 줄은 있었습니다. 2024년 6월부터 주석인 채로요. 탭 둘을 iframe으로 띄우고 같은 가짜 게이트웨이를 보게 해서 이 순서를 재현했습니다. 번호는 요청 순서입니다. A 탭 위쪽 칸을 보면 저장소는 비었는데 defaults에만 token-1이 남아 있어요. B 탭의 defaults는 비어 있습니다. B 탭은 로그인을 직접 하지 않고 저장소의 토큰만 읽어 썼으니까요. 실제 앱에서도 B 탭은 다음 요청을 토큰 없이 보내 "헤더가 없다"는 다른 에러를 받았고, 이 400은 처음 로그인한 탭에서만 났습니다. 그날 머지한 서버 로그아웃이 버그를 꺼냈다 서버 로그아웃 API를 붙인 까닭은 화면의 토큰만 지우면 서버 세션이 남아, 로그아웃한 토큰으로 API가 계속 통했기 때문입니다. 그 시절에는 옛 토큰이 로그인 요청에 실려 가도 서버에서 유효한 토큰이니 검사를 통과했을 거예요. 그날 머지한 변경이 옛 토큰을 정말로 무효로 만들면서 이 줄이 처음 문제가 됐습니다. 두 번째 시도에 로그인이 된 이유는 응답 인터셉터에 있었습니다. // 응답 인터셉터 (줄임) if (error.response?.data?.code === LOGGED_OUT_TOKEN) { clearSession() // 첫 실패 때 A 탭의 기본값도 여기서 지워진다 } 첫 실패가 다음 시도의 길을 치워 준 셈입니다. 로그인 요청에서 헤더를 걷어 내기 if (config.url === '/auth/login' || config.url === '/auth/refresh') { - // 토큰을 싣지 않는 요청 - // delete config.headers.Authorization + // 다른 탭의 로그아웃으로 무효가 된 토큰이 기본값에 남아 있을 수 있다 + config.headers.delete('Authorization') return config } axios 1.7.2는 요청 인터셉터를 돌리기 전에 defaults.headers.common 을 그 요청의 config.headers 로 합쳐 둡니다( lib/core/Axios.js 의 Flatten headers 부분). 그래서 인터셉터에서 지우면 실제로 나가는 요청에서도 빠집니다. onLoginSuccess 에서 defaults에 넣는 줄을 지우는 편이 더 근본적이긴 합니다. 그런데 defaults에 토큰을 넣는 곳이 로그인, 토큰 갱신, 래퍼 옵션까지 세 군데라서 이번에는 나가는 길목 한 곳에서 막았어요. 사실 이 코드에서는 주석만 풀어도 고쳐집니다. 그래도 delete 연산자 대신 headers.delete() 메서드를 쓴 건, 누가 헤더 이름을 소문자로 넣어도 지워지게 하려고서입니다. // 기본값을 소문자 이름으로 넣었다면 instance.defaults.headers.common.authorization = 'Bearer old-token' // 요청 인터셉터 안에서 delete config.headers.Authorization // 나간 요청에 'Bearer old-token'이 남는다 config.headers.delete('Authorization') // 나간 요청에 헤더가 없다 config.headers 는 AxiosHeaders 인스턴스라서 메서드는 이름을 대소문자 구분 없이 찾습니다. 0.x에서는 이렇게 쓰면 안 돼요. 0.27.2로 돌려 보니 인터셉터 안의 config.headers.delete 가 DELETE 요청용 헤더 객체여서 TypeError: config.headers.delete is not a function 이 났고, 토큰도 config.headers.common 밑에 있었습니다. 4 로그인 요청의 Authorization 줄만 보면 됩니다. 나머지 순서는 수정 전과 같아요. 시험은 axios의 adapter 를 바꿔 끼워 실제로 나가려던 config를 붙잡는 식으로 썼습니다. 고치기 전 코드로 돌리면 실패하는 것까지 봤어요. it('로그인 요청에는 기본값에 남은 옛 토큰을 싣지 않는다', async () => { const sent = [] instance.defaults.adapter = async (config) => { sent.push(config) return { data: {}, status: 200, statusText: 'OK', headers: {}, config } } instance.defaults.headers.common.Authorization = 'Bearer stale-token' localStorage.removeItem('token') // 다른 탭이 로그아웃한 상태 await instance.post('/auth/login', { id: 'tester', password: 'pw' }) expect(sent[0].headers.Authorization).toBeUndefined() }) 응답을 기다리는 사이 바뀐 옵션 전날 고친 버그는 탭 사이가 아니라 요청 사이에서 값이 섞였습니다. 저희 API 래퍼는 부를 때마다 받은 옵션을 instance.defaults 에 써 넣어요. 그중 하나가 "에러 안내는 화면이 직접 하니 공통 에러 창은 띄우지 마라"는 옵션입니다. // api.js (수정 전) export const api = (options = {}) => { instance.defaults.silentError = options.silentError ?? false return { getItems: () => instance.get('/items'), getStats: () => instance.get('/stats') } } instance.interceptors.response.use( (response) => response, (error) => { if (!instance.defaults.silentError) showErrorDialog(error) // 응답이 도착한 때의 값을 읽는다 return Promise.reject(error) } ) // 목록 화면 api({ silentError: true }).getItems().catch(() => toast('목록을 불러오지 못했습니다.')) api().getStats() 목록 요청이 응답을 기다리는 사이 통계 요청이 api() 를 부르면 값이 false 로 덮입니다. 왼쪽 5번이 응답 인터셉터가 읽은 값입니다. 목록 요청은 true 로 보냈는데 false 를 읽어 토스트 위에 공통 에러 창을 겹쳐 띄웠어요. 반대도 됩니다. 통계 요청이 실패 응답을 기다리는 사이 목록 화면이 api({ silentError: true }) 를 부르면, 공통 창이 떠야 할 통계 실패가 안내 0건으로 묻힙니다. 같은 axios로 돌려서 두 경우 모두 확인했습니다. axios는 요청을 보낼 때 instance.defaults 의 키를 그 요청의 config로 복사해 둡니다. 직접 만든 키도 복사되는지 확인해 봤어요. instance.defaults.silentError = true // api({ silentError: true }) const pending = instance.get('/slow') // 400 으로 끝날 요청 instance.defaults.silentError = false // 기다리는 사이 api() // 응답 인터셉터에서 찍은 값 // instance.defaults.silentError: false // error.config.silentError: true 그래서 요청을 보낸 때의 값을 읽도록 고쳤습니다. 요청 인터셉터에서 던진 에러처럼 config 가 없는 경우도 있어 ?. 를 붙였어요. - if (!instance.defaults.silentError) showErrorDialog(error) + if (!error.config?.silentError) showErrorDialog(error) blob 다운로드에도 같은 꼴이 남았다 파일을 내려받는 api({ responseType: 'blob' }) 도 instance.defaults.responseType 을 바꾸고, blob 응답이 성공했을 때만 'json' 으로 되돌립니다. api({ responseType: 'blob' }).downloadFile(id) // defaults.responseType = 'blob' api().getItems() // 다운로드 응답 전에 나가면 이 요청도 blob 으로 받는다 다운로드를 기다리는 사이 나간 요청이 blob으로 나가는 건 axios 1.7.2로 확인했습니다. 다운로드가 실패하면 되돌리는 줄이 아예 돌지 않으니 그 뒤 요청도 blob으로 나갈 텐데, 이건 코드로만 읽었고 화면에서 재현하지는 않았어요. 이번 수정은 응답 인터셉터가 읽는 쪽만 바꿨기 때문에 이 건은 그대로입니다. 래퍼가 defaults를 고치지 않고 옵션을 요청 config로 넘기게 바꾸면 둘 다 풀리는데, 그러면 옵션을 넘기는 서른한 곳의 요청이 나가는 방식이 한꺼번에 바뀝니다. 그 화면들을 다시 시험할 시간을 따로 잡으려고 합니다. 탭 사이 로그아웃 동기화도 남았습니다. 지금은 B 탭에서 로그아웃해도 A 탭은 메뉴를 누르거나 다음 요청이 실패하기 전까지 로그인된 화면에 머물러요. storage 이벤트로 토큰이 지워진 걸 듣고 A 탭도 함께 정리하는 게 다음 할 일입니다. 혹시 같은 꼴이 있는지 grep -rn "defaults\." src 로 찾아보세요. 앱이 시작할 때 한 번 넣는 baseURL 같은 값은 괜찮습니다. 로그인할 때 넣는 토큰이나 호출할 때마다 바꾸는 옵션이 나오면, 그 줄이 이 글의 두 버그가 난 자리와 같은 모양입니다.
Score: 54.4Confidence: 49%
View offervelog
[천문학 3] 별까지의 거리는 어떻게 측정할까? — 연주시차와 우주 거리 사다리 앞에서 천구와 천체의 위치를 공부했다. 그렇다면 이제 새로운 질문이 생긴다. 별이 어느 방향에 있는지는 알 수 있다. 하지만 그 별이 우리에게서 얼마나 멀리 떨어져 있는지는 어떻게 알 수 있을까? 천문학자는 별까지 직접 갈 수 없다. 그럼에도 우리는 어떤 별이 10광년 떨어져 있는지, 어떤 은하가 수백만 광년 떨어져 있는지를 알고 있다. 그 비밀은 각도, 밝기, 빛의 특성 을 이용하는 데 있다. 이번 글에서는 천문학에서 거리를 측정하는 기본 원리를 알아본다. 1. 천문학에서 거리 측정은 왜 어려울까? 지구에서는 거리를 직접 측정할 수 있다. 예를 들어 자 줄자 레이저 거리 측정기 GPS 등을 사용할 수 있다. 하지만 별까지의 거리는 이렇게 직접 측정할 수 없다. 별은 너무 멀리 떨어져 있기 때문이다. 그래서 천문학에서는 거리마다 다른 방법을 사용한다. 이러한 여러 거리 측정 방법을 단계적으로 연결하는 체계를 우주 거리 사다리(Cosmic Distance Ladder) 라고 한다. 2. 우주 거리 사다리란? 우주 거리 사다리는 하나의 측정 방법만 사용하는 것이 아니다. 가까운 천체는 가까운 천체에 맞는 방법을 쓰고, 먼 천체는 더 멀리까지 사용할 수 있는 다른 방법을 쓴다. 가까운 별 ↓ 연주시차 더 먼 별 ↓ 밝기 비교 더 먼 은하 ↓ 세페이드 변광성 아주 먼 은하 ↓ Ia형 초신성 즉, 가까운 거리 측정법 ↓ 그 결과를 이용해 더 먼 거리 측정법을 보정 ↓ 다시 더 먼 우주까지 확장 하는 방식이다. 그래서 사다리라는 이름이 붙었다. 3. 가장 기본적인 방법 — 연주시차 가까운 별의 거리를 측정하는 대표적인 방법은 연주시차(Annual Parallax) 이다. 연주시차는 지구가 태양 주위를 공전한다는 사실을 이용한다. 4. 연주시차는 왜 발생할까? 손가락 하나를 눈앞에 세워보자. 왼쪽 눈만 뜨고 손가락을 본다. 그다음 오른쪽 눈만 뜨고 본다. 그러면 손가락의 위치가 뒤쪽 배경에 대해 조금 이동한 것처럼 보인다. 손가락이 실제로 움직인 것은 아니다. 관측 위치가 바뀌었기 때문이다. 이것이 시차의 기본 원리다. 5. 지구도 같은 방식으로 움직인다 지구는 태양을 중심으로 공전한다. 따라서 6개월 간격으로 관측하면 지구의 위치가 크게 달라진다. 1월의 지구 \ \ 태양 / / 7월의 지구 이 두 위치에서 같은 별을 관측하면, 가까운 별은 아주 멀리 있는 배경별에 비해 위치가 약간 움직인 것처럼 보인다. 이 겉보기 위치 변화가 연주시차다. 6. 가까운 별일수록 시차가 크다 이 개념은 직관적으로 이해할 수 있다. 손가락을 눈 가까이에 두면 눈을 바꿔 뜰 때 위치 변화가 크게 보인다. 반대로 손가락을 멀리 두면 위치 변화가 작아진다. 별도 마찬가지다. 가까운 별 → 시차 큼 먼 별 → 시차 작음 따라서 시차의 크기를 측정하면 별까지의 거리를 계산할 수 있다. 7. 연주시차의 기본 공식 연주시차에서는 다음 관계가 사용된다. d = 1 / p 여기서 d = 거리(parsec) p = 연주시차(arcsecond) 이다. 예를 들어 어떤 별의 연주시차가 1 arcsecond 라면 거리는 1 parsec 이다. 그래서 파섹이라는 거리 단위 자체가 연주시차와 연결되어 있다. 8. 파섹의 의미 앞에서 1 pc ≈ 3.26광년 이라고 배웠다. 이번에는 그 정의를 이해할 수 있다. 어떤 별의 연주시차가 1각초 라면 그 별까지의 거리를 1파섹 이라고 한다. 즉, p = 1 arcsecond ↓ d = 1 parsec 이다. 9. 각초란 무엇일까? 각도를 더 작은 단위로 나누면 1° = 60 arcminute 그리고 1 arcminute = 60 arcsecond 이다. 따라서 1° = 3600 arcsecond 이다. 별의 연주시차는 대개 매우 작기 때문에 각도 단위보다 훨씬 작은 각초(arcsecond) 를 사용한다. 10. 연주시차 예시 어떤 별의 연주시차가 0.5 arcsecond 라고 해보자. 공식은 d = 1 / p 이므로 d = 1 / 0.5 따라서 d = 2 pc 이다. 광년으로 바꾸면 대략 2 × 3.26 ≈ 6.52광년 이다. 11. 시차가 작을수록 더 멀다 다른 예를 보자. p = 0.1 arcsecond 이라면 d = 1 / 0.1 따라서 d = 10 pc 이다. 즉, 시차 ↓ 거리 ↑ 라는 관계다. 이것은 반드시 기억해야 한다. 12. 연주시차는 어디까지 사용할 수 있을까? 연주시차는 매우 정확하고 직접적인 거리 측정법이다. 하지만 별이 멀어질수록 시차가 너무 작아진다는 문제가 있다. 별이 멀어짐 ↓ 시차가 작아짐 ↓ 측정이 어려워짐 그래서 아주 먼 별이나 은하까지의 거리를 연주시차만으로 측정하기는 어렵다. 이때 다른 방법이 필요해진다. 13. 밝기를 이용해 거리를 측정할 수 있다 멀리 있는 전등을 생각해보자. 같은 밝기의 전등이라도 가까이 있으면 밝게 보이고 멀리 있으면 어둡게 보인다. 별도 마찬가지다. 따라서 별이 실제로 얼마나 밝은지 알고 있다면, 현재 얼마나 어둡게 보이는지를 이용해 거리를 추정할 수 있다. 이때 중요한 개념이 겉보기 밝기 절대 밝기 이다. 14. 겉보기 밝기 겉보기 밝기는 말 그대로 지구에서 관측했을 때 얼마나 밝아 보이는가 를 의미한다. 별 자체가 매우 밝더라도 아주 멀리 있다면 어둡게 보일 수 있다. 반대로 별 자체는 상대적으로 어두워도 가까이 있다면 밝게 보일 수 있다. 따라서 겉보기 밝기만으로는 별의 실제 밝기를 알 수 없다. 15. 절대 밝기 절대 밝기는 별을 일정한 거리에서 보았다고 가정했을 때의 밝기다. 천문학에서는 보통 별을 10 parsec 떨어진 곳에 두었다고 가정하고 밝기를 비교한다. 즉, 겉보기 밝기 → 지금 지구에서 보이는 밝기 절대 밝기 → 10 pc 거리에서 보았다고 가정한 밝기 이다. 16. 밝기와 거리의 관계 빛은 거리가 멀어질수록 넓은 공간에 퍼진다. 그래서 밝기는 거리의 제곱에 반비례한다. 밝기 ∝ 1 / 거리2 즉, 거리 2배가 되면 밝기는 1 / 4 이 된다. 거리 3배가 되면 밝기는 1 / 9 이 된다. 이를 역제곱 법칙 이라고 한다. 17. 그렇다면 별의 실제 밝기는 어떻게 알까? 여기서 문제가 생긴다. 멀리 있는 별을 봤을 때 어두워 보이는 이유가 별 자체가 어두워서인지 멀리 있어서인지 구분하기 어렵다. 그래서 천문학에서는 실제 밝기를 알 수 있는 특별한 천체 를 찾는다. 이런 천체를 표준촛불(Standard Candle) 이라고 한다. 18. 표준촛불이란? 표준촛불은 실제 밝기를 알고 있는 천체 를 의미한다. 실제 밝기를 알고 있다면 현재 관측되는 밝기와 비교해서 거리를 구할 수 있다. 실제 밝기 + 관측된 밝기 ↓ 거리 추정 대표적인 표준촛불에는 세페이드 변광성 Ia형 초신성 등이 있다. 19. 세페이드 변광성 세페이드 변광성은 밝기가 일정한 주기로 변하는 별이다. 이 별에는 매우 중요한 특징이 있다. 밝기 변화 주기와 실제 밝기 사이에 관계가 있다. 즉, 주기 측정 ↓ 실제 밝기 추정 ↓ 관측 밝기와 비교 ↓ 거리 계산 이 가능하다. 20. 세페이드 변광성이 왜 중요할까? 세페이드 변광성은 개별 별이지만 매우 밝기 때문에 꽤 먼 거리에서도 관측할 수 있다. 그래서 다른 은하 안에 있는 세페이드 변광성을 발견하면 그 은하까지의 거리도 추정할 수 있다. 즉, 세페이드 변광성 거리 ≈ 그 별이 속한 은하의 거리 라고 볼 수 있다. 21. 세페이드 변광성과 우주의 크기 천문학 역사에서 세페이드 변광성은 매우 중요했다. 다른 은하에 있는 세페이드 변광성을 이용하면서 우리은하 밖에도 독립적인 은하들이 존재한다는 사실을 확인하는 데 큰 역할을 했다. 그 결과 우주 = 우리은하 라는 생각에서 벗어나 우주에는 수많은 은하가 존재한다 라는 현대적인 우주관으로 발전하게 되었다. 22. 더 먼 우주는 어떻게 측정할까? 세페이드 변광성도 무한히 멀리까지 사용할 수 있는 것은 아니다. 더 먼 우주에서는 Ia형 초신성 같은 매우 밝은 천체를 사용한다. 23. Ia형 초신성 Ia형 초신성은 특정 조건에서 폭발하는 백색왜성과 관련된 초신성이다. 매우 밝기 때문에 먼 은하에서도 관측할 수 있다. 또한 밝기 특성을 보정하면 거리 측정에 사용할 수 있다. 그래서 Ia형 초신성은 멀리 떨어진 은하의 거리를 측정하는 중요한 표준화 가능한 촛불 로 사용된다. 24. 초신성으로 무엇을 알 수 있었을까? Ia형 초신성을 이용하면 매우 먼 은하까지의 거리를 추정할 수 있다. 그리고 멀리 있는 은하의 거리와 적색편이를 함께 비교하면서 우주의 팽창 역사를 연구할 수 있다. 이 연구는 훗날 우주의 팽창이 가속되고 있다 는 사실을 발견하는 데도 중요하게 사용되었다. 이 내용은 나중에 우주론 시리즈에서 다시 공부할 예정이다. 25. 우주 거리 사다리의 전체 구조 이제 전체 흐름을 보면 이해하기 쉽다. 가까운 천체 연주시차 ↓ 별의 거리 측정 ↓ 세페이드 변광성의 실제 밝기 보정 ↓ 더 먼 은하까지 거리 측정 ↓ Ia형 초신성 보정 ↓ 더 먼 우주 거리 측정 즉, 하나의 방법으로 우주 전체를 측정하는 것이 아니다. 각 단계의 측정값이 다음 단계의 기준이 된다. 26. 왜 '사다리'라고 부르는가? 사다리를 생각해보자. 첫 번째 발판을 밟아야 두 번째 발판으로 올라갈 수 있다. 우주 거리 측정도 마찬가지다. 연주시차 ↓ 세페이드 ↓ 초신성 ↓ 우주론적 거리 앞 단계의 측정이 정확해야 다음 단계의 측정도 정확해진다. 그래서 전체 체계를 우주 거리 사다리 라고 부른다. 27. 거리 측정에서 오차가 중요한 이유 우주 거리 사다리는 여러 단계가 연결되어 있다. 따라서 앞 단계에 오차가 생기면 다음 단계에도 영향을 줄 수 있다. 연주시차 오차 ↓ 세페이드 보정 오차 ↓ 초신성 거리 오차 ↓ 우주의 팽창률 계산에도 영향 그래서 천문학에서는 거리 측정 정확도가 매우 중요하다. 28. Gaia 우주망원경이 중요한 이유 현대 천문학에서는 수많은 별의 위치와 시차를 매우 정밀하게 측정하는 관측 임무가 진행되어 왔다. 대표적인 것이 Gaia 우주망원경 이다. Gaia는 우리은하에 있는 매우 많은 별들의 위치 거리 운동 을 정밀하게 측정한다. 이러한 데이터는 우리은하의 구조와 별의 분포를 연구하는 데 매우 중요하다. 29. 별의 거리를 알면 무엇을 알 수 있을까? 거리만 알아내는 것으로 끝나지 않는다. 별까지의 거리를 알아야 그 별의 진짜 성질도 알 수 있다. 예를 들어 거리 + 겉보기 밝기 ↓ 실제 밝기 를 알 수 있다. 실제 밝기를 알게 되면 별의 에너지 방출량 별의 질량 별의 종류 별의 진화 단계 등을 연구할 수 있다. 즉, 거리 측정은 천체물리학의 기본이 된다. 30. 별의 위치와 거리를 함께 알면 3차원 우주가 된다 앞에서는 천구를 이용해 적경 + 적위 로 별의 방향을 표현했다. 하지만 이것만으로는 2차원적인 정보다. 여기에 거리 까지 알게 되면 별이 우주에서 실제로 어디에 있는지 3차원적으로 표현할 수 있다. 적경 + 적위 + 거리 ↓ 3차원 위치 따라서 천구 좌표와 거리 측정은 서로 연결되어 있다. 31. 이번 편에서 가장 중요한 개념 가장 먼저 기억할 것은 가까운 별의 거리 → 연주시차 이다. 그리고 시차가 크다 → 가깝다 시차가 작다 → 멀다 이다. 두 번째는 d = 1 / p 이다. 여기서 d = pc p = arcsecond 이다. 세 번째는 멀리 있는 천체 → 표준촛불 을 이용한다는 것이다. 대표적으로 세페이드 변광성 Ia형 초신성 이 있다. 핵심 정리 연주시차 지구가 태양 주위를 공전하면서 가까운 별이 배경별에 대해 움직이는 것처럼 보이는 현상이다. 가까운 별 → 시차 큼 먼 별 → 시차 작음 거리 공식 d = 1 / p d = 거리(pc) p = 연주시차(arcsecond) 파섹 연주시차가 1각초인 별까지의 거리 = 1 pc 1 pc ≈ 3.26광년 역제곱 법칙 밝기 ∝ 1 / 거리2 거리 2배 밝기 = 1/4 거리 3배 밝기 = 1/9 표준촛불 실제 밝기를 알고 있는 천체를 이용해 관측 밝기와 비교하여 거리를 측정한다. 대표적인 예 세페이드 변광성 Ia형 초신성 우주 거리 사다리 연주시차 ↓ 세페이드 변광성 ↓ Ia형 초신성 ↓ 더 먼 우주 거리마다 서로 다른 측정법을 연결해 사용하는 방식이다. 마무리 천문학자는 별까지 직접 갈 수 없다. 하지만 각도 빛 밝기 시간에 따른 변화 를 관측함으로써 별과 은하까지의 거리를 계산할 수 있다. 특히 연주시차는 천문학에서 가장 직접적인 거리 측정 방법 중 하나다. 그리고 연주시차에서 얻은 거리 정보를 바탕으로 더 멀리 있는 별, 다른 은하, 더 나아가 우주 규모의 거리까지 측정한다. 이것이 우주 거리 사다리다. 그런데 여기서 또 하나의 궁금증이 생긴다. 지구 주변에는 별만 있는 것이 아니다. 태양 주위에는 행성 위성 소행성 혜성 등 다양한 천체가 존재한다. 그렇다면 우리가 속한 태양계는 정확히 어떤 구조로 이루어져 있을까? 다음 편에서는 우리가 실제로 살고 있는 우주 공간인 태양계 를 공부한다. 다음 글 [천문학 4] 태양계는 어떻게 이루어져 있을까? — 태양부터 카이퍼 벨트까지
velog
[천문학 2] 천구와 천체의 위치는 어떻게 표현할까? 우리가 밤하늘을 보면 수많은 별이 지구를 둘러싸고 있는 것처럼 보인다. 하지만 실제로 별들은 지구에서 서로 완전히 다른 거리에 존재한다. 그런데 천문학에서는 천체의 위치를 표현하기 위해, 일단 모든 별이 거대한 구의 표면에 붙어 있다고 가정한다. 이 가상의 구를 천구(Celestial Sphere) 라고 한다. 이번 글에서는 천구를 기준으로 천체의 위치를 어떻게 표현하는지 알아본다. 1. 천구란 무엇일까? 천구는 지구를 중심으로 모든 천체가 붙어 있다고 가정한 가상의 구 이다. 실제 우주에 천구라는 물체가 존재하는 것은 아니다. 천체의 위치를 편리하게 표현하기 위해 만든 좌표 개념이다. 실제 우주 별 A ─── 지구에서 10광년 별 B ───────── 지구에서 100광년 별 C ───────────────── 지구에서 1,000광년 하지만 하늘을 바라볼 때는 이 거리 차이가 잘 느껴지지 않는다. 그래서 다음처럼 생각한다. 별 별 별 ┌───────────┐ │ │ │ 지구 │ │ │ └───────────┘ 모든 별이 거대한 구의 표면에 있다고 가정 이 가상의 구가 천구다. 2. 왜 천구가 필요할까? 밤하늘에서 별을 찾으려면 "저쪽에 있는 별" 이라고 말하는 것만으로는 부족하다. 정확한 위치를 표현할 수 있는 기준이 필요하다. 지구에서는 위치를 표현할 때 위도 경도 를 사용한다. 천구에서도 비슷한 방식으로 천체의 위치를 표현한다. 대표적으로 적경 적위 를 사용한다. 즉, 지구 위도 + 경도 와 비슷하게 천구 적위 + 적경 을 사용한다. 3. 천구의 중심은 어디일까? 천구를 설명할 때는 보통 관측자가 있는 지구를 중심으로 생각한다. 천구 ┌─────────────┐ │ │ │ ● │ │ 지구 │ │ │ └─────────────┘ 따라서 천구의 중심에는 지구가 있다고 가정한다. 하지만 이것은 지구가 실제 우주의 중심이라는 뜻은 아니다. 단순히 천체의 방향을 편리하게 표현하기 위한 좌표계일 뿐이다. 4. 천구의 북극과 남극 지구에는 북극 남극 이 있다. 지구의 자전축을 하늘까지 무한히 연장했다고 생각해보자. 그러면 그 축이 천구와 만나는 두 지점이 생긴다. 이를 각각 북천극 남천극 이라고 한다. 북천극 ↑ │ │ ┌────●────┐ │ │ │ │ 지구 │ │ │ │ └────●────┘ │ │ ↓ 남천극 5. 북극성이 중요한 이유 북반구에서 하늘을 보면 별들이 밤새 움직이는 것처럼 보인다. 하지만 북극성은 거의 같은 위치에 있는 것처럼 보인다. 왜 그럴까? 북극성이 북천극 근처에 위치하기 때문 이다. 지구는 자전하고 있다. 그래서 우리가 보기에는 별들이 천구를 따라 회전하는 것처럼 보인다. 그런데 회전축 근처에 있는 북극성은 위치 변화가 매우 작다. 따라서 밤하늘에서는 다른 별들이 북극성을 중심으로 회전하는 것처럼 보인다. 6. 별은 실제로 하루에 한 바퀴 도는 걸까? 아니다. 하루 동안 별들이 동쪽에서 떠서 서쪽으로 움직이는 것처럼 보이는 가장 큰 이유는 지구의 자전 이다. 지구는 서쪽에서 동쪽 방향으로 자전한다. 따라서 관측자인 우리는 반대로 하늘이 동쪽 → 서쪽 으로 움직이는 것처럼 느낀다. 이런 겉보기 운동을 일주운동 이라고 한다. 7. 일주운동 일주운동은 지구의 자전 때문에 천체가 하루 동안 천구를 회전하는 것처럼 보이는 현상 이다. 지구 실제 운동 서 → 동 방향 자전 하지만 우리 눈에는 하늘 동 → 서 방향으로 회전 하는 것처럼 보인다. 이 때문에 태양도 동쪽에서 떠서 서쪽으로 지는 것처럼 보인다. 8. 천구의 적도 지구에는 적도가 있다. 지구의 적도면을 천구까지 확장하면 천구에도 원 하나가 생긴다. 이를 천구의 적도 라고 한다. 지구의 적도 ↓ 천구까지 확장 ↓ 천구의 적도 천구의 적도는 천체의 위치를 나타내는 중요한 기준선이다. 특히 이후 배울 적경과 적위 의 기준이 된다. 9. 적위란? 적위는 영어로 Declination 이라고 한다. 기호로는 보통 δ 를 사용한다. 적위는 천체가 천구의 적도에서 얼마나 북쪽 또는 남쪽에 있는지를 나타낸다. 지구의 위도 와 비슷하다. 10. 적위의 범위 천구의 적도를 기준으로 북쪽 → + 남쪽 → - 로 표현한다. 범위는 +90° ~ -90° 이다. 북천극 +90° 천구의 적도 0° 남천극 -90° 따라서 어떤 별의 적위가 +40° 라면 천구의 적도보다 북쪽에 있는 별이라는 뜻이다. 11. 적경이란? 적경은 영어로 Right Ascension 이라고 한다. 줄여서 RA 라고 표현한다. 적경은 지구의 경도 와 비슷한 역할을 한다. 다만 경도처럼 도(°)를 사용하는 대신 보통 시간 분 초 를 사용한다. 12. 왜 적경은 시간으로 표현할까? 천구는 지구의 자전 때문에 하루에 한 바퀴 회전하는 것처럼 보인다. 하루는 약 24시간 이다. 한 바퀴는 360° 이므로 24시간 = 360° 가 된다. 따라서 1시간 = 15° 이다. 그래서 적경에서는 위치를 0h ~ 24h 범위로 표현한다. 13. 적경과 적위를 함께 사용한다 천체의 위치는 보통 적경 + 적위 로 표현한다. 예를 들어 어떤 별의 위치가 적경 6h 적위 +30° 라고 한다면 천구상에서 그 별이 어디 있는지 정확하게 표현할 수 있다. 이는 지구에서 위도 + 경도 로 도시 위치를 나타내는 것과 비슷하다. 14. 적경의 기준점은 어디일까? 경도에는 기준점이 있다. 지구에서는 영국 그리니치 천문대를 지나는 본초자오선 을 0°로 사용한다. 적경에도 0의 기준이 필요하다. 그 기준으로 사용하는 것이 춘분점 이다. 춘분점은 태양이 천구의 적도를 남쪽에서 북쪽으로 통과하는 지점이다. 이 지점을 적경 0h 로 정의한다. 15. 황도란? 여기서 또 하나 중요한 선이 등장한다. 바로 황도(Ecliptic) 다. 황도는 1년 동안 태양이 천구 위를 이동하는 것처럼 보이는 경로 이다. 실제로 태양이 지구 주위를 도는 것은 아니다. 지구가 태양을 공전하기 때문에 배경별을 기준으로 보면 태양의 위치가 조금씩 변하는 것처럼 보인다. 이 경로가 황도다. 16. 지구의 공전과 황도 지구는 태양 주위를 약 1년에 한 바퀴 돈다. 지구 ↓ 태양 주위를 공전 ↓ 관측자가 바라보는 태양의 방향 변화 ↓ 태양이 천구를 이동하는 것처럼 보임 ↓ 황도 따라서 황도 역시 실제로 존재하는 선이 아니다. 천구에 표시한 가상의 경로다. 17. 황도와 천구의 적도는 같지 않다 천구의 적도와 황도는 서로 완전히 겹치지 않는다. 그 이유는 지구의 자전축이 기울어져 있기 때문이다. 지구 자전축은 공전 궤도면에 대해 약 23.4° 기울어져 있다. 따라서 천구에서도 천구의 적도 과 황도 가 약 23.4° 기울어진 상태로 교차한다. 이 기울기가 계절 변화와도 관련되어 있다. 18. 춘분점과 추분점 황도와 천구의 적도가 만나는 지점은 두 곳이다. 그중 태양이 남쪽에서 북쪽으로 천구의 적도를 통과하는 지점을 춘분점 이라고 한다. 반대로 북쪽에서 남쪽으로 통과하는 지점을 추분점 이라고 한다. 황도 / ------●------ 천구의 적도 춘분점 춘분점은 적경을 측정하는 기준점이기도 하다. 19. 지평선 좌표계 천체의 위치를 나타내는 방법이 적경과 적위만 있는 것은 아니다. 우리가 실제로 하늘을 볼 때 가장 직관적인 좌표계는 지평 좌표계 다. 지평 좌표계에서는 방위각 고도 를 사용한다. 20. 고도 고도는 천체가 지평선에서 얼마나 높이 떠 있는가 를 나타낸다. 지평선 = 0° 머리 바로 위 = 90° 머리 바로 위의 지점을 천정 이라고 한다. 따라서 천정 고도 90° ↓ 관측자 ↓ 지평선 고도 0° 이라고 생각할 수 있다. 21. 방위각 방위각은 지평선을 따라 천체가 어느 방향에 있는지를 나타내는 각도 이다. 일반적으로 북쪽을 기준으로 각도를 측정한다. 북쪽 0° 동쪽 90° 남쪽 180° 서쪽 270° 북쪽 360° 따라서 고도 + 방위각 을 알면 현재 하늘에서 천체가 어디에 있는지 나타낼 수 있다. 22. 적도 좌표계와 지평 좌표계의 차이 두 좌표계의 가장 큰 차이가 있다. 적도 좌표계 적경 + 적위 를 사용한다. 별 자체의 위치를 기록하기 좋다. 관측자의 위치와 시간에 따라 크게 달라지지 않는다. 지평 좌표계 방위각 + 고도 를 사용한다. 현재 내가 바라보는 하늘의 위치를 표현하기 좋다. 하지만 관측자의 위치와 시간에 따라 값이 계속 변한다. 23. 왜 지평 좌표는 계속 변할까? 별 하나를 생각해보자. 지구가 자전하면서 별은 하늘에서 계속 움직이는 것처럼 보인다. 따라서 오후 8시 와 오후 11시 에 같은 별을 보면 고도와 방위각이 달라진다. 관측자의 위치가 달라져도 값이 바뀐다. 반면 적경과 적위는 별의 위치를 천구에 고정해서 표현하기 때문에 별 목록이나 천문학 자료에서 사용하기 편리하다. 24. 천정과 천저 관측자의 머리 바로 위에 있는 지점을 천정(Zenith) 이라고 한다. 반대로 관측자의 발 아래 방향으로 천구와 만나는 지점을 천저(Nadir) 라고 한다. 천정 ↑ │ │ 관측자 │ │ ↓ 천저 천문학 관측에서 천정은 매우 자주 등장하는 개념이다. 25. 자오선 관측자의 북쪽 지평선 ↓ 천정 ↓ 남쪽 지평선 을 연결하는 천구상의 큰 원을 자오선 이라고 한다. 천체가 자오선을 통과할 때를 남중 또는 일반적으로 자오선 통과 라고 표현한다. 보통 천체가 자오선 근처에 있을 때 하늘에서 가장 높은 위치에 도달한다. 26. 북극성은 왜 방향을 찾는 데 사용했을까? 북극성은 북천극 가까이에 있다. 따라서 밤이 지나도 위치 변화가 작다. 북반구에서 북극성을 찾으면 대략적인 북쪽 방향을 알 수 있다. 그래서 과거 항해에서도 중요한 기준으로 사용되었다. 또한 북극성의 고도는 관측자의 위도와 대략 비슷하다. 예를 들어 북위 약 37° 지역에서는 북극성이 지평선에서 약 37° 정도 높이에 보인다. 이 관계는 천문 항법에서도 중요한 원리다. 27. 천구는 실제 우주의 구조가 아니다 여기서 반드시 구분해야 한다. 천구는 실제 우주의 구조가 아니다. 별들이 같은 거리에서 지구를 둘러싸고 있는 것이 아니다. 실제로 별들의 거리는 모두 다르다. 별 A 10광년 별 B 100광년 별 C 1,000광년 하지만 관측자의 눈에는 하늘이라는 2차원 표면에 투영되어 보인다. 천구는 그 위치를 표현하기 위한 가상의 좌표 체계다. 28. 이번 편에서 꼭 기억할 것 천문학에서 중요한 것은 단순히 별 이름을 외우는 것이 아니다. 먼저 천체의 위치를 어떻게 표현하는가 를 이해해야 한다. 핵심 구조는 다음과 같다. 천구 │ ├─ 북천극 / 남천극 │ ├─ 천구의 적도 │ ├─ 황도 │ ├─ 춘분점 │ └─ 천체 좌표 │ ├─ 적경 ├─ 적위 │ ├─ 방위각 └─ 고도 핵심 정리 천구 지구를 중심으로 모든 천체가 붙어 있다고 가정한 가상의 구 실제 존재하는 구조는 아니다. 천구의 극 지구 자전축을 연장 ↓ 북천극 남천극 천구의 적도 지구의 적도면을 천구까지 확장한 선 적위 지구의 위도와 비슷하다. 범위 +90° ~ -90° 적경 지구의 경도와 비슷하다. 0h ~ 24h 24h = 360° 1h = 15° 황도 1년 동안 태양이 천구를 이동하는 것처럼 보이는 경로 지평 좌표계 고도 + 방위각 으로 천체의 현재 위치를 나타낸다. 고도 지평선 = 0° 천정 = 90° 가장 중요한 구분 적경 + 적위 → 천구에서 천체의 위치 방위각 + 고도 → 현재 관측자의 하늘에서 보이는 위치 마무리 천문학에서 밤하늘은 단순히 별들이 흩어져 있는 공간이 아니다. 천체의 위치를 체계적으로 기록하기 위해 천구라는 가상의 좌표 공간 을 사용한다. 그리고 이 천구 위에서 천체의 위치를 적경 적위 로 표현한다. 하지만 여기까지 공부하면 새로운 궁금증이 생긴다. 별이 어느 방향에 있는지는 알았다. 그렇다면 그 별이 실제로 우리에게서 얼마나 멀리 떨어져 있는지는 어떻게 알 수 있을까? 별까지 직접 줄자를 가져갈 수도 없다. 그럼에도 천문학자들은 수십 광년, 수천 광년, 심지어 수백만 광년 떨어진 천체의 거리를 측정한다. 다음 편에서는 그 방법을 공부한다. 다음 글 [천문학 3] 별까지의 거리는 어떻게 측정할까? — 연주시차와 우주 거리 사다리
velog
缅甸圣淘沙娱乐赌场 圣淘沙赌场官网 228950.com 经理QQ:5370604 圣淘沙PG官网 sts2516.com 经理飞机TG@stssy08 东南亚线上博彩网投首选平台✅ 公司位于缅甸 佤邦孟波 16年大型实体赌场 主营游戏:百家乐 牛牛 龙虎 大小 单双 炸金花 推筒子 网投、电投、现场三合一 操作简单、方便快捷、信誉度高 现场-线上1:1同步直播 支持24小时验证现场 资金安全干净 大额提款无忧 上下分支持:USDT 银行卡 支付宝 微信 缅币KBZpay
Score: 54.4Confidence: 49%
View offervelog
Looking for luxury slippers for women in India ? Rettrose offers stylish and comfortable slippers crafted for women who love premium designs, quality and everyday comfort.
Score: 54.4Confidence: 49%
View offervelog
Buy Instagram Accounts With Real Followers Safely Seeking to grow your online presence on Instagram but struggling to attract a substantial following? In today's dynamic digital landscape, the competition for attention is fierce, making it challenging to stand out amidst the crowd. Fear not, as this article delves into the realm of purchasing Instagram accounts with real followers safely, offering you a strategic solution to bolster your social media influence. Embark on a journey that will equip you with the knowledge and tools needed to navigate the process of acquiring authentic Instagram accounts seamlessly. Discover invaluable insights on how to identify trustworthy sellers, ensure genuine followers, and effectively leverage your newfound platform for maximum impact. Elevate your social media game and unlock the potential for exponential growth – let's embark on this transformative adventure together! If you want to more information just contact now. 24 Hours Reply/Contact ✅Telegram: @smmusareview ✅WhatsApp: +44 7478079809 ✅E-mail: smmusareview24h@gmail.com ✅Website: https://smmusareview.com/product/buy-instagram-accounts/ Why Buy Instagram Accounts? Buying Instagram accounts with real followers can provide a valuable head start in establishing a strong online presence. It saves time and effort that would be spent on building a following from scratch, allowing individuals and businesses to dive straight into engaging with their audience and showcasing their content. Furthermore, purchasing an account with an existing follower base can offer instant credibility and social proof. This can attract new followers organically, as people are more likely to follow an account that already has a significant following. Ultimately, buying Instagram accounts can jumpstart your journey towards success on the platform. How to Find Trustworthy Sellers When looking for trustworthy sellers to buy Instagram accounts with real followers, it is crucial to do thorough research. Start by checking reviews and testimonials from other buyers to gauge the seller's reputation. Look for sellers who have a proven track record of delivering authentic accounts that meet your specific criteria. Additionally, consider reaching out to the seller directly to ask questions about their process and how they ensure the authenticity of their accounts. A reliable seller will be transparent about their methods and provide you with confidence in your purchase. By taking these steps, you can find a trustworthy seller who will help you acquire an Instagram account with real followers safely and securely. Ensuring Real Followers When purchasing an Instagram account with real followers, it is crucial to verify the authenticity of the audience. Look for engagement metrics such as likes, comments, and shares to ensure that the followers are active and genuinely interested in the content. Avoid accounts with suspiciously high follower counts but low engagement, as they may be filled with fake or inactive profiles. One effective way to ensure real followers is to request a detailed analytics report from the seller. This report should include demographic information, engagement rates, and growth trends over time. By thoroughly examining these metrics, you can validate the quality of the followers and make an informed decision about the account's authenticity. Safely Completing the Purchase When it comes to completing the purchase of an Instagram account, it is crucial to prioritize safety and security. Look for sellers who offer secure payment methods, such as escrow services, to protect both buyer and seller in the transaction. Ensure that all terms and conditions are clearly outlined and agreed upon before finalizing the purchase. Additionally, always verify the authenticity of the account and its followers before making any payments. Request insights or analytics data to confirm the engagement rate and demographics of the followers. By taking these precautions, you can safely complete your purchase and confidently move forward with growing your Instagram presence. What to Look for in an Account When considering buying an Instagram account with real followers, it's crucial to examine the account's engagement rate. Look for accounts with active followers who regularly like, comment, and share content. Authentic engagement is a key indicator of a genuine following and can ensure the success of your future posts. Additionally, pay attention to the account's niche relevance. A targeted audience that aligns with your brand or content is more likely to result in meaningful connections and interactions. Verify that the account's followers are genuinely interested in the content being shared, as this will lead to higher engagement rates and organic growth over time. Tips for Growing Your Followers Creating engaging content is vital to attracting and retaining followers on Instagram. Post high-quality photos and videos that resonate with your target audience. Use relevant hashtags to increase visibility, and interact with your followers by responding to comments and messages. Collaborating with other accounts in your niche can help expose your profile to a wider audience. Host giveaways or contests to encourage user engagement and attract new followers. Consistency is key, so establish a posting schedule that keeps your content fresh and ensures steady growth of your follower base. Avoiding Common Pitfalls When purchasing Instagram accounts, it's crucial to avoid common pitfalls that could lead to wasted money and disappointment. One common pitfall is falling for inflated follower numbers without considering their authenticity. Always verify the engagement rate of the followers to ensure they are real and active. Another pitfall to watch out for is neglecting to check for account history and previous ownership. Buying an account with a questionable past could result in penalties from Instagram or a decline in credibility with your new audience. Stay vigilant and thoroughly research the account's background before making a purchase. Leveraging Your New Account Now that you have acquired an Instagram account with genuine followers, it's time to leverage this newfound asset to its full potential. Start by crafting engaging and visually appealing content that resonates with your audience. Consistency is key - post regularly and utilize relevant hashtags to increase visibility. Engage with your followers by responding to comments, hosting interactive Q&A sessions, and running exciting giveaways. Collaborate with influencers in your niche to further expand your reach and credibility. Remember, building a successful online presence takes time and effort, but with dedication and strategic planning, the possibilities for growth are limitless. Conclusion As we wrap up our discussion on buying Instagram accounts with real followers safely, it is essential to remember that building a strong and engaged audience takes time and effort. By carefully selecting a reputable seller, verifying the authenticity of the followers, and taking proactive steps to grow your account organically, you can set yourself up for success in the competitive world of social media marketing. Remember, authenticity and quality engagement will always prevail over shortcuts and quick fixes.
velog
환경: Next.js 16.0.10 · TypeScript · pnpm 교재: Next.js Learn 대시보드 강좌 Chapter 4: Creating Layouts and Pages Chapter 5: Navigating Between Pages Chapter 6: Setting Up Your Database 이번 주는 대시보드 페이지들을 만들고, 페이지 이동을 부드럽게 바꾸고, 실제 DB를 연결해서 배포까지 해봤다. 1. 폴더 기반 라우팅과 page.tsx Next.js(App Router)는 폴더 구조가 곧 URL 이다. 폴더 하나가 URL의 한 조각(Route Segment)이 된다. app/ ├── page.tsx → / └── dashboard/ ├── page.tsx → /dashboard ├── customers/ │ └── page.tsx → /dashboard/customers └── invoices/ └── page.tsx → /dashboard/invoices // app/dashboard/page.tsx export default function Page() { return <p>Dashboard Page</p>; } 폴더만 만들면 왜 안 될까? 폴더는 주소의 경로 만 만든다. 그 주소에서 무엇을 보여줄지 는 page.tsx 가 정한다. Next.js는 폴더 안에서 page.tsx 라는 약속된 이름의 파일 을 찾고, 그 파일이 export default 로 내보낸 컴포넌트를 화면에 그린다. 그래서 폴더만 있고 page.tsx 가 없으면 404가 뜬다. 이 규칙 덕분에 app/ui , app/lib 처럼 page.tsx 가 없는 폴더는 주소가 생기지 않는다. 컴포넌트나 유틸 코드를 라우트 근처에 함께 둬도 공개되지 않는데, 이걸 Colocation 이라고 한다. 2. 레이아웃 중첩과 Partial Rendering 대시보드의 모든 페이지에는 같은 사이드바가 필요하다. 페이지마다 복사하지 않고 layout.tsx 에 한 번만 넣는다. // app/dashboard/layout.tsx import SideNav from '@/app/ui/dashboard/sidenav'; export default function Layout({ children }: { children: React.ReactNode }) { return ( <div className="flex h-screen flex-col md:flex-row md:overflow-hidden"> <div className="w-full flex-none md:w-64"> <SideNav /> </div> <div className="grow p-6 md:overflow-y-auto md:p-12">{children}</div> </div> ); } page.tsx 와 layout.tsx 가 합쳐지는 과정 {children} 은 "현재 주소의 페이지가 들어올 빈자리" 다. /dashboard/customers 에 접속하면 Next.js가 레이아웃과 페이지를 이렇게 끼워 맞춘다. RootLayout (app/layout.tsx) ← <html>, <body>, 폰트, 전역 CSS └── DashboardLayout (app/dashboard/layout.tsx) ← 사이드바 └── customers/page.tsx ← "Customers Page" (children 자리) 루트 레이아웃 ( app/layout.tsx ): 모든 페이지를 감싸는 필수 레이아웃 대시보드 레이아웃 ( app/dashboard/layout.tsx ): dashboard 폴더 아래 페이지만 감싼다 그래서 홈( / )에는 루트 레이아웃만 적용돼서 사이드바가 없고, /dashboard 로 시작하는 주소에만 사이드바가 나온다. "상속"보다는 "감싼다" 처음엔 "하위 폴더가 레이아웃을 상속받으니까 부분 렌더링이 된다"고 이해했는데, 정확히는 상위 레이아웃이 하위 페이지를 바깥에서 감싸는 구조(중첩) 다. 하위 페이지가 레이아웃 코드를 물려받는 게 아니라, 레이아웃 안의 {children} 자리에 들어가는 것이다. Partial Rendering customers → invoices로 이동하면 구분 이동 시 루트 레이아웃, 대시보드 레이아웃(사이드바) 그대로 유지 {children} 자리의 페이지 customers/page.tsx → invoices/page.tsx 로 교체 바뀌는 부분만 다시 그리기 때문에 빠르고, 사이드바에 있는 상태(입력값 등)도 유지된다. 단, 이건 감싸는 구조 + <Link> 로 이동할 때 일어난다. 브라우저 새로고침이나 <a> 태그로 이동하면 페이지 전체를 처음부터 다시 불러오기 때문에 레이아웃도 다시 그려진다. 3. <Link> 와 페이지 이동 처음 사이드바는 <a> 태그로 되어 있어서 메뉴를 누를 때마다 페이지 전체가 새로고침 됐다. next/link 의 <Link> 로 바꾸면 해결된다. import Link from 'next/link'; <Link href="/dashboard/invoices">Invoices</Link> 고객 → 청구서 클릭 한 번에 일어나는 일 Code-splitting : Next.js는 코드를 페이지별로 나눠둔다. 처음 접속할 때 전체 앱이 아니라 지금 페이지 코드만 받는다. Prefetching : 화면에 <Link> 가 보이면, 그 링크의 페이지 코드를 미리 받아둔다 . (배포 환경에서 동작) Client-side Navigation : 클릭하면 서버에 페이지 전체를 다시 요청하는 대신, 브라우저(JS)가 {children} 자리만 교체한다. 그 결과 Partial Rendering 이 일어나서 사이드바는 그대로, 오른쪽 내용만 바뀐다. 즉 <Link> 는 이 기능들을 쓰기 위한 입구 이고, 각 기능이 합쳐져서 "깜빡임 없는 이동"이 된다. <Link> 는 CSS로 버튼처럼 꾸밀 수 있지만 역할은 페이지 이동 이다. 저장·삭제처럼 어떤 동작을 실행 하는 건 <button> 을 쓴다. 4. 활성 링크 — usePathname() 과 'use client' 지금 있는 페이지의 메뉴를 파란색으로 표시하는 기능이다. 이번 주에 제일 헷갈린 부분이라 역할을 하나씩 나눠서 정리했다. 먼저, 서버 컴포넌트와 클라이언트 컴포넌트 App Router의 컴포넌트는 기본적으로 서버 컴포넌트 다. 구분 실행 위치 할 수 있는 것 서버 컴포넌트 (기본값) 서버 화면을 미리 만들어 보내기. 브라우저 기능은 사용 불가 클라이언트 컴포넌트 브라우저 클릭, 입력, 현재 주소 확인 등 사용자와 상호작용 각자 맡은 역할 코드 역할 'use client' "이 파일은 브라우저에서도 실행되는 클라이언트 컴포넌트"라는 표시 usePathname() 현재 URL 경로를 읽어오는 훅 pathname 읽어온 경로를 담는 변수 pathname === link.href 현재 경로와 메뉴 주소가 같은지 비교 clsx(...) 비교 결과가 참일 때만 파란색 클래스를 붙이기 usePathname() 은 브라우저 주소창을 봐야 하는 기능이라 클라이언트 컴포넌트에서만 쓸 수 있다. 그래서 usePathname() 을 쓰려면 파일 맨 위에 'use client' 가 꼭 필요하다. 둘은 대신할 수 있는 관계가 아니라 "사용 조건"과 "실제 기능" 의 관계다. 실제로 색이 바뀌는 과정 'use client'; // 1 클라이언트 컴포넌트로 지정 import Link from 'next/link'; import { usePathname } from 'next/navigation'; import clsx from 'clsx'; // links 배열(메뉴 이름·주소·아이콘)은 생략 export default function NavLinks() { const pathname = usePathname(); // 2 현재 경로 읽기 return ( <> {links.map((link) => ( <Link key={link.name} href={link.href} className={clsx( 'flex h-[48px] ... bg-gray-50 ...', // 기본 스타일 (항상) { 'bg-sky-100 text-blue-600': pathname === link.href, // 3 비교 → 4 같을 때만 파란색 }, )} > {/* 아이콘, 메뉴 이름 */} </Link> ))} </> ); } /dashboard/invoices 에 있을 때: pathname = "/dashboard/invoices" Invoices 메뉴: link.href 가 같음 → bg-sky-100 text-blue-600 적용 Home, Customers 메뉴: 다름 → 기본 스타일만 usePathname() 이 색을 바꾸는 게 아니다. 경로를 알려주기만 하고, 색을 정하는 건 비교 + clsx 다. 5. GitHub · Vercel · PostgreSQL 연결 흐름 [내 컴퓨터 코드] ──push──▶ [GitHub] ──연결──▶ [Vercel 배포] │ 연결 ▼ [Neon PostgreSQL DB] GitHub : Vercel은 내 컴퓨터가 아니라 GitHub에서 코드를 가져간다. Vercel : GitHub 저장소를 Import해서 배포. 이후 main 에 push하면 자동으로 재배포 된다. Neon : Vercel의 Storage → Create Database에서 생성하고 프로젝트에 연결. Region은 Vercel 서버 기본 위치와 같은 Washington, D.C.(iad1) 로 골랐다. 사용자 위치가 아니라 서버와 DB가 가까운 게 기준이다. Seed 데이터 빈 DB에 처음 넣는 연습용 데이터다. app/lib/placeholder-data.ts 의 데이터를 localhost:3000/seed 에 접속해서 넣었다. route.ts 와 page.tsx /seed , /query 는 page.tsx 가 아니라 route.ts (Route Handler)다. 파일 하는 일 결과 page.tsx 화면 구성 HTML 화면 route.ts 요청 받기 → 처리/DB 조회 → 응답 JSON 같은 데이터 // app/query/route.ts export async function GET() { try { return Response.json(await listInvoices()); // DB 조회 결과를 JSON으로 응답 } catch (error) { return Response.json({ error }, { status: 500 }); } } 브라우저로 /query 에 접속하면 화면 대신 이런 데이터가 그대로 보인다. [{"amount":666,"name":"Evil Rabbit"}] 모든 페이지가 route.ts 를 거쳐야 하는 건 아니다. 이번엔 DB 연결 확인용으로 썼고, 대시보드 화면에서 데이터를 가져오는 방법은 다음 챕터(Ch.7 Fetching Data)에서 다룬다. 6. 환경변수와 Secret 관리 환경변수, .env , Vercel 설정의 관계 환경변수 : 코드 밖에 두는 설정값. POSTGRES_URL 은 이름 이고, 실제 값 에는 DB 주소·아이디·비밀번호가 들어간다. 코드에서는 이름으로만 꺼내 쓴다. const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' }); 같은 POSTGRES_URL 이 두 장소에 보관 된다. 실행 환경 값을 읽는 곳 개발 ( pnpm dev ) 내 컴퓨터의 .env 파일 배포 (Vercel) Vercel 프로젝트의 Environment Variables 배포용이 별도 기능이 아니라 같은 설정을 다른 장소에 둔 것 이다. Vercel 서버는 내 컴퓨터의 .env 를 볼 수 없기 때문에 따로 필요하다. 두 곳에 같은 접속 정보가 있으니 개발할 때도, 배포했을 때도 같은 DB 에 연결된다. 실습에서 맞춘 설정 Neon 연결 시 Custom Prefix를 POSTGRES 로 입력 → Vercel에 POSTGRES_URL 이 자동 등록됨. 교재 코드가 이 이름을 찾기 때문. "Create database branch for deployment"는 체크 해제 → 체크하면 배포용 DB 복사본이 따로 생겨서, 로컬에서 Seed한 데이터가 배포 사이트에서 안 보이게 된다. .env 라는 이름이 비밀을 지켜주는 건 아니다 .env 도 그냥 파일이라 git add 하면 GitHub에 올라간다. 막아주는 건 .gitignore 다. # .gitignore .env*.local .env push 전에 git status 로 .env 가 목록에 없는지 확인했다. 그리고 Vercel에서 값을 복사할 때는 Show secret 을 먼저 눌러야 **** 가 아닌 실제 값이 복사된다. 7. 실습하며 겪은 문제 1 /query 결과가 2개 나옴 원인: /seed 가 두 번 실행됨. users · customers · revenue 는 고정된 값(ID, 월)이 있어서 이미 있으면 건너뛰지만, invoices 는 실행할 때마다 ID가 새로 만들어져서 중복으로 들어감 해결: Vercel Storage → Query에서 Read-only를 끄고 DROP TABLE invoices; 실행 → /seed 한 번만 다시 접속 2 배포 사이트에서 /query 가 옛날 안내 문구를 보여줌 원인: 이전 배포의 배포별 고정 주소 로 접속함. 이 주소는 그 시점 버전에 고정되어 있음 해결: 프로젝트 Domains의 Production 주소 (항상 최신 배포)로 접속 3 배포 전에 app/seed 삭제 그대로 배포하면 누구나 배포주소/seed 에 접속해서 Seed를 실행할 수 있다(1 같은 중복 발생). Git 기록에 남아 있으니 필요하면 되살릴 수 있다. 마무리 URL과 폴더 구조 대응: /dashboard , /dashboard/customers , /dashboard/invoices 배포 환경에서 Seed 데이터 조회: https://next-js-dashboard-iota-lime.vercel.app/query 이번 주에 가장 크게 정리된 건 "누가 무엇을 담당하는지" 였다. usePathname() 은 경로를 읽기만 하고 색은 clsx 가 정하는 것, .env 가 아니라 .gitignore 가 비밀을 지키는 것처럼 역할을 나눠서 보니 헷갈리던 게 풀렸다. 다음 주는 Ch.7부터 DB 데이터를 실제 대시보드 화면에 가져온다.
velog
들어가며 @Transactional 을 붙이면 트랜잭션은 알아서 처리된다. 대부분은 그걸로 충분하다. 그런데 아래 질문 중 하나라도 막힌다면 그 내부를 한 번 들여다볼 때다. 같은 클래스 안에서 @Transactional 메서드를 호출했더니 트랜잭션이 걸리지 않는다. 왜일까? save() 를 호출하지 않았는데 UPDATE 쿼리가 나간다. 누가 보낸 걸까? 예외를 try-catch로 잡았는데도 UnexpectedRollbackException 이 터진다. REQUIRES_NEW 를 썼더니 트래픽이 몰릴 때 커넥션 풀이 바닥난다. readOnly = true 는 정확히 무엇을 바꿀까? 이 글은 @Transactional 메서드 하나가 호출되는 순간부터 커넥션이 풀로 돌아가는 순간까지를 순서대로 따라간다. 먼저 전체 지도와 14단계 흐름을 훑고, 이어서 각 단계를 소스 코드 수준에서 하나씩 뜯어본다. 단계마다 그 구조 때문에 생기는 함정도 함께 정리했다. 글 전체에서 아래 예제를 계속 사용한다. 회원과 상품을 조회하고, 재고를 줄이고, 주문을 저장하는 흔한 서비스 메서드다. @Service @RequiredArgsConstructor public class OrderService { private final MemberRepository memberRepository; private final ItemRepository itemRepository; private final OrderRepository orderRepository; @Transactional public Long placeOrder(Long memberId, Long itemId, int count) { Member member = memberRepository.findById(memberId).orElseThrow(); Item item = itemRepository.findById(itemId).orElseThrow(); item.removeStock(count); // 재고 감소. save() 호출 없음 Order order = Order.create(member, item, count); orderRepository.save(order); // 주문 저장 return order.getId(); } } 엔티티의 ID 생성 전략은 SEQUENCE라고 가정한다. IDENTITY일 때 달라지는 점은 3막에서 다룬다. 등장인물과 지도 트랜잭션 하나에 관여하는 컴포넌트는 생각보다 많다. 각자 맡은 일은 단순하지만, 서로를 어떻게 찾아내는지 알아야 전체가 보인다. 왼쪽의 제어 흐름(프록시 → 트랜잭션 매니저)과 오른쪽의 데이터 접근(원본 객체 → 리포지토리)은 서로 다른 길로 내려오지만, 스레드별 저장소에서 같은 EntityManager를 만난다. 컴포넌트 하는 일 트랜잭션 프록시 Spring이 원본 OrderService 대신 컨테이너에 등록해 둔 CGLIB 프록시. 호출을 가로채 TransactionInterceptor에 넘긴다. TransactionInterceptor @Transactional 속성을 읽고 시작, 커밋, 롤백을 지휘한다. DB를 직접 만지지는 않는다. JpaTransactionManager EntityManager를 만들고 트랜잭션을 실제로 시작하고 끝낸다. PlatformTransactionManager 의 JPA 구현체다. TransactionSynchronizationManager 현재 스레드에 묶인 자원(EntityManager, 커넥션)과 콜백 목록을 보관하는 ThreadLocal 저장소. EntityManager 영속성 컨텍스트. 구현체는 Hibernate의 SessionImpl 이며 1차 캐시, 스냅샷, 쓰기 지연 SQL 저장소를 가진다. JDBC Connection 진짜 DB 트랜잭션의 주인. autoCommit=false 로 시작해 commit() 또는 rollback() 으로 끝난다. 그림에서 기억할 것은 두 가지다. 첫째, JPA 트랜잭션도 결국은 JDBC 커넥션 하나의 트랜잭션이다. setAutoCommit(false) 로 시작해 commit() 이나 rollback() 으로 끝난다. 영속성 컨텍스트는 그 사이에서 변경 사항을 모아 두었다가 커밋 직전에 SQL로 바꿔 한꺼번에 보낸다. 둘째, 트랜잭션 매니저와 리포지토리는 서로를 직접 참조하지 않는다. 매니저가 스레드 저장소에 넣어 둔 EntityManager를, 같은 스레드에서 실행되는 리포지토리가 꺼내 쓸 뿐이다. 트랜잭션에 관한 거의 모든 함정이 이 두 사실에서 나온다. 한 번의 호출, 14단계 컨트롤러가 orderService.placeOrder() 를 호출했을 때 일어나는 일을 시간 순서대로 나열했다. 세부 내용은 뒤에서 막별로 다시 다루니, 여기서는 흐름만 잡으면 된다. 시작 (1–6) 프록시가 호출을 가로챈다 · CGLIB 프록시 컨트롤러가 주입받은 orderService는 OrderService를 상속한 프록시다. 호출은 원본보다 프록시에 먼저 도착하고, 프록시는 TransactionInterceptor에 일을 넘긴다. 트랜잭션 속성을 읽는다 · TransactionInterceptor 메서드의 @Transactional 을 해석해 전파 속성(REQUIRED), 격리 수준, readOnly, 롤백 규칙을 얻는다. 해석 결과는 메서드별로 캐시된다. 진행 중인 트랜잭션이 있는지 본다 · JpaTransactionManager 현재 스레드의 저장소에 EntityManagerHolder가 있는지 확인한다. 비어 있으므로 새 트랜잭션을 시작하기로 한다. EntityManager를 만든다 · JpaTransactionManager EntityManagerFactory에서 새 EntityManager(Hibernate SessionImpl )를 생성한다. 이 트랜잭션의 영속성 컨텍스트가 여기서 태어난다. 커넥션을 얻고 자동 커밋을 끈다 · Hibernate em.getTransaction().begin() 이 호출되면 Hibernate가 커넥션 풀에서 커넥션을 빌려 setAutoCommit(false) 를 호출한다. DB 입장에서는 이 순간 트랜잭션이 시작된다. 자원을 스레드에 묶는다 · TransactionSynchronizationManager EntityManager와 커넥션을 현재 스레드의 저장소에 등록하고, 트랜잭션 이름·readOnly 여부와 콜백 목록을 초기화한다. 실행 (7–9) 원본 메서드가 실행된다 · OrderService 인터셉터가 invocation.proceed() 로 원본 OrderService.placeOrder() 를 호출한다. 리포지토리가 같은 EntityManager를 꺼내 쓴다 · 공유 EM 프록시 findById() 는 내부에서 em.find() 를 부른다. 이 em은 프록시라서 스레드 저장소에서 4단계의 EntityManager를 찾아 위임한다. 리포지토리 메서드에도 @Transactional 이 있지만 기존 트랜잭션에 참여할 뿐이다. 1차 캐시에 없으니 SELECT가 나간다. 변경은 메모리에만 쌓인다 · 영속성 컨텍스트 item.removeStock() 은 자바 객체의 필드만 바꾼다. orderRepository.save() 는 INSERT를 쓰기 지연 저장소(ActionQueue)에 넣는다. 아직 DB로 간 쓰기 SQL은 하나도 없다. 종료 (10–14) 정상 반환, 커밋 요청 · TransactionInterceptor 예외 없이 반환되면 인터셉터가 transactionManager.commit() 을 호출한다. 플러시: SQL이 DB로 간다 · Hibernate 커밋 직전에 Hibernate가 엔티티의 현재 값과 스냅샷을 비교해 UPDATE를 만들고, ActionQueue에 쌓인 SQL을 정해진 순서로 실행한다. JDBC 커밋 · Connection connection.commit() . 이제 변경이 DB에 확정된다. 커밋 후 콜백 · TransactionSynchronization afterCommit, afterCompletion 콜백이 실행된다. @TransactionalEventListener 가 동작하는 지점이 여기다. 정리 · JpaTransactionManager 스레드 저장소에서 자원을 떼어내고 EntityManager를 닫는다. 영속성 컨텍스트가 사라지고, 커넥션은 설정을 되돌린 뒤 풀로 돌아간다. 로그로 직접 확인하기 아래 설정을 켜면 이 흐름을 로그로 볼 수 있다. 직접 한 번 돌려 보기를 권한다. logging: level: org.springframework.orm.jpa.JpaTransactionManager: DEBUG org.hibernate.SQL: DEBUG 출력 예시다. 왼쪽 숫자는 위 단계 번호이고, 해시값은 실행마다 다르다. 03 | DEBUG JpaTransactionManager : Creating new transaction with name [com.example.shop.OrderService.placeOrder]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT 04 | DEBUG JpaTransactionManager : Opened new EntityManager [SessionImpl(1846302731<open>)] for JPA transaction 06 | DEBUG JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@5b6e8f77] 08 | DEBUG JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1846302731<open>)] for JPA transaction 08 | DEBUG JpaTransactionManager : Participating in existing transaction 08 | DEBUG org.hibernate.SQL : select m1_0.member_id,m1_0.grade,m1_0.name from member m1_0 where m1_0.member_id=? 08 | DEBUG JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1846302731<open>)] for JPA transaction 08 | DEBUG JpaTransactionManager : Participating in existing transaction 08 | DEBUG org.hibernate.SQL : select i1_0.item_id,i1_0.name,i1_0.price,i1_0.stock from item i1_0 where i1_0.item_id=? 09 | DEBUG JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1846302731<open>)] for JPA transaction 09 | DEBUG JpaTransactionManager : Participating in existing transaction | (persist() 때의 시퀀스 조회 SQL은 생략) 10 | DEBUG JpaTransactionManager : Initiating transaction commit 11 | DEBUG JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1846302731<open>)] 11 | DEBUG org.hibernate.SQL : insert into orders (count,item_id,member_id,status,order_id) values (?,?,?,?,?) 11 | DEBUG org.hibernate.SQL : update item set name=?,price=?,stock=? where item_id=? 14 | DEBUG JpaTransactionManager : Closing JPA EntityManager [SessionImpl(1846302731<open>)] after transaction 로그에서 두 가지가 눈에 띈다. 리포지토리를 호출할 때마다 Participating in existing transaction 이 찍힌다. 그리고 코드에서는 재고 감소가 주문 저장보다 먼저인데, 실제 SQL은 커밋 시점에 INSERT가 UPDATE보다 먼저 나간다. 두 가지 모두 아래에서 이유를 설명한다. 1막. 프록시: 호출을 가로채는 문지기 단계 1–2 프록시는 언제, 어떻게 만들어지나 애플리케이션이 뜰 때 자동 프록시 생성기(빈 후처리기)가 등록되는 빈을 하나씩 검사한다. 트랜잭션용 어드바이저( BeanFactoryTransactionAttributeSourceAdvisor )의 포인트컷이 @Transactional 이 붙은 클래스나 메서드를 찾아내면, 원본 빈 대신 프록시를 컨테이너에 등록한다. Spring Boot는 기본으로 클래스를 상속하는 CGLIB 프록시를 쓴다( spring.aop.proxy-target-class=true ). 그래서 컨트롤러가 주입받는 orderService의 실제 타입은 원본이 아니라 하위 클래스다. 직접 찍어 보면 바로 확인할 수 있다. System.out.println(orderService.getClass()); // class com.example.shop.OrderService$$SpringCGLIB$$0 프록시의 메서드는 TransactionInterceptor를 거쳐 TransactionAspectSupport.invokeWithinTransaction() 에 도착한다. @Transactional 의 동작 전체가 이 메서드 하나에 들어 있다. // TransactionAspectSupport.invokeWithinTransaction() 요약 protected Object invokeWithinTransaction(Method method, Class<?> targetClass, InvocationCallback invocation) throws Throwable { TransactionAttribute txAttr = getTransactionAttributeSource() .getTransactionAttribute(method, targetClass); // 1 @Transactional 해석 PlatformTransactionManager tm = determineTransactionManager(txAttr); TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpoint); // 2 시작 또는 참여 Object retVal; try { retVal = invocation.proceedWithInvocation(); // 3 원본 메서드 실행 } catch (Throwable ex) { completeTransactionAfterThrowing(txInfo, ex); // 4 롤백 규칙 판단 throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); // 5 커밋 return retVal; } 구조는 try-catch 하나다. 시작하고, 실행하고, 예외가 나면 롤백 규칙을 따지고, 아니면 커밋한다. 이 글의 나머지는 1부터 5까지를 하나씩 깊게 들어가는 이야기다. 1의 해석은 AnnotationTransactionAttributeSource 가 맡는다. 메서드에 붙은 애너테이션이 클래스에 붙은 것보다 우선하고, 한 번 해석한 결과는 메서드별로 캐시한다. Spring의 @Transactional 과 jakarta.transaction.Transactional 을 둘 다 인식한다. 함정: 자기 호출은 프록시를 거치지 않는다 @Service public class OrderService { public void placeOrders(List<OrderRequest> requests) { for (OrderRequest req : requests) { placeOrder(req); // this.placeOrder(): 프록시를 거치지 않는다 } } @Transactional public void placeOrder(OrderRequest req) { ... } } 외부에서 placeOrders() 를 호출하면 프록시는 이 메서드에 @Transactional 이 없으니 그대로 원본 객체에 위임한다. 원본 안에서의 placeOrder() 호출은 this , 즉 원본 객체에 대한 호출이다. 프록시는 이 호출을 볼 방법이 없다. 결과적으로 placeOrder() 는 트랜잭션 없이 실행된다. 안에서 부르는 리포지토리 메서드는 각자 자기 트랜잭션을 열고 바로 커밋하므로, 중간에 예외가 나도 앞에서 저장한 데이터는 롤백되지 않는다. 가장 깔끔한 해결은 트랜잭션 경계를 다른 빈으로 분리하는 것이다. @Service @RequiredArgsConstructor public class OrderBatchService { private final OrderService orderService; // 프록시가 주입된다 public void placeOrders(List<OrderRequest> requests) { requests.forEach(orderService::placeOrder); // 매번 프록시를 거친다 } } 코드 블록 단위로 트랜잭션을 걸어야 한다면 TransactionTemplate 을 쓰는 방법도 있다. 프록시 대신 코드로 같은 일을 한다. 프록시가 적용되지 않는 메서드 private 메서드 : 하위 클래스가 오버라이드할 수 없으니 프록시가 끼어들 자리가 없다. 애너테이션은 조용히 무시된다. final 메서드와 final 클래스 : 같은 이유로 적용되지 않는다. Kotlin은 클래스가 기본 final이라 kotlin-spring 플러그인이 대신 열어 준다. protected, package-private 메서드 : Spring 6.0부터 CGLIB 프록시에서 적용된다. 그 이전 버전은 public 메서드만 지원했다. @PostConstruct 안에서의 호출 : 결국 자기 호출이고, 초기화 콜백이 실행되는 시점에는 프록시가 아직 만들어지지도 않았다. 시작 시점에 트랜잭션이 필요하면 ApplicationReadyEvent 리스너에서 다른 빈을 호출한다. 2막. 트랜잭션 매니저: 시작은 어떻게 이뤄지나 단계 3–6 PlatformTransactionManager: 기술을 감추는 추상화 인터셉터는 JPA를 모른다. 알고 있는 건 아래 인터페이스 하나뿐이다. public interface PlatformTransactionManager extends TransactionManager { TransactionStatus getTransaction(TransactionDefinition definition); void commit(TransactionStatus status); void rollback(TransactionStatus status); } 구현체는 데이터 접근 기술마다 있다. JDBC나 MyBatis는 DataSourceTransactionManager , JPA는 JpaTransactionManager , 분산 트랜잭션은 JtaTransactionManager . Spring Boot는 JPA 의존성이 있으면 JpaTransactionManager 를 자동으로 등록한다. 구현체들은 모두 AbstractPlatformTransactionManager 를 상속한다. 전파 속성 처리, 동기화 콜백 관리 같은 공통 흐름은 부모가 템플릿 메서드로 잡아 두고, 자식은 doGetTransaction , doBegin , doCommit , doRollback , doSuspend 같은 기술별 조각만 채운다. // AbstractPlatformTransactionManager.getTransaction() 요약 public final TransactionStatus getTransaction(T
velog
enumerate() - 번호와 값을 같이 꺼내기 for문으로 리스트를 돌다 보면 이런 생각이 들 때가 있다. "지금 꺼낸 값이 몇 번째 값이지?" 예를 들어 5일 동안의 수익률이 있을 때, 그냥 값만 보는 게 아니라 "3일차 수익률은 1%" 처럼 몇 일차인지도 같이 알고 싶을 때가 있다. 이럴 때 쓰는 함수가 바로 enumerate() 다. 실습 환경: VS Code + Jupyter Notebook (Python 3.14) 0. 한 줄 요약 함수 하는 일 꺼내 주는 것 enumerate(리스트) 리스트를 돌면서 번호표 를 같이 붙여 줌 (번호, 값) 한 쌍 비유하자면 은행 번호표 기계 다. 줄 선 사람(값)에게 순서대로 번호표를 하나씩 쥐여 준다. 이 표만 기억해도 절반은 끝났다. 이제 하나씩 보자. 1. enumerate 없이 for문을 돌리면 daily_returns = [0.03, -0.02, 0.01, -0.01, 0.04] for r in daily_returns: print(r) 0.03 -0.02 0.01 -0.01 0.04 값은 잘 나오는데, 몇 번째 값인지는 알 수 없다. 번호를 직접 세려면? day = 1 for r in daily_returns: print(day, r) day += 1 번호 변수 day 를 따로 만들고 반복할 때마다 day += 1 로 직접 늘려 줘야 한다. 잘 되긴 하지만 귀찮고, day += 1 을 깜빡하면 번호가 안 바뀌는 실수도 생긴다. 2. enumerate 사용하기 for i, r in enumerate(daily_returns): print(i, r) 0 0.03 1 -0.02 2 0.01 3 -0.01 4 0.04 enumerate 는 매번 (번호, 값) 한 쌍을 꺼내 준다. 그 한 쌍이 i 와 r 에 나뉘어 들어가는 것이다. for i, r in enumerate(daily_returns): # ↑ ↑ # 번호 값 💡 번호를 직접 셀 필요가 없다. enumerate 가 대신 세 준다. 3. start=1 : 번호를 1부터 시작하기 파이썬은 번호를 0부터 센다. 그래서 위 결과도 0부터 시작했다. 그런데 "0일차"는 어색하다. 이럴 때 start=1 을 붙인다. for day, r in enumerate(daily_returns, start=1): print(day, r) 1 0.03 2 -0.02 3 0.01 4 -0.01 5 0.04 쓰는 법 번호 시작 enumerate(리스트) 0부터 enumerate(리스트, start=1) 1부터 enumerate(리스트, start=10) 10부터 4. 순서가 중요하다 (이름은 상관없다) 처음에 헷갈리기 쉬운 부분이다. for day, r in enumerate(daily_returns, start=1): 첫 번째 자리 → 항상 번호 두 번째 자리 → 항상 값 파이썬은 변수 이름 을 보고 판단하지 않는다. 그냥 앞 자리에 번호, 뒷 자리에 값 을 넣을 뿐이다. for a, b in enumerate(daily_returns, start=1): print(a, b) # 결과는 위와 똑같다 day , r 이라는 이름은 사람이 읽기 쉽게 붙인 것일 뿐이다. 순서를 바꿔 쓰면? for day, r in enumerate(daily_returns, start=1): print(f"{r}일차 수익률: {day:.2%}") 0.03일차 수익률: 100.00% "0.03일차"에 "100%"... 완전히 엉망이 된다. 번호 자리와 값 자리를 헷갈리지 말자. 5. 실전 예제 : 수익률이 양수인 날 찾기 enumerate + if 를 같이 쓰면 조건에 맞는 값이 몇 번째인지 쉽게 찾을 수 있다. daily_returns = [0.03, -0.02, 0.01, -0.01, 0.04] for day, r in enumerate(daily_returns, start=1): if r > 0: print(f"{day}일차 수익률: {r:.2%}") 1일차 수익률: 3.00% 3일차 수익률: 1.00% 5일차 수익률: 4.00% 💡 :.2% 는 100을 곱하고 소수점 둘째 자리까지 + % 기호 를 붙여 주는 서식이다. 0.03 → 3.00% 리스트 컴프리헨션으로 한 줄에 positive_days = [day for day, r in enumerate(daily_returns, start=1) if r > 0] print(positive_days) [1, 3, 5] 6. 정리 enumerate(리스트) → 반복할 때 (번호, 값) 을 같이 꺼내 준다. 번호는 기본 0부터 , start=1 을 주면 1부터 시작한다. for 번호, 값 in enumerate(...) → 순서 가 중요하고, 이름은 자유다. 번호 변수를 직접 만들고 += 1 할 필요가 없어서 코드가 짧고 실수가 줄어든다. # 이것만 기억하자 for 번호, 값 in enumerate(리스트, start=1): print(번호, 값)
Score: 54.4Confidence: 49%
View offerhacker-news-frontpage
102 points · 43 comments · by nerdypepper
Score: 52.06Confidence: 46%
View offerScore: 54.4Confidence: 49%
Score: 54.4Confidence: 49%
Score: 54.4Confidence: 49%
Score: 54.4Confidence: 49%
Score: 54.4Confidence: 49%