별 헤는 밤

별 헤는 밤님의 아티클

별 헤는 밤

별 헤는 밤

❌ 대외비 유출❌ 다음 업데이트 기능 스포를 가장한 자랑하기

카메라로 현위치의 실시간 <별 찾기 기능>

12월 말(을 목표로..) 저희 <별 헤는 밤>이 새로운 버전을 업데이트할 계획입니다!

근데.. 새롭게 추가되는 기능에 얼른 자랑하고 싶은 기능이 있어서 이렇게 달려왔습니다...

바로 바로 <별 찾기 기능>!!!!

카메라로 직접 현재 내가 보고 있는 밤하늘에 위치한 별자리를!! 실시간으로!! 찾아주는 어마어마한 기능이에요!!

사실 이거 유출하면 안되는 건데... 디스콰이엇에만 살포시.. 스포하겠습니다..

동영상 업로드가 안돼서 일일이 캡쳐까지해서라도 자랑하고 싶은 이 마음..

(그래서 화질이 조금 구릴수도 있습니다..)

별찾기 기능

Group 3 (2).png

기존 <별 헤는 밤>의 밤하늘 화면이나 별찾기 탭에서 찾고 싶은 별자리를 선택하시면 별찾기 기능이 실행됩니다.

위의 화면 플로우로 별찾기 과정이 진행됩니다.

(이 날것의 배경화면을 통해서 얼마나 따끈따끈하게 완료된 기능인지 보이지 않나요🤣)

현재 내가 위치한 밤하늘을 배경으로, 내가 선택한 별자리가 실시간으로 어느 위치에 있는지 방향과 각도를 통해서 쉽게 찾을 수 있도록 도와주는 기능이에요!!!

사용자의 현재 위치가 변하면 별자리 가이드도 계속해서 변하기 때문에 정말 손쉽게 원하는 별자리의 위치를 파악할 수 있답니다. (짱멋져...)

12월말에 정식으로! 업데이트 될 예정이니 궁금하신 분들은 미리 이 링크에서(클릭클릭!) 다운받아두시면

업데이트 알림을 통해서 가장 빠르게 별찾기 기능을 사용하실 수 있답니다!!!

또한 저희 티스토리 블로그(클릭)나, 디스콰이엇을 팔로우하시면 다음 업데이트 소식도 확인하실 수 있습니다!

많은 관심 부탁드려요~!!! 🌟🌃

📌티스토리 링크

📌구글플레이스토어 링크

📌디스콰이엇 링크

6
0
별 헤는 밤

별 헤는 밤

[별린이 성장기] 별밤 에디터이지만 별에 대해서 아는게 1도 없는 것에 대해서

별자리 지식 테스트 : 당신의 별자리 지식은?!

안녕하세요!

저희 <별 헤는 밤>은 사람들이 더 쉽고 간편하게 별자리를 관측하고 밤하늘을 즐길 수 있게 도와주는 어플입니다.

(아직도 이용을 안 해보셨다고요??? 그렇다면 여기로!!!🔽)

<별 헤는 밤>에는 별자리 정보, 관측지 정보, 날씨, 광공해, 월령, 관측적합도 등 별자리에 대한 모든 것이 담겨 있습니다.

그렇다면 과연 <별 헤는 밤>의 에디터는 별자리에 대한 모든 것을 다 알고 있을까요??!? 😎 

그래서!!!

이를 확인하고자 자가 테스트를 진행봤습니다! (ㅇ.. 안돼....)

이름하여 "별밤 에디터 자질 테스트"!!! 여러분들도 함께 풀어보세요!

 


별밤 에디터 자질 테스트

<별자리 지식 테스트>

 

1. 가을철 대표 별자리는?

1) 페가수스자리  2) 목동자리  3) 거문고자리  4) 오리온자리

 

2. 백조자리의 데네브, 거문고자리의 베가, 독수리자리의 알타이르가 가장 잘 보이는 계절은?

1) 봄  2) 여름  3) 가을  4) 겨울

 

3. 돌고래자리가 가장 잘 보이는 시기는?

1) 1월  2) 4월  3) 5월  4) 8월

 

4. 대기오염물질과 인공불빛 때문에 시야에서 별이 사라지는 현상은?

1) 광공해  2) 월령  3) 소음공해  4) 전파공해

 

정답 : 1, 2, 4, 1


짠! 다들 몇 개나 맞추셨나요??

저는 말이죠..... 비밀입니다.. (암담, 참담, 절망....)

아니.. 해명을 하자면... <별 헤는 밤> 어플만 키면 다 알아서 알려주니까... ㅠ

그래도 별밤 에디터가 별린이라는건 조금 이상하긴 하니까..

이제부터 별린이 탈출일기! 성장일기!를 한번 기록해볼려고 합니다.....!!!!

우선 오늘의 오답노트부터 시작해볼까요???

 


오답노트

 

1. 가을철 대표 별자리 : 페가수스자리

가을철 밤하늘을 바라보면 4개의 밝은 별들이 사각형의 형태를 취하고 있는걸 보실 수 있습니다.

그 4개의 별들은 각 각 '알페라츠', '알게니브', '쉐아트', '마르카브'로 불리는 별들로 이 4개의 별들이 이루는 사각형을

바로 '가을의 대사각형'이라고 합니다! 그리고 이 사각형이 페가수스의 몸체를 담당하고 있습니다.

'마르카브'에서 뻗어나온 4개의 별들이 페가수스의 머리를, '쉐아트'에서 뻗어나가는 별들로 앞다리를, 

'알페라츠'에서 뻗어나온 별로 뒷다리를 그리게 된다면 페가수스자리가 완성됩니다!!!!! (거꾸로 뒤집어진 페가수스 모양)

 

페가수스자리

2. 백조자리의 데네브, 거문고자리의 베가, 독수리자리의 알타이르가 가장 잘 보이는 계절 : 여름
데네브, 베가, 알타이르의 3가지 별들은 여름철 밤하늘에서 가장 잘 보입니다.
이 별들을 이으면 삼각형 모양이 되고, 이를 여름의 대삼각형이라고 불러요! 

가장 밝은 별인 베가 (위), 데네브 (좌), 알아티르 (우)

3. 돌고래자리가 가장 잘 보이는 시기 : 7~9월
돌고래자리는 늦여름, 가을밤에 볼 수 있는 별자리로 7~9월 사이에 상대적으로 잘 보이는 편이에요.
다만 3등성 이하의 어두운 별들로 구성되어 있어서 관측적합도가 높은 곳에서만 보인답니다.

돌고래자리

4. 대기오염물질과 인공불빛 때문에 시야에서 별이 사라지는 현상 : 광공해 
광해 또는 빛공해라고도 불리는 광공해(light pollution, 光公害)는 인간에 의해 과도하게 발생된 빛에 의한 공해를 의미합니다.
밤에도 꺼지지 않는 조명, 가로등, 네오사인 등이 주변에 위치한 천체관측소의 관측을 방해하고, 사람들이 도시에서 별빛을 보기 어렵게 만들어요. 식물의 광합성 작용을 방해하여 곤충들은 바이오리듬을 잃어버려 이상행동을 하기도 합니다. 여름철 매미가 밤에도 시끄럽게 우는 원인이라고도 해요.


 

오늘의 퀴즈와 오답노트 어떠셨나요?? 조금 도움이 되셨나요?

일단 저는 어디가서 여름의 대삼각형과 가을철 별자리에 대해서 아는 척은 할 수 있을 것 같네요. 후후

이렇게 오늘은 저의 별자리 지식 수준 테스트를 통해서 저의 별린이력(?)을 한번 소개해봤습니다.

다음에는 찐 별린이로써 가장 궁금한 것들부터 차근 차근 가져올게요!!!

제가 별린이에서 별자리 고수가 되는 그날까지!!!! 파이팅!!!!!!

더 다양한 블로그 콘텐츠를 접하시고 싶으시면 여기로 오세요!! (클릭클릭!)


4
0
별 헤는 밤

별 헤는 밤

[이것만 알면 나도 별 고수] 별 사진 잘 찍는 법(1) : 셔터 스피드 조절

<별 헤는 밤> 구경하기 👀

 



반갑습니다. [이것만 알면 나도 별 고수] 과정에 오신 여러분들을 환영합니다!

 

이 과정에서는 평소에 쉽게 이해하거나 기억하기 어려웠던 용어나 개념들을 차근차근 알아볼 거에요.

그러면 별 보기에 관심있는 누구라도 스마트폰이나 카메라로 예쁜 사진들을 찍어갈 수 있을 거에요. 그리고 이 글들을 통해 앞으로는 좀 더 좋은 환경과 조건을 찾아 멋진 밤하늘을 한껏 즐길 수 있도록 도와드릴게요.

 

 

<결론만 보기>

15(초) 내외의 셔터 스피드가 적당

 

- 셔터 스피드가 높은 숫자(5, 15, 30...) 일 수록 더 많은 빛을 수용 ▶ 더 많은 별빛을 담음  ※삼각대 필수!

- 셔터 스피드가 낮은 숫자(1/10000, 1/2000, 1/100...) 일 수록 더 빠르게 촬영 ▶ 움직이는 대상 촬영에 좋음

 

카메라 세팅 이해하기

첫 대주제는 고민을 거듭하다 먼저 카메라로 정했습니다.

멋진 밤하늘이 보이는 날씨나 지역은 우리 <별 헤는 밤> 어플을 통해 쉽게 찾아볼 수 있지만, 여기서 좋은 사진을 찍기 위해서는 각각의 상황에 맞는 카메라 세팅을 할 수 있는 개개인의 재량이 필요하기 때문이에요.

별 사진 촬영에는 여러 세팅이 필요합니다. 초점, 노출 – 조리개, 셔터 스피드, ISO, 화이트밸런스, RAW…

아니 무슨 사진 한 장 찍는데 뭘 이렇게 많이 조정해야해… 읽기만 해도 너무 복잡하죠? 그래서 무작정 야간모드로 사진을 한 번 찍어봅니다. 그러면 우리는 이런 결과물을 얻게 되는거에요.

 

?

 

 

울트론이 침공한 것 같아요&hellip;.

 

 

이건 우리가 ‘셔터 스피드’를 이해하지 못해서 생긴 일입니다.

 

셔터 스피드

셔터 스피드는 카메라의 셔터가 닫히는 속도입니다. 셔터 스피드가 빠르면 노출이 짧아지고, 셔터 스피드가 느리면 노출이 길어집니다.

 

노출은 무엇일까요? 노출은 카메라 렌즈의 구멍을 통해 들어오는 빛을 셔터가 열려 있는 시간만큼 필름이나 이미지 센서에 비추는(노출하는) 일입니다. 여기서 ‘셔터가 열려 있는 시간’, 즉 카메라 셔터가 닫히는 속도를 조절하는 요소가 바로 ‘셔터 스피드’ 인 거에요.

 

 

셔터 스피드가 빠르면

일반적으로 우리가 스마트폰이나 카메라를 조작하면, 거의 촬영 버튼을 누름과 동시에 촬영된 사진을 얻게 됩니다. 셔터 스피드가 굉장히 빠르게 설정되어 있는 상황입니다.

 

셔터 스피드가 빠르면 어떤 점이 좋을까요? 우리는 흔히 점프샷을 찍을 때, 빠르게 점프하는 사람을 찍어야 합니다. 그런데 분명 점프한 순간 버튼을 눌렀는데, 꼭 착지해 있는 모습만 찍힐 때가 있어요. 그래서 좀 일찍 버튼을 누르면, 뛰기 전 모습이 찍히기도 합니다. 이럴 때 셔터 스피드를 더 빠르게 조정하면 원하는 타이밍에 더 근접하게 사진을 찍을 수 있습니다.(물론 딜레이 문제도 있습니다.)

‘연사를 하면 되지 않냐!’ 라고 하시는 분들도 있는데, 맞습니다. 그런데 사실 그것도 셔터 스피드를 굉장히 빠르게 하는 거랍니다. 😊

 

 

 

 

아무튼, 이렇게 셔터 스피드가 빠를 경우에는 빠르게 움직이는 대상을 사진에 잡아낼 수 있습니다. 빠르게 설정할 수록, 정확히 노출된 순간만을 담아 잔상이 남는 것도 방지해 마치 정지해 있는 것처럼 선명한 사진을 얻을 수도 있어요.

 

단점은 무엇일까요? 셔터가 열려 있는 순간이 짧기 때문에, 그 시간동안 원하는 만큼 충분한 빛이 필름이나 센서에 전달되지 못해 비교적 어두운 사진을 얻게 됩니다. 특히 빛이 충분하지 않은 야간에는 그 차이가 크게 나는 편이죠. 이렇게요.

 

완벽한 예시는 아닐 수 있습니다.

 

 

셔터 스피드가 느리면

셔터 스피드를 느리게 하면, 더 많은 수의 작은 별빛들까지 카메라에 담아낼 수 있게 됩니다. 또는 좀 더 역동적인 사진이나 빛의 궤적을 촬영할 수도 있어요. 야경을 찍을 때에도 더 반짝거리고 예쁜 사진들을 얻을 수 있답니다.

 

그래서 보통 카메라의 야간 모드에는 셔터 스피드가 느리게 설정되어 있습니다. 하지만 렌즈가 열려 있는 시간 동안의 모든 변화가 필름이나 센서에 전달되기 때문에, 카메라가 흔들린다거나 피사체가 조금이라도 움직인다면 아까처럼 형체를 알아볼 수 없는 사진을 얻게 된답니다. 그래서 ‘카메라를 움직이지 마세요.’와 같은 경고문이 출력되는 거에요.

SPEED가 1/15로 설정되어 있는 모습

 

셔터 스피드는 카메라 설정에서 보통 1/n ~ N (초) 값으로 표현되어 있습니다. 그 시간 동안 렌즈가 열려있다는 이야기입니다. 우리가 셔터 스피드를 30으로 설정한다면, 렌즈가 30초 동안 열려 있고, 그 시간동안 빛을 받아들임과 동시에 렌즈에 있는 변화를 필름이나 센서에 기록한다는 뜻이에요.

 

일반적으로 1/60 근방의 셔터 스피드를 사용하고, 이를 안전셔터속도(손으로 들고 촬영할 때 흔들림 없이 촬영 가능한 셔터 속도의 최저한계)라고 말합니다. 카메라 렌즈의 종류에 따라 다른 안전셔터속도를 사용하기도 합니다.(ex. 렌즈 사양에 따라 100mm 렌즈의 경우 1/100(초), 200mm렌즈의 경우 1/200(초)...)

 

 

별 사진 찍을 때 좋은 셔터 스피드

그렇다면 별 사진을 촬영할 때는 어느 정도의 셔터 스피드가 적당할까요? 일반적으로 15초 내외의 셔터 스피드가 적당합니다. 별빛을 많이 받기 위해 더 높은 셔터 스피드를 사용해도 되지만, 너무 높은 셔터 스피드를 사용할 경우 지구의 자전에 의해 별의 위치가 점점 변하기 때문에 움직인 궤적이 생길 수 있습니다. 물론 정~~~~~~말 높은 셔터 스피드를 사용할 경우 이런 사진을 찍을 수도 있습니다.

 

한 번 도전해 보세요!

 

그러나 주의해야 할 점은, 처음에 언급했던 것처럼 상황에 맞는 셔터 스피드를 설정해야 한다는 것입니다. 주변에 아무런 광공해가 없다면 좋겠지만, 조금이라도 있다면 멋진 사진을 위해서는 별빛이 보이는 정도를 어느 정도 타협해야 할 수도 있습니다. 참을성을 갖고 몇 차례 시도해 보면서, 오늘 내 상황에 맞는, 맘에 드는 적당한 셔터 스피드를 찾아나가 보도록 합시다.

 

 

삼각대가 필요해요

15초의 셔터 스피드를 설정한다는 것은, 15초동안 카메라가 전혀 움직이지 않고 고정되어 있어야 선명한 사진을 얻을 수 있다는 뜻이기도 합니다. 그래서 우리는 원하는 별 사진을 찍기 위해 삼각대가 꼭 필요합니다. 근처 다이소나 마트 등에서 스마트폰이나 카메라용 삼각대를 만 원 이하의 저렴한 가격에 팔고 있으니 별 사진도 촬영하실 계획이라면 꼭 가져가는 것을 추천해요!

 

하지만 가져가는 것을 깜빡했다거나 이 글을 이미 별 보는 와중에 읽고 계시다면... 근처 바닥에 카메라(스마트폰)를 하늘로 향한 채 두고 버튼을 눌러 놓는 방법도 시도해 볼 수 있습니다.

근데 누가 들고 도망가면 그게 제 책임은 아닙니다...

 

 

삼각대가 있다면, 위에서 보여드린 예시처럼 셔터 스피드를 조절하는 것만으로도 좀 더 좋은 별 사진을 가져갈 수 있습니다! 물론 이제 여기서 간단히 조리개를 조절하고, ISO와 화이트밸런스 값을 설정하고, 포토샵 등을 통해서 몇 차례 후보정을 거치기만 하면 사진작가들처럼 멋진 사진을 찍을 수도 있습니다. 정말 쉽죠?

 

 

어려운데요

그래서 이 [이것만 알면 나도 별 고수] 과정을 준비했습니다. 차근차근 이 글들을 따라오다 보면, 언젠가 나도 별 고수가 되어 멋진 별들을 보고 대단한 사진을 찍을 날이 올 거에요. <별 헤는 밤> 과 함께 별린이에서 별 고수가 되기까지 우리 차근차근 성장해 보도록 합시다. 다음 편에서는 오늘 셔터 스피드처럼 노출에서 중요한 자리를 차지하는 조리개를 이해하는 시간을 가져보겠습니다. 그럼 다음 시간에 봐요!

4
0
별 헤는 밤

별 헤는 밤

[티스토리 블로그 만들기] 홈프로모션 설정하기!라고 적고 티스토리 망치는 방법이라고 읽는다!

<별 헤는 밤> 사용해 보기 👇


 
안녕하세요!
다시 돌아온 <무인도 티스토리에서 살아남기>입니다!!!
 

 
티스토리 블로그 만들기 1편

 

이번 편은 바로

 

홈 프 로 모 션 설 정 하 기.

 

입니다! 지난 편에서 이거 때문에 아주 골머리가 아팠는데요..

당시 나의 심정

 
분명 티스토리 처음 시작하시는 분들 중에 저와 같은 문제에 부딪힌 분들이 있으실 것이기 때문에
제가 찾은 해결방법을 함께 공유해 드리려고 왔습니다!

 

하지만

이번 편은 망했다는 것을 미리 언급드립니다....

 

 

※망함주의※

※※망함주의※※

※※※망함주의※※※

 


제가 찾은 홈프로모션 설정 방법은 크게 두 가지입니다.

1. 홈프로모션에 넣을 이미지 크기를 직접 설정하기
2. 정사각형 프로필로 만들어서 홈 프로모션 포맷을 바꾸기

*홈프로모션 : 메인 홈 화면이나 게시판 상단 이미지 (홈대문)

 
저는 처음에는 두 가지를 다 시도해 봤는데요 여러분들은 이 글을 보시고 적합한 걸로 선택하시면 될 것 같아요!
 
 

▶ 홈프로모션 이미지 크기 설정하기

 
우선 첫 번째로 제가 생각한 해결 방법은 '아 그럼 홈프로모션 이미지 크기에 맞게 이미지를 설정하면 되는 거 아냐?'였습니다.
그래서 우선 홈프로모션 이미지의 크기를 측정하였습니다..... (일단 끝까지 읽어봐 주세요..ㅠ)
이미지 크기를 어떻게 측정하냐고요? 줄자로 재는 거죠!!! ㅎㅎ..
하지만 아쉽게도 (천만 다행히도) 줄자가 없었기에 문명인답게 인터넷을 활용하는 방법을 택했답니다.
구글에 '이미지 자르는 사이트'를 입력해서, 제일 상단에 노출되는 사이트에서 <별 헤는 밤> 티스토리 홈 화면을 전체 캡처한 후에 자를 영역을 선택했습니다. 자를 영역을 선택하면 우측에 그 영역의 크기를 친절하게 알려주더라고요!

 
그렇게 알아낸 홈프로모션 이미지 크기는 (맥북 에어 m1 모니터 화면 기준) 2880 x 679였습니다!!
이제 신나서 또 구글에 이미지 크기 줄여주는 사이트를 입력해서 맨 상단에 쓰는 사이트에 들어갑니다. (I러브IMG 사랑합니다)
그래서 원래 홈프로모션으로 설정하고 싶던 이미지를 '아이러브이미지야 줄여줘!'라고 외치면서 부탁합니다.

 
그럼 어떻게 되냐면요..!!!

...................

흑흑

뭔가 저는 이렇게 이미지 내용들이 압축되는 것이 아닌... 다 같이 알아서 촥! 하고 줄어드는 걸 기대했는데 말이죠...
그래도 직접 적용하면 다르겠지라는 마음을 가져보고...
희망차게 적용을 해보면
 
따란~~!!!!!

......

 

홈프로모션 이미지 변경하는 방법 (더보기)

 
그렇죠.. 티스토리는 받은 이미지를 그대로 출력한 죄 밖에 없는데..... 

I am.. 바보예요..

 
이렇게 해보고 느낀 이 방법의 문제점은
1. 사용하는 컴퓨터의 화면 크기에 따라서 보이는 이미지의 크기가 다르다! (당연하지..)
2. 기존 이미지 크기를 바꿔도 이미지 내용이 압축되는 거지 이미지 내용이 줄어들지는 않는다! (이걸 저는 해봐야 알았습니다.. 예...)
또 가장 큰 문제점

해결되지 않는다!!!

 
그래서 바로 두 번째 방법을 시도해 봅니다.
 

▶ 정사각형 프로필 만들기 

(일단 따라 하지 말아봐 주세요...)
 

1) HTML 수정하기

이 방법은 홈프로모션 화면을 기존처럼 상단에 길게 표시하는 게 아니라 좌측에 프로필 화면처럼 작게 만드는 방법입니다.
우선 기존처럼 동일하게 <스킨 편집> 화면으로 들어가면 됩니다.

 
대신에 이번에는 이렇게 좌측에 있는 'html 편집'을 클릭하셔야 합니다!!
그리고 ctrl + f를 이용해서 'promotion-1-image'를 찾으면 됩니다!
찾으면 되는데..

 
저는 왜 없죠..? 모든 구글 블로그 티스토리에서.. 저걸 찾으라고 하셨는데 저는 없더라고요.. 나만 없어 왜..
그래서 그냥 직접 눈알 빠지도록 찾았습니다.. 
(알고 보니까 코드 화면을 선택한 후에 ctrl+f 키를 눌러야 됐던 것이더라고요..)

역시 노가다가 짱

 
그다음에 아래의 화면에 나와있는 부분을 드래그하여 선택 후 지워줍니다.

대략 저 'promotion-1-image'에서 3번째 줄부터 'promotion-2-image'라고 언급되어 있는 줄의 윗부분까지 삭제해 주면 됩니다.
<별 헤는 밤> 티스토리 기준 82~108까지였습니다. 
삭제한 후 적용 버튼을 눌러줍니다! 그리고 좌측 화면에 있는 새로고침 버튼을 누르면

 
이렇게 홈프로모션 이미지가 아예 삭제되신 것을 보실 수 있을 겁니다.
다시 코드 화면을 선택한 후에 'id="aside"를 검색합니다.

 
검색 후 빨간 부분에

<s_sidebar_element>
  <!-- 프로필 이미지 -->
  <div class="profileBox">
    <ul>
      <li style="background-image: url();" />
    </ul>
  </div>
</s_sidebar_element>

 위의 코드를 입력하면 됩니다. 저는 제가 엔터를 눌러서 띄어쓰기를 했습니다.
 

2) CSS 수정하기

이번에는 CSS 화면으로 들어가서 '#aside"를 검색합니다.

 
그리고 빈부분에 아래의 코드르 입력하시면 되는데, 저는 빈 줄이 없어서 356에서 제가 엔터를 쳤습니다.
그 부분에 (빨간 상자)

#aside .sidebar-1 .profileBox ul li {
	max-height: 260px;
	max-width: 260px;
	height: 22.296296296296296vw;
	width: 22.296296296296296vw;
	background-size: cover;
	background-position: center;
}

위 코드를 입력해 줍니다. (이제 거의 다 왔어요!!)
이후  '@media screen and (max-width:767px)'를 검색하시고 아래의 코드를 입력하시면 됩니다.

#aside .sidebar-1 .profileBox ul li {
	width: 100%;
}

 

 
이렇게 다 하시고 적용을 누르시면! 좌측에 프로필 상자가 생긴답니다!!!

?

 
근데 저는 왜.. 왜 없죠..?? 저 빨간 부분에 제가 선택한 이미지가 짜란~! 하고 나와 있어야 하는데...
이게 뭐죠????????  도대체 무엇이.. 어떻게.. 어디서부터 잘못된 것일까요.. 

 
뭔가 잘못입력했나 싶어서 계속해서 다시 봤는데도 틀린부분이 나오지 않더라고요.. 
그래서 혹시 몰라 참고한 블로그 주소는 올리지 않겠습니다....
이 글을 보시는 누군가가 계신다면.. 잘못된 부분이 있다면 부디 알려주세요.....
그래서 결론은
 
오늘도 해결하지 못했다....

애증의 홈프로모션..

 
티스토리에서 살아남는 꿀팁을 알려드려야 하는데 그러지 못하고 있어서 송구스럽네요..
다음에는 꼭 꼭 꼭 해결방안을 들고 찾아오겠습니다... 
 

아직도 나의 심정

 

4
2
별 헤는 밤

별 헤는 밤

[좋은 UI란 무엇일까?] (1) - 브랜딩과 경험 사이

블로그에서 빠르게 읽기 👇

안녕하세요! 별헤는밤의 디자이너를 맡고 있는 S라고 합니다.

대학생이었던 2021년 말 팀에 합류해, 2년 차 프로덕트 디자이너가 된 지금까지

별 헤는 밤의 디자인을 혼자 맡아 조금씩 개선해가고 있어요.

 

첫 디자인 포스팅으로는,

별밤 팀이 처음 세상에 내보냈던 화면을 전격 뜯어고치게 된 이야기를 풀어보려 합니다!

(부족한 과거 작업이지만, 프로덕트 공부 초기 악으로 깡으로 만든 작업물이니 귀엽게 봐주세요🤧)

 


🚀 브랜딩 중심 UI에서 경험 중심 UI로

제가 팀에 합류한 시점은 2021년 말, 초기 별밤 팀이 데이터 활용 공모전을 준비하던 막바지였습니다.

해당 시점에는 이미 초기 디자인 컨셉이 존재했고, 제가 급하게 바통을 이어받게 된 상황이었으므로

당시 저는 기존 컨셉에 충실하여 시간 안에 작업물을 완성시키는 데에 주력을 다했었어요.

 

그렇게 세상에 나온 모습이 바로 아래의 V1 화면이에요.

별헤는밤 첫 출시 화면 (2021)

 

 

위 화면을 보면 어떤 느낌이 드시나요?

초기 별헤는밤의 디자인은 예쁜 밤하늘이 떠오르는 감성적인 앱으로 브랜딩하려 했기에

화면 이곳저곳에서 ‘그라데이션 및 glow 효과’를 많이 사용한 모습을 확인하실 수 있는데요.

(좌)여백에 그라데이션 처리 (중)shadow 대신 outer glow를 활용한 모습 (우)영역을 채우지 않고 그라데이션 처리

 

V1 출시 이후, 디자인을 수정할 여유가 나자마자

저희는 초기 디자인 컨셉을 휴지통에 버리게 됩니다.

그 이유는 무엇이었을까요?

.

.

아래 사진에서 힌트를 얻을 수 있답니다.

 

'으악, 눈부셔!'

 정답은 바로,

“예뻐보이려고 한 디자인이 유저의 핵심 경험에 불편을 초래했기 때문”이에요.

다크모드에서 명도 차가 나는 그라데이션을 사용하면, 더 밝은 부분에서 빛이 새어 들어오는 듯한 느낌이 들기 마련인데요.

바로 그것이 관측지에서 앱을 사용하는 유저들의 눈에 피로감과 관측 불편을 야기할 수 있는 것이죠.

 

🙄 ‘에이, 다크모드면 충분하지!’

겨우 그라데이션 가지고 밝은 느낌이 든다니! 도심 속에서는 전혀 체감할 수가 없는데요.

정말 그렇게 불편할까요?

 

관측지에서는 빛이 조금만 밝아도 관측에 방해가 될 수 있어요. (좌측 하단 붉은 빛) Ⓒ 별헤는밤 워크샵 사진

 

 

별 헤는 밤은, 별을 좋아하는 사람들에게 밤하늘 관측 정보를 전달하기 위해 만들어진 서비스로

광공해가 중요한 천체 관측의 특성상 아주 깜깜한 곳에서 사용하게 될 가능성이 높은 앱이에요.

(*광공해 : 인간에 의해 발생된, 과잉 또는 필요 이상의 빛에 의한 공해. 별빛을 흐리고 천문대의 관측을 방해한다.)

 

그래서 별헤는밤은 필수적으로 어두운 다크모드의 앱이어야 했어요.

하지만 기껏 다크모드로 만들어놓고,

브랜딩 컨셉을 맞추려 예뻐 보이는 그라데이션을 남발했다가 불편한 앱이 되어버린 거죠.

 

😥 : “예쁘기만 하면 되는 게 아니었구나… 이건 망한 UI 디자인이다!”

 

-

 

한 차례의 깨달음 후, 저는 부끄러움에 사로잡혀

별밤을 브랜딩 중시 앱에서 유저 경험을 우선하는 앱으로 빠르게 전환하고 싶어졌어요.

예쁜 디자인보다 실용적인 디자인을 앞세워서요.

 

그래서 이때부터 ‘다크모드에서 더 다크모드로’라는 새로운 목표를 세우고,

별밤은 첫 디자인 업데이트를 시작하게 됩니다!

 

 

-다음 편에 계속!-

🌠 다음 이야기 : [좋은 UI란 무엇일까?] (2) - 다크모드에서 더 다크모드로

 

 

이미 다크모드인데, 어떻게 더 다크모드가 될 수 있을까 궁금하지 않으신가요?

디자인을 미리 보고 싶다면 지금 플레이스토어에서 별 헤는 밤을 다운로드하세요! 🙌

 

 

 

5
2
별 헤는 밤

별 헤는 밤

[Android] 게시글 댓글 기능 구현하기

<별 헤는 밤> 앱 구경하기

목표: 기존 게시글 페이지에 댓글 기능 추가하기

별 헤는 밤 v1은 유저 간의 소통 방식이 게시글을 작성하는 것 밖에 없었기 때문에 커뮤니티 기능을 추가하기 위해서 댓글 기능을 구현하는 것이 필요해졌다.

댓글 기능을 구현하면서 서버 구현은 다른 기능과 유사하여 어려움이 크게 없었지만, 레이아웃이 여태까지 구현했던 화면가 특징이 달라서 애를 많이 먹었다.

댓글 기능이 있는 레이아웃의 특징은 다음과 같다.

  • 댓글이 일정 개수가 넘게 되면 따로 스크롤이 되도록 변화해야 한다.

  • 화면의 전체적인 길이, 다른 레이아웃의 위치 등을 동적으로 바꿔야 한다.


1. 스크롤 기능 구현 시 문제점

댓글이 일정 개수가 넘게 되면 높이가 더 이상 늘어나지 않고 고정된 높이에서 이중으로 스크롤이 될 수 있도록 구현해야 했다.

댓글을 작성하고 화면에 반영되는 로직은 다음과 같다.

댓글을 입력 → 서버에서 댓글을 생성 → 화면 새로고침 → 새로운 댓글 목록을 가져옴

그럼 다음과 같은 생각이 들 것이다.

‘어 어차피 댓글 목록 새로 가져오면 자동으로 높이가 list에 맞춰지는데 굳이 recyclerview 높이를 따로 설정할 필요가 있나?’

당연하고 자연스러운 발상이지만 Android는 기본적으로 Scrollview안에 recyclerview를 넣어 이중 스크롤이 되는 것을 지양한다.

따라서 보통 NestedScrollview를 사용하여 초기에 recyclerview item을 모두 불러와 단일 스크롤로 형성하게 만든다. 그럼 1번 이미지처럼 모든 댓글을 한 번에 다 출력되기 때문에, 만약 댓글이 100개가 넘어가게 된다면 굉장히 오래 화면을 내려야 할 것이다.

그렇다고 recylcerview의 높이를 고정시키면 2번째 화면 같이 불필요한 공백이 생겨 효율적이지 않다.

 

실패코드

<NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
				<LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="16dp"
            android:orientation="vertical">

          <androidx.recyclerview.widget.RecyclerView
              android:id="@+id/commentRecyclerview"
              android:layout_width="match_parent"
              android:layout_height="wrap_content" // or 512dp로 고정하면 여백이 존재하게 됨
              android:orientation="vertical"
              />
       </LinearLayout>
</LinearLayout>

댓글 recyclerview 의 xml (실패의 케이스)


2. 동적 레이아웃 구현을 통해 문제 해결

이중 스크롤을 구현하는데 정적인 화면으로는 구현할 수 없다는 것을 알았으니 동적으로 레이아웃을 설정하기로 결정했다.

우선 댓글 창 인터페이스에서 고려해야 하는 변화는 다음과 같다.

  • 댓글 삭제/ 생성 시 댓글 창 높이 변화

  • 댓글이 한 개도 존재하지 않게 되었을 때 댓글 창 레이아웃 삭제

  • 댓글 창 높이 변화 이벤트

if(result.size()>1&&result.size()<5){ //댓글이 4개까지만 늘어나고 5개부터는 고정됨
         int padding_in_dp = 108*result.size(); // xml에서 댓글 item 높이는 108dp
         final float scale = getResources(). getDisplayMetrics(). density;
         int padding_in_px = (int) (padding_in_dp * scale + 0.5f);
         LinearLayout.LayoutParams params=(LinearLayout.LayoutParams) commentRecyclerView.getLayoutParams();
         params.height=padding_in_px;
         commentRecyclerView.setLayoutParams(params);
         }else if(result.size()>4){
            int padding_in_dp = 512;
            final float scale = getResources().getDisplayMetrics().density;
            int padding_in_px = (int) (padding_in_dp * scale + 0.5f);
            LinearLayout.LayoutParams params=(LinearLayout.LayoutParams)commentRecyclerView.getLayoutParams();
            params.height=padding_in_px;
            commentRecyclerView.setLayoutParams(params);
      }

 

댓글 목록을 업데이트하는 함수 안에 위와 같이 Recyclerview의 LayoutParams의 높이를 직접 수정하는 코드를 추가하여 해결했다. 이때 주의할 점은 xml은 px, dp 값 둘 다 설정이 가능하지만 코드상에서는 px값 밖에 사용할 수 없다. 따라서 xml에서 dp값을 사용했다면 이를 변환시켜주는 과정이 필요하다. 그래서 ‘getResources(). getDisplayMetrics(). density’ 코드로 1dp가 몇 px인지를 구하여 대입해 주면 된다.

 

  • 댓글 창 레이아웃 삭제 이벤트

 if (!result.isEmpty()){
               commentRecyclerView.setVisibility(View.VISIBLE);
           }else{
               commentRecyclerView.setVisibility(View.GONE);
           }

recyclerview 댓글 유무에 따라 visibility 조정

 

댓글 창이 아예 삭제되어야 하는 상황의 경우 이전에 ‘더 보기’ 기능을 구현한 부분을 참고해서 댓글이 존재하지 않을 경우 setVisibility(View.GONE)을 통해 댓글 레이아웃을 삭제했다.

위와 같이 코드 상에서 동적으로 화면을 구성하면 아래 화면과 같이 댓글이 5개 이상 넘어가면 높이가 고정되고 이중으로 스크롤되는 것을 확인할 수 있다.

이렇게 동적인 레이아웃 구성으로 댓글 창을 구현해 보았다.

다음에는 게시글 이미지에서 viewPager2에 터치와 스와이프 기능 분리 주제를 다루도록 해보자.

 


P.S ) 아래 코드는 java 파일의 댓글 레이아웃 구현과 관련된 전체 코드입니다.

<댓글 Layout 구현 부분(전체) - PostActivtiy.java>

Call<List<PostComment>> commentCall = //retrofit Call 함수 적기;
     commentCall.enqueue(new Callback<List<PostComment>>() {
         @Override
         public void onResponse(Call<List<PostComment>> call, Response<List<PostComment>> response) {
             if(response.isSuccessful()){
                 List<PostComment> result = response.body();
                 if (!result.isEmpty()){
                     commentRecyclerView.setVisibility(View.VISIBLE);
                 }else{
                     commentRecyclerView.setVisibility(View.GONE);
                 }
                 for(int i=0;i<result.size();i++){
                     commentAdapter.addItem(result.get(i));
                 }
                 if(result.size()>1&&result.size()<5){ //댓글이 4개까지만 늘어나고 5개부터는 고정됨
                     int padding_in_dp = 108*result.size();
                     final float scale = getResources().getDisplayMetrics().density;
                     int padding_in_px = (int) (padding_in_dp * scale + 0.5f);
                     LinearLayout.LayoutParams params=(LinearLayout.LayoutParams)commentRecyclerView.getLayoutParams();
                     params.height=padding_in_px;
                     commentRecyclerView.setLayoutParams(params);
                 }
                 else if(result.size()>4){
                     int padding_in_dp = 512;
                     final float scale = getResources().getDisplayMetrics().density;
                     int padding_in_px = (int) (padding_in_dp * scale + 0.5f);
                     LinearLayout.LayoutParams params=(LinearLayout.LayoutParams)commentRecyclerView.getLayoutParams();
                     params.height=padding_in_px;
                     commentRecyclerView.setLayoutParams(params);
                 }
                 postCommentCount.setText(String.valueOf(result.size()));
                 commentRecyclerView.setAdapter(commentAdapter);
             }
             else{
                 Log.d("postComment", "게시물 댓글 정보 업로드 실패");
             }
         }

         @Override
         public void onFailure(Call<List<PostComment>> call, Throwable t) {
             Log.d("postComment", "게시물 댓글 인터넷 오류");
         }
     });


<별 헤는 밤> 블로그에서 더 빠르게 읽어보기

3
0
별 헤는 밤

별 헤는 밤

[WebClient] 비동기 아키텍처를 통한 외부 api 콜 성능 개선

<별 헤는 밤> 블로그 구경가기

 

▶ 개요 및 배경

별 헤는 밤 버전 업데이트를 진행하면서, 날씨 페이지를 맡게 되었다.

날씨 페이지의 메인 로직은 외부 api를 호출하고, 응답받은 날씨 데이터를 적절히 분석하여 보여주는 것이다.

날씨 페이지에서는 아래와 같은 2가지 외부 api 호출이 필요하다.

기존에는 앱단에서 직접 api를 호출하는 방식이었는데, 해당 구조는 다음과 같은 문제점이 있었다.

  • 프런트 단에서 데이터를 직접 호출하는 것이므로 성격에 맞지 않는다고 판단

  • 날씨 데이터를 파싱하고 분석하는 로직이 복잡하여 코드가 길어지고 가독성이 떨어짐

  • 기획이 수정되면서 호출한 날씨 정보를 DB에 저장해야 되는 기능이 필요해짐

이에 따라 외부 api 호출을 BE 단에서 하는 구조로 수정하였고, 성능 개선을 고민하게 되면서 외부 api를 호출할 때 WebClient라는 http 호출 클라이언트 라이브러리를 사용했다.

 

▶ WebClient 란?

 

Spring 공식 문서에 다음과 같이 나와있다.

Spring WebFlux includes a client to perform HTTP requests with. WebClient has a functional, fluent API based on Reactor, see Reactive Libraries, which enables declarative composition of asynchronous logic without the need to deal with threads or concurrency.

 

… 역시 영어는 어렵다. 한줄 요약하면 다음과 같다.

WebClient = Spring WebFlux에 포함된 Http 호출 클라이언트로, Reactor를 기반으로 하여 비동기적으로 동작한다.

 

Http 호출 클라이언트라는 것은 알겠는데 Reactor 를 기반으로 하는 것은 무엇이며 왜 비동기로 동작한다는 것일까? Spring WebFlux 은 또 뭘까? 이에 대해 간단히 알아보자.

 

 

▶ Spring WebFlux 🆚 Spring MVC

 

우리가 흔하게 사용하는 Spring MVC 패턴에서는 서버가 동시에 여러 요청을 받을 수 있도록 Multi Thread + Blocking

방식으로 동작한다.

 

위 그림과 같이 요청이 들어올 때마다 미리 만들어둔 Thread 를 할당하고, 만약 Thread Pool 이 꽉 차면 Queue에 대기 상태로 존재하게 된다.

이렇게 Spring MVC 패턴에서는 Thread 수를 늘려 동시성을 처리한다.

이와 달리 Spring WebFlux 는 하나의 Thread로 동작한다. Spring WebFlux 란, reactive programming을 할 수 있게 도와주는 스프링 모듈로, Single-Thread와 Non-Blocking 방식으로 동작한다.

 

위 그림과 같이 이벤트 루프를 사용해 응답이 올 때까지 Blocking 되지 않고 콜백 함수를 사용해 응답이 온 것을 알아 차린다. 따라서 적은 자원으로 여러 요청을 처리할 수 있다는 장점이 있다.

 

 

▶ Reactor 란?

 

Reactor는 Reactive Programming 을 할 수 있게 만들어진, JVM 환경에서 동작하는 non-blocking reactive 라이브러리이다.

  • Reactive Programming

    리액티브 프로그래밍은 데이터 스트림 및 변경 전파와 관련된 선언적 프로그래밍 패러다임이며, 리액티브 프로그래밍을 사용하면 정적이나 동적인 데이터 스트림을 쉽게 표현할 수 있다. by https://en.wikipedia.org/wiki/Reactive_programming 

* 위에서 말한 데이터 스트림이란 데이터의 흐름이라고 이해하면 편하다.

 

Reactive Stream은 Publisher - Subscriber 구조로 되어있으며, 스트림에 데이터가 들어왔을 때 Publisher는 Subscriber에게 데이터를 Push 하게 된다. 여기서 중요한 점은 Subscriber가 Publisher를 구독하지 않으면 아무 일도 발생하지 않는다는 것이다. 이는 Publisher - Subscriber 구조의 매우 중요한 특징이므로 꼭 기억하길 바란다.

Reactor에서는 Mono와 Flux 라는 두 가지 reactive 데이터 타입이 제공된다.

Mono는 0 또는 1개의 항목을 가지는 리액티브 시퀀스를 나타내며, Flux는 여러 개 (0~N) 항목을 가지는 리액티브 시퀀스를 나타낸다.

 

Mono<Integer> data0 = Mono.just(1); // Mono 생성

Flux<Integer> data1= Flux.range(1, 10); // Flux 생성

 

위와 같이 간단하게 Mono, Flux를 생성할 수 있다. 하지만 위와 같이 코드를 작성하면 아무 일도 일어나지 않는다.

Subscriber가 Publisher를 구독하지 않으면 아무 일도 발생하지 않는다 라고 한 게 기억나는가?

 

Flux<Integer> data1= Flux.range(1, 10).log();

data1.subscribe(); // 이 때 체인이 실행됨

 

위와 같이 subscribe()를 붙여야 체인이 동작한다.

이 정도로 개념을 간단히 정리하고 날씨 페이지 코드를 살펴보자.

 

 

▶ 코드 구현

public Mono<OpenWeatherResponse> getOpenWeather(Double lat, Double lon) {

		UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(OPEN_WEATHER_URL);
		uriBuilder.queryParam("lat", lat);
		uriBuilder.queryParam("lon", lon);
		uriBuilder.queryParam("exclude", OPEN_WEATHER_EXCLUDE);
		uriBuilder.queryParam("appid", OPEN_WEATHER_API_KEY);
		uriBuilder.queryParam("units", OPEN_WEATHER_UNITS);
		
		return webClient.get()
		        .uri(uriBuilder.build().toUri())
		        .retrieve()
		        .toEntity(OpenWeatherResponse.class)
		        .flatMap(response -> {
		            log.info("HTTP Response for Open Weather ({}, {}) | {} | {}", lat, lon, response.getStatusCode(), response.getBody());
		            return Mono.just(Objects.requireNonNull(response.getBody()));
		        });
}

getOpenWeather 메서드

 

위 코드는 OpenWeather에서 날씨 예보 정보를 불러오는 코드이다.

webClient 뒤에 .get() 은 GET 요청을 의미하고,. uri() 은 말 그대로 요청 uri를 나타낸다.

. retrieve()는 응답을 받는 메서드로, ResponseEntity 형태로 받는다.

. toEntity()는 원하는 형태로 응답을 변환하는 메서드,. flatMap()는 Mono의 operator 중 하나로, 값을 변환하고 싶을 때 사용하는 메서드이다.

위에서 배운 Mono 형태로 데이터를 리턴하는 것을 알 수 있다.

 

public Map<String, String> getFineDustMap(String date) {

    UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(AIR_KOREA_URL);
    uriBuilder.queryParam("searchDate", date);
    uriBuilder.queryParam("serviceKey", AIR_KOREA_API_KEY);
    uriBuilder.queryParam("returnType", AIR_KOREA_RETURN_TYPE);
    uriBuilder.queryParam("informCode", AIR_KOREA_INFORM_CODE);

    Map<String, String> result = new HashMap<>();

    webClient.get()
            .uri(uriBuilder.build().toUri())
            .retrieve()
            .toEntity(AirKoreaResponse.class)
            .doOnNext(response -> {
                log.info("HTTP Response for Air Korea Get | {} | {}", response.getStatusCode(), response.getBody());
                AirKoreaResponse airKoreaResponse = response.getBody();

								// ...
								// airKoreaResponse 로 result 를 만드는 로직
								// ...
								
            })
            .retryWhen(Retry.fixedDelay(2, Duration.ofSeconds(5)))
            .block();
    return result;
}

getFineDustMap 메서드

 

날씨 페이지에서는 날씨 예보 뿐만 아니라, 미세먼지 정보도 필요하다고 위에서 언급했다. getOpenWeather()와 비슷하게 webClient를 사용하여 외부 api를 호출하는 것을 알 수 있다.

다른 점은. block()를 사용한 것인데, 이 메서드는 비동기로 동작하는 webclient를 응답이 올 때까지 기다리게 하는 메서드이다. 사용하는 것을 권장하지 않지만, 비즈니스 로직 중 미세먼지 값을 받고 난 후, 처리해야 하는 로직이 있어 부득이하게 해당 함수를 사용하였다.

 

위에 설명한 WebFlux, Reactive, Mono, Flux 등에는 사실 매우 방대한 개념이 있고, 이 글만을 읽고는 이해하기 어려운 개념들이다. webClient 동작 방식, 사용법을 위해 간단히 설명한 정도이고 REAL 리액티브 프로그래밍을 위해선 application 전체를 Non-Blocking으로 구현해야 한다. 하지만 Spring에서 http 호출을 위해 webClient를 쓰는 것을 권장하고 있으니, 이런 개념에 대해 한번쯤 알아두면 좋을 것 같다.

5
0
별 헤는 밤

별 헤는 밤

[JPA] 검색 메소드 수정하기 - N+1 문제

👇별헤는밤 티스토리에서 전문을 확인해보세요!👇

[오늘의 할 일]

검색어, 필터(해쉬태그, 지역)를 적용하여 관측지를 검색할 수 있는 메소드를 수정한다.

 별 헤는 밤에는 관측지 검색기능이 존재한다. 검색어와 필터를 통해 검색할 수 있는데 초기 구현 버전은 아래와 같았다.

간단히 설명하면(전혀 간단하지 않게 구현했지만)

  1. 검색어, 해시태그, 지역이 존재하는 경우를 모두 나누고

  2. 각각의 결과를 다른 리스트에 담고

  3. 공통으로 존재하는 결과를 추려서 반환했다

  4. 페이지 처리도 없이!

기존 코드

Observation.java

public class Observation {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long observationId;

    @Column(nullable = false, unique = true)
    private String observationName;

    @Column
    private String intro;   //한줄소개
		@Column
    private Long areaCode;  //지역코드

		//---생약---//

		@OneToMany(mappedBy = "observation")
    private List<ObserveFee> observeFees=new ArrayList<>();

		@OneToMany(mappedBy = "observation")
    private List<ObserveHashTag> observeHashTags=new ArrayList<>();

    @OneToMany(mappedBy = "observation")
    private List<ObserveImage> observeImages = new ArrayList<>();
}

 

ObserveHashTag.java(별헤는밤 티스토리에서 구경하실 수 있습니다!)

 

ObservationService.java(별헤는밤 티스토리에서 구경하기!)

 

문제점을 정리해보면 다음과 같다.

문제점

  1. 불필요하게 너무 많이 쿼리를 호출한다.

  2. 검색조건에 따른 분기를 더 깔끔하게 해결하고 싶다.

  3. n+1문제 해결하기(이미지, 해시태그, 요금)

오늘은 이 중에 3번 문제를 먼저 해결해보려고 한다.

 

수정

1. N+1문제 해결
N+1문제는 연관 관계가 설정된 엔티티를 조회할 경우(1)에 조회된 데이터 개수(n) 만큼 연관 관계의 조회 쿼리가 추가로 발생하여 n+1만큼의 데이터를 읽어오는 현상이다.

 

별 헤는 밤의 경우에는 관측지에 연관된 관측지 이미지, 해시태그, 관측지 요금 3가지가 모두 n+1문제가 있었다.

 

n+1문제를 해결하기 위해 내가 고려한 방법은 다음과 같다.

  • 방법1 fetch join
    JPA에서 일반조인이 아닌 fetch join을 사용하면 n+1문제를 해결할 수 있다.
    JPA에서 일반조인이 아닌 fetch join을 사용하면 n+1문제를 해결할 수 있다.

    • 일반 조인
      ◦ 연관 엔티티에 join을 하게 되면 Select 대상의 엔티티는 영속화하여 가져오지만, 조인의 대상은 영속화하여 가져오지 않는다.
      ◦ 연관 엔티티가 검색 조건에 포함되고, 조회의 주체가 검색 엔티티뿐일 때 사용하면 좋다.

💡 어떻게 join을 하는데 연관 객체를 영속화하지 않을 수 있을까?
→ JPA에서 연관 객체는 프록시 객체로 가져온다. fetch 전략이 LAZY로 설정되어있는 경우 연관 객체를 프록시 객체로 설정하기 때문이다.

 

  • 패치 조인
    ◦ 연관 엔티티에 fetch join을 하게 되면 select 대상의 엔티티뿐만 아니라 조인의 대상까지 영속화하여 가져온다.
    ◦ 연관 엔티티까지 select의 대상일 때, N+1의 문제를 해결하여 가져올 수 있는 좋은 방법이다.

    -> pagination을 못 쓰고 한 개의 속성에만 적용 가능하다.
    우리의 경우 이미지, 해시태그, 요금 3개의 속성이 일대다이므로 적합하지 않아 사용하지 않았다.

  • 방법2 @BatchSize → 적용
    1대 다 관계에서 ‘다’에 해당하는 many의 모든 값을 한 번에 가져오는 것이 아닌 BatchSize로 지정된 만큼 range로 한 번에 가져온다. 이 경우 연관된 데이터의 수에 따라 한번의 조회로 값을 전부 가져오지는 못할 수 있다.
    → 우리의 경우 각 속성의 특성에 맞게 Batchsize를 지정해 줌으로써 해결하였다.

다음은 BatchSize가 적용된 코드의 일부분이다. 적용 방법은 1대다에서 ‘1’에 해당하는 엔티티에 @BatchSize 어노테이션을 추가하면 된다.

@Table(name="observation")
public class Observation { 		
	...
		@OneToMany(mappedBy = "observation")
    @BatchSize(size = 10)
    private List<ObserveFee> observeFees=new ArrayList<>();

    @OneToMany(mappedBy = "observation")
    @BatchSize(size = 20)
    private List<ObserveHashTag> observeHashTags=new ArrayList<>();

    @OneToMany(mappedBy = "observation")
    @BatchSize(size = 10)
    private List<ObserveImage> observeImages = new ArrayList<>();
}

 

여기까지 3가지 문제점 중 가장 간단한 n+1 문제를 해결해보았다.

다음 시간에는 복잡한 두 가지 문제를 해결한 방법을 알아보자!


🔴
별헤는밤 티스토리에서 다음 이야기 업데이트를 누구보다 빠르게 확인하실 수 있어요!



5
0
별 헤는 밤

별 헤는 밤

<나혼자산다> 코드쿤스트가 선택한 중미산 천문대

다들 이번주 나혼자산다의 코코사이언스 과학 실험 보셨나요? 👀

다운로드.jpeg

이번편에서는 코드쿤스트가 동심으로 돌아가 과학실험을 하는 모습들을 보여줬습니다.

저도 덩달아 동심으로 돌아가는 느낌이 들어서 좋았던 회차였습니다.

마지막에 코드쿤스트가 슈퍼블루문(지난번에 설명드렸던 그 달!)을 보러간 천문대는 '중미산 천문대'라고 합니다!

트민남! 아니 트렌디한 남자 코드쿤스트가 선택한 트렌디한 '중미산 천문대'!

'중미산 천문대'에 대한 정보를 간편하고 빠르게 얻고 싶으신 분들은 모두 🌟<별 헤는 밤>🌟을 주목하세요!

<별 헤는 밤>에서는 '중미산 천문대'에 대한 정보(주소, 입장료, 휴일, 주차시설)와 실제 다녀오신 분들의 생생한 후기! 천문대 근처 관광 코스까지 모두 다 알 수 있습니다! (맛집들도 많아요!)

또한 내가 가고 싶은 날짜에 '중미산 천문대'의 날씨와 시간별 관측적합도까지 알 수 있답니다!

제목을-입력해주세요_-001 (3).png

'중미산 천문대' 외에도 우리나라에 숨은 별자리 맛집들에 대한 정보가 아주 많으니 아직 사용해보지 않으신 분들은 모두 여기로!! 🙆🏻‍♀️

5
0
별 헤는 밤

별 헤는 밤

다음에 보자! 언제? 2037년 1월 31일에! .....

8월 31일에 슈퍼블루문이 떴다는 사실 다들 알고 계신가요?
그래서 슈퍼블루문이 뭐야? 큰 파란색 달이라는건가??
슈퍼블루문은 슈퍼문과 블루문을 동시에 볼 수 있는 달을 뜻해요.
슈퍼문은 달이 지구에서 가장 가까운 지점인 근지점에 있을때의 보름달을 의미하고,
블루문은 한달에 보름달이 두번째 뜨는 현상에서, 두번재로 뜬 보름달을 의미해요.
따라서 블루문은 달의 색과는 무관하다는 사실!! 🫢
슈퍼문과 블루문이 동시에 뜨는 현상은 매우 희귀해서 가장 최근에 떴을 때가 2018년이었다고 해요. 2023년인 올해를 놓치셨다면

다음 슈퍼블루문은 무려 14년 뒤인 2037년이라는 사실..!

우리 모두 앞으로 남은 한해도 슈퍼블루문의 기운을 받아 행복하게 보냅시다!! 🍀

KakaoTalk_Photo_2023-09-05-21-32-19.jpeg

제가 찍은 슈퍼블루문 사진 두고 갑니다 (총총)

2
0