개발자라면 한 번쯤은 마주치게 되는 상황, 바로 모바일 웹페이지 디버깅이죠! 스마트폰 화면을 보면서 일일이 수정하기엔 너무 답답하고, PC처럼 개발자 도구가 있으면 얼마나 좋을까 싶을 때가 많을 거예요.

걱정 마세요! 갤럭시 스마트폰과 크롬 브라우저만 있다면 PC에서 모바일 웹페이지를 원격으로 디버깅할 수 있답니다. 지금부터 차근차근 따라 해볼까요?


1단계: 개발자 옵션 활성화하기

가장 먼저 할 일은 갤럭시 폰에서 개발자 옵션을 활성화하는 거예요. 이게 있어야 USB 디버깅 기능을 사용할 수 있거든요. 혹시 아직 활성화하지 않았다면 아래 링크를 참고해서 빠르게 설정해주세요!

https://comeinsidebox.com/enable-galaxy-developer-options-and-debugging/


2단계: USB 디버깅 켜기

개발자 옵션을 활성화했다면, 이제 USB 디버깅을 켤 차례예요.

  1. 설정 앱을 열어주세요.
  2. 스크롤을 쭉 내리다 보면 새로 생긴 개발자 옵션이 보일 거예요. 탭해서 들어가 주세요.
  3. 개발자 옵션 메뉴에서 USB 디버깅 스위치를 찾아 활성화시켜주세요.

이제 스마트폰 설정은 준비 완료!


3단계: 컴퓨터와 스마트폰 연결 및 chrome://inspect 접속

이제 컴퓨터 차례예요. 크롬 브라우저를 켜고 주소창에 다음을 입력해주세요.

chrome://inspect

 

그리고 USB 케이블로 컴퓨터와 스마트폰을 연결해주세요.

 

짜잔! 이렇게 생긴 창이 나타날 거예요.

만약 위 스크린샷처럼 스마트폰에서 보고 있는 크롬 페이지 목록이 바로 보이지 않고,

"Pending authentication: please accept debugging session on the device."

라는 메시지가 뜬다면 당황하지 마세요! 스마트폰에서 디버깅 세션을 수락해야 한다는 뜻이에요. 이럴 땐 개발자 옵션 > USB 디버깅 스위치를 껐다가 다시 켜보세요. 아마 스마트폰 화면에 "USB 디버깅을 허용하시겠습니까?" 같은 팝업이 뜰 거예요. **"허용" 또는 "확인"**을 눌러주시면 됩니다!


4단계: 원하는 페이지 디버깅 시작!

chrome://inspect 페이지에서 스마트폰의 크롬 브라우저에 띄워진 페이지 목록이 보이면, 이제 원하는 페이지의 'inspect' 버튼을 클릭하기만 하면 돼요.

'inspect'를 클릭하면 PC 크롬 브라우저에 익숙한 개발자 도구 창이 뙇! 하고 나타날 거예요. 이제 PC에서 편하게 모바일 웹페이지를 검사하고 디버깅할 수 있게 된 거죠. 마치 스마트폰에서 개발자 도구를 켜서 보는 것처럼요!

어떤가요? 생각보다 간단하죠? 이제 모바일 웹 디버깅, 답답해하지 말고 PC에서 시원하게 해결해보세요! 궁금한 점이 있다면 언제든지 댓글로 물어봐 주세요. 😊

😱 Git Merge, 실수했다면? git revert로 안전하게 되돌리기

안녕하세요, 개발자 여러분!

팀 프로젝트에서 야심차게 개발한 기능을 main 브랜치에 머지(Merge)했는데... 앗! 😱 예상치 못한 치명적인 버그가 발견되어 긴급하게 배포를 되돌려야 하는 상황, 한 번쯤 겪어보셨을 겁니다.

이때 git reset으로 과거를 지워버리자니 이미 팀원들과 공유된 브랜치라 무섭고, 어떻게 해야 할지 막막하셨나요?

오늘은 이런 상황에서 우리의 히어로가 되어줄 git revert와, 특히 머지 커밋(Merge Commit)을 되돌릴 때 꼭 알아야 할 -m 옵션의 비밀에 대해 쉽고 자세하게 알아보겠습니다.

왜 머지 커밋 Revert는 특별할까요?

일반 커밋을 revert하는 것은 간단합니다. git revert <커밋_해시> 한 줄이면 끝이죠. Git이 해당 커밋의 변경 사항을 정확히 반대로 적용하는 새로운 커밋을 만들어주니까요.

하지만 머지 커밋은 부모(parent)가 두 개라는 점에서 다릅니다.

feature 브랜치를 main 브랜치에 머지한 상황을 생각해볼까요? 이 머지 커밋은 두 개의 부모를 가집니다.

  • 부모 1: 머지되기 전 main 브랜치의 마지막 커밋
  • 부모 2: 머지된 feature 브랜치의 마지막 커밋

이 때문에 git revert를 그냥 실행하면 Git은 혼란에 빠집니다. "두 부모 중 어떤 것을 기준으로 삼고, 어떤 부모로부터 온 변경 사항을 되돌려야 하지?" 라고 말이죠.

바로 이때, 우리가 Git에게 방향을 알려주는 나침반 역할을 하는 것이 -m (또는 --mainline) 옵션입니다.

실전! 머지 커밋 Revert 따라하기 (Step-by-Step)

자, 이제 feature/login 브랜치를 main에 머지했다가 되돌리는 시나리오로 직접 실습해보겠습니다.

STEP 1: 되돌릴 머지 커밋 찾기

가장 먼저, 문제의 머지 커밋을 찾아야 합니다. git log를 이용해 히스토리를 확인합시다.

unfold_lessbash
content_copyterminal
git log --oneline --graph

다음과 같은 로그를 확인했다고 가정해봅시다.

unfold_lessplaintext
content_copyaddcompare_arrowsopen_in_full
*   a1b2c3d (HEAD -> main) Merge branch 'feature/login'
|\
| * 7f8e9d0 (feature/login) Add login form validation
| * 6a5b4c3 Add login button
|/
*   d4e5f6g Initial commit on main

여기서 우리가 되돌리고 싶은 범인은 a1b2c3d 머지 커밋입니다.

STEP 2: 주축(Mainline) 부모 번호 확인하기

이제 -m 옵션에 쓸 부모 번호를 알아낼 차례입니다. git show 명령어로 머지 커밋의 상세 정보를 확인합니다.

unfold_lessbash
content_copyterminal
git show a1b2c3d

출력 결과 상단에 이런 정보가 보일 겁니다.

unfold_lessplaintext
content_copyaddcompare_arrowsopen_in_full
commit a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
Merge: d4e5f6g 7f8e9d0
Author: Your Name <you@example.com>
Date:   ...

    Merge branch 'feature/login'

Merge: d4e5f6g 7f8e9d0 라인을 주목하세요!

  • d4e5f6g: 첫 번째 부모 (parent 1) 입니다. 즉, main 브랜치의 커밋입니다.
  • 7f8e9d0: 두 번째 부모 (parent 2) 입니다. feature/login 브랜치에서 온 커밋이죠.

우리는 feature/login에서 온 변경 사항을 없애고 싶으므로, main 브랜치의 히스토리를 주축(mainline)으로 유지해야 합니다. 따라서 부모 1번을 선택해야 합니다.

STEP 3: git revert 실행하기 ✅

이제 모든 준비가 끝났습니다. -m 1 옵션과 함께 revert 명령을 실행합니다.

unfold_lessbash
content_copyterminal
# git revert -m <주축_부모_번호> <되돌릴_머지_커밋>
git revert -m 1 a1b2c3d

이 명령을 실행하면, Git은 feature/login 브랜치를 통해 추가되었던 모든 변경 사항을 제거하는 새로운 커밋을 생성합니다. 커밋 메시지 편집기가 나타나면, 왜 되돌렸는지 명확한 이유를 적고 저장하면 작업 완료!

git log를 다시 확인해보면, Revert "Merge branch 'feature/login'" 이라는 새로운 커밋이 main 브랜치에 안전하게 추가된 것을 볼 수 있습니다.

⚠️ 가장 중요한 주의사항: 되돌린 브랜치, 다시 머지할 때

"좋아, 버그는 해결했고... 이제 feature/login 브랜치를 수정해서 다시 머지해야지!" 라고 생각하셨나요? 여기서 아주 중요한 함정이 있습니다.

문제점: 한 번 revert된 머지에 포함됐던 커밋들(6a5b4c3, 7f8e9d0)은 main 브랜치 입장에서 "이미 히스토리에 포함되었다가 되돌려진 내역"으로 기록됩니다. 그래서 나중에 수정된 feature/login 브랜치를 다시 머지하려고 하면, Git은 이전에 되돌렸던 커밋들의 변경 사항을 누락시킨 채 머지합니다.

해결책: "Revert를 Revert하라!" 이 문제를 해결하는 가장 깔끔한 방법은, 원래의 feature 브랜치를 다시 머지하기 전에, 이전에 했던 revert 커밋을 또다시 revert 해주는 것입니다.

  1. main 브랜치에서 feature/login 머지를 되돌린 revert 커밋을 찾습니다. (예: r1e2v3t)
  2. 이 revert 커밋을 다시 revert 합니다.
    unfold_lessbash
    content_copyterminal
    # Revert 커밋을 되돌려서, 과거의 feature/login 변경사항을 다시 복원
    git revert r1e2v3t
    
  3. 이제 main 브랜치는 feature/login의 변경 사항을 다시 받아들일 준비가 되었습니다.
  4. 수정된 feature/login 브랜치를 main에 머지합니다.
    unfold_lessbash
    content_copyterminal
    git merge feature/login
    
    이제 모든 변경 사항이 정상적으로 포함됩니다!

마치며: 오늘의 핵심 정리

git revert는 공유 브랜치의 히스토리를 깔끔하고 안전하게 관리할 수 있는 강력한 도구입니다. 머지 커밋을 되돌릴 때 오늘 배운 내용을 꼭 기억하세요!

  • 머지 커밋 Revert: git revert -m 1 <머지_커밋_해시>
  • 안전성: revert는 히스토리를 지우지 않고, 변경을 되돌리는 새로운 커밋을 생성합니다.
  • 재머지(Re-merge) 시: 반드시 "Revert 커밋을 다시 Revert" 하는 패턴을 기억하세요.

이제 실수로 잘못된 브랜치를 머지해도 당황하지 않고, 전문가처럼 우아하게 대처할 수 있겠죠?

Happy Coding! 🚀

React 개발을 하다 보면, 개발 환경, 로컬 환경, 운영 환경에 따라 API 주소나 각종 설정 값들이 달라져야 하는 경우가 많습니다. 이런 설정들을 코드에 직접 박아 넣으면 관리가 엉망이 되겠죠? 😥

그래서 오늘은 React 앱에서 env-cmd라는 멋진 도구를 사용하여 환경변수를 깔끔하게 관리하는 방법을 알려드리겠습니다! 마치 마법처럼, 설정 파일만 바꿔주면 앱이 알아서 환경에 맞는 설정을 적용해 줄 거예요. ✨

 

 

1단계: env-cmd 설치하기

먼저, 프로젝트에 env-cmd를 설치해야 합니다. 터미널을 열고 아래 명령어를 입력하세요:

npm install env-cmd --save

 

또는

yarn add env-cmd

 

 

이 명령어는 env-cmd를 프로젝트의 개발 의존성(devDependencies)으로 설치합니다. package.json 파일을 열어보시면 devDependencies 항목에 env-cmd가 추가된 것을 확인할 수 있습니다.

 

 

 

2단계: 환경변수 파일 만들기

이제 환경별로 설정 값들을 담을 파일을 만들어야 합니다. 프로젝트 루트 디렉토리에 다음 파일들을 만들어주세요:

  • .env.development: 개발 환경 설정
  • .env.local: 로컬 환경 설정 (개인 개발용)
  • .env.production: 운영 환경 설정 (실제 서비스용)

각 파일 안에는 환경변수들을 다음과 같은 형식으로 작성합니다.

REACT_APP_API_URL=https://dev.example.com/api
REACT_APP_FEATURE_ENABLED=true

주의! React 앱에서 사용하는 환경변수는 반드시 REACT_APP_으로 시작해야 합니다.

 

 

3단계: package.json 스크립트 수정하기

 

이제 package.json 파일의 scripts 부분을 수정하여 env-cmd를 사용하도록 설정합니다. 기존의 start 스크립트를 다음과 같이 변경하세요:

 
"scripts": {
    "start": "env-cmd -f .env.development react-scripts start",
    "start:local": "env-cmd -f .env.local react-scripts start",
    "start:prod": "env-cmd -f .env.production react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },

 

 

스크립트 설정, 왜 이렇게 해야 할까요? 🤔

 

  • env-cmd: 이 명령어는 환경변수를 설정하고 다른 명령어를 실행하는 역할을 합니다.
  • -f: 이 옵션은 env-cmd에게 어떤 환경변수 파일을 사용할지 알려줍니다. -f .env.development는 .env.development 파일을 사용하겠다는 의미입니다.
  • react-scripts start: 이 명령어는 React 앱을 개발 모드로 실행합니다.

순서가 중요한 이유: env-cmd가 먼저 실행되어 환경변수를 설정해야 react-scripts start가 환경변수를 읽어서 앱에 적용할 수 있습니다. 만약 순서가 바뀌면 react-scripts start는 환경변수를 찾지 못해 제대로 동작하지 않을 수 있습니다.

이제 터미널에서 다음과 같이 명령어를 실행하여 앱을 실행할 수 있습니다:

  • 개발 환경: npm start 또는 yarn start
  • 로컬 환경: npm run start:local 또는 yarn start:local
  • 운영 환경: npm run start:prod 또는 yarn start:prod

 

 

4단계: 코드에서 환경변수 사용하기

 

React 컴포넌트 안에서 환경변수를 사용하려면 process.env 객체를 사용하면 됩니다. 예를 들어, API 주소를 사용하려면 다음과 같이 할 수 있습니다:

 
const apiUrl = process.env.REACT_APP_API_URL;

fetch(apiUrl + '/users')
  .then(response => response.json())
  .then(data => console.log(data));

 

-f 옵션, 왜 꼭 써야 할까요? 🤔

 

env-cmd는 기본적으로 .env 파일을 찾아서 환경변수를 설정합니다. 하지만 여러 환경에 따라 다른 설정 파일을 사용해야 하므로, -f 옵션을 사용하여 사용할 환경변수 파일을 명시적으로 지정해야 합니다. -f 옵션이 없다면 env-cmd는 어떤 파일을 사용해야 할지 알 수 없어 기본 설정으로 동작하거나 오류가 발생할 수 있습니다.

 

장점

  • 환경별 설정 분리: 환경변수 파일을 사용하여 각 환경에 맞는 설정을 쉽게 관리할 수 있습니다.
  • 코드 유지보수성 향상: 설정 값들이 코드에 직접 박혀 있지 않으므로 코드가 더 깔끔하고 유지보수하기 쉬워집니다.
  • 보안 강화: API 키나 비밀번호와 같은 중요한 정보는 코드에 직접 저장하지 않고 환경변수로 관리하여 보안을 강화할 수 있습니다.

 

마치며

이제 env-cmd를 사용하여 React 앱의 환경변수를 체계적으로 관리하는 방법을 알게 되었습니다. 이 방법을 사용하면 개발, 로컬, 운영 환경에 따라 설정 값을 쉽게 변경할 수 있고, 코드의 유지보수성과 보안성도 높일 수 있습니다.

 

 

오늘은 개발자들의 생산성을 확 높여주는 마법, CI/CD에 대해 이야기해보려고 합니다. 매번 코드를 수정하고, 빌드하고, 서버에 접속해서 파일을 올리는 반복 작업에 지치셨나요? CI/CD는 이런 과정을 자동화해서 우리가 더 중요한 일에 집중할 수 있게 도와줍니다.

 

🤔 CI/CD, 도대체 뭔가요?

CI/CD는 두 가지 개념의 조합입니다.

 

CI (Continuous Integration, 지속적 통합):

  • 핵심: 개발자들이 각자 작업한 코드를 자주 중앙 저장소(GitLab의 main, stage, prod 브랜치 같은 곳)에 합치는(merge) 것입니다.
  • 자동화: 코드가 합쳐질 때마다 자동으로 빌드되고 테스트됩니다.
  • 장점: 코드 충돌이나 숨어있는 버그를 조기에 발견하고 수정할 수 있어, 전체 프로젝트의 안정성이 높아집니다. "어? 내 컴퓨터에선 잘 됐는데!" 같은 상황이 줄어들죠.
  • YAML 파일에서: build-job, prod-build-job 부분이 바로 CI 단계입니다. 코드가 특정 브랜치로 푸시(push)되면, GitLab이 알아서 Node.js 환경을 만들고(image: node:18-alpine3.21), 필요한 라이브러리를 설치(npm install)한 뒤, 프론트엔드 코드를 웹 브라우저가 이해할 수 있는 파일들(HTML, CSS, JS)로 변환(npm run build...)합니다.

 

CD (Continuous Delivery/Deployment, 지속적 제공/배포):

  • 핵심: CI 단계를 성공적으로 통과한 결과물(빌드된 파일)을 자동으로 실제 사용자가 접속하는 서버 환경까지 전달하는 과정입니다.
  • 종류:
    • 지속적 제공(Delivery): 운영 서버 배포 직전까지만 자동화하고, 실제 배포는 사람이 버튼을 눌러 승인하는 방식입니다. (안정성이 중요할 때!)
    • 지속적 배포(Deployment): 모든 과정이 자동으로 진행되어, 코드가 합쳐지고 테스트를 통과하면 즉시 운영 서버에 반영되는 방식입니다. (빠른 업데이트가 중요할 때!)
  • YAML 파일에서: deploy-job, prod-deploy-job 부분이 CD 단계입니다. CI에서 만들어진 build 폴더 안의 파일들을 AWS S3라는 저장소로 복사(aws s3 sync)해서 웹사이트를 업데이트합니다.
    • prod-build-job과 prod-deploy-job에 when: manual 보이시죠? 이건 "운영(prod) 환경 빌드와 배포는 자동으로 하지 말고, 사람이 GitLab 화면에서 직접 실행 버튼을 눌러줘!"라는 뜻입니다. 즉, 지속적 제공(Delivery) 방식을 사용하고 있는 거죠.
    • main(개발)이나 stage(테스트) 브랜치는 when: manual이 없으니, 코드 변경 시 자동으로 빌드되고 배포될 가능성이 높습니다 (설정에 따라 **지속적 배포(Deployment)**에 가깝게 운영).

 

🧐 잠깐, image: docker:latest는 뭐죠?

파일 상단과 deploy-job 등에서 image: docker:latest를 사용하는 것을 볼 수 있습니다. 이는 해당 CI/CD 작업을 수행할 때 기본적으로 도커(Docker) 명령어를 사용할 수 있는 환경을 사용하겠다는 의미입니다. deploy-job에서는 이 도커 환경 안에서 apk add 명령어로 AWS CLI 도구를 설치하고 S3 및 CloudFront 명령어를 실행합니다.

 

 

✨ 캐시 무효화(Invalidation), 왜 필요할까요? (feat. CloudFront)

deploy-job과 prod-deploy-job을 보면 aws cloudfront create-invalidation이라는 명령어가 있습니다. 이게 바로 '무효화' 부분인데요, 왜 필요할까요?

  1. S3 + CloudFront = 빠른 웹사이트: 이 프로젝트는 빌드된 웹사이트 파일(HTML, CSS, JS 등)을 AWS S3라는 파일 저장소에 저장합니다. 그리고 AWS CloudFront라는 CDN(콘텐츠 전송 네트워크) 서비스를 이용해 사용자에게 파일을 더 빠르게 전달합니다.
  2. CDN의 똑똑한 캐싱: CloudFront는 전 세계 여러 지역에 있는 서버(엣지 로케이션)에 우리 웹사이트 파일의 복사본을 미리 저장(캐싱)해 둡니다. 사용자가 접속하면 가장 가까운 서버에서 파일을 받으니 로딩 속도가 빨라지는 원리죠.
  3. 캐시의 함정: 그런데 문제가 있습니다! S3에 새 버전의 파일을 업로드(배포)해도, CloudFront 서버에는 아직 이전 버전의 파일이 캐시되어 있을 수 있습니다. 그러면 사용자는 분명 배포가 끝났는데도 옛날 화면을 보게 되는 거죠. 😱
  4. 무효화 마법 주문!: aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths "/*" 명령어는 CloudFront에게 "야, $DISTRIBUTION_ID 배포판에 캐시된 모든 파일(/*) 이제 유효하지 않아! 새로 가져가!" 라고 알려주는 역할을 합니다.
  5. 결과: 이 명령어를 실행하면, CloudFront는 캐시된 옛날 파일 대신 원본 저장소(S3)에서 최신 파일을 가져와 사용자에게 보여주고, 그 최신 파일을 다시 캐싱합니다. 덕분에 사용자는 배포 즉시 최신 웹사이트를 볼 수 있게 됩니다!
  6. "완료될 때까지 기다려!" 루프: while true; do ... done 부분은 CloudFront가 무효화 작업을 완료할 때까지 (상태가 Completed가 될 때까지) 10초마다 확인하는 코드입니다. 무효화는 즉시 끝나지 않기 때문에, 완료를 확인하고 파이프라인을 성공적으로 마무리하기 위한 장치입니다.


정리: 이 YAML 파일은 무슨 일을 하나요?

이 .gitlab-ci.yml 파일은 GitLab CI/CD를 통해 다음과 같은 멋진 자동화 흐름을 만듭니다.

  1. 개발/테스트/운영 브랜치(main, stage, prod)에 코드 변경이 감지되면:
    • (CI) 자동으로 해당 환경에 맞는 프론트엔드 코드를 빌드합니다. (단, prod는 수동 실행)
    • (CD) 빌드된 결과물을 각 환경의 AWS S3 버킷으로 업로드(배포)합니다. (단, prod는 수동 실행)
    • (CD - 무효화) AWS CloudFront 캐시를 무효화하여 사용자가 즉시 최신 버전을 볼 수 있도록 합니다.
  2. 안정성 확보: 운영(prod) 환경 배포는 수동으로 실행하도록 하여(Continuous Delivery), 실수로 잘못된 코드가 배포되는 것을 방지합니다.

이제 CI/CD가 어떻게 돌아가는지, 그리고 왜 캐시 무효화가 중요한지 감이 오시나요? 이 자동화 파이프라인 덕분에 개발팀은 코드 작성에 더 집중하고, 사용자는 더 빠르고 안정적으로 새로운 기능을 만날 수 있게 됩니다! ✨

  • 핵심: 데이터베이스와 소통하기 위한 언어. 목적에 따라 크게 3가지(+1가지)로 나뉨.
    • 🏗️ DDL (Data Definition Language - 데이터 정의어)
      • 별명: 구조 정의 언어, 뼈대 만들기
      • 역할: 데이터베이스 객체(테이블, 뷰, 인덱스 등)의 구조를 정의(CREATE), 변경(ALTER), 삭제(DROP).
      • 주요 명령어: CREATE, ALTER, DROP, TRUNCATE
      • 암기 포인트: DDL = 구조 (정의/변경/삭제) / CREATE, ALTER, DROP
    • ✍️ DML (Data Manipulation Language - 데이터 조작어)
      • 별명: 내용 조작 언어, 내용 채우기/바꾸기
      • 역할: 테이블 내의 데이터를 조회(SELECT), 삽입(INSERT), 수정(UPDATE), 삭제(DELETE).
      • 주요 명령어: SELECT, INSERT, UPDATE, DELETE
      • 암기 포인트: DML = 데이터 (조회/삽입/수정/삭제) / SELECT, INSERT, UPDATE, DELETE
    • 👮‍♂️ DCL (Data Control Language - 데이터 제어어)
      • 별명: 권한 관리 언어
      • 역할: 데이터베이스 사용자에게 권한을 부여(GRANT)하거나 회수(REVOKE). 데이터 접근 제어.
      • 주요 명령어: GRANT, REVOKE
      • 암기 포인트: DCL = 권한 (부여/회수) / GRANT, REVOKE
    • (추가) ✅ TCL (Transaction Control Language - 트랜잭션 제어어)
      • 별명: 작업 확정/취소 언어
      • 역할: DML 작업들을 하나의 논리적 단위(트랜잭션)로 묶어 확정(COMMIT)하거나 취소(ROLLBACK).
      • 주요 명령어: COMMIT, ROLLBACK, SAVEPOINT
      • 암기 포인트: TCL = 트랜잭션 (확정/취소) / COMMIT, ROLLBACK

7계층: 응용 계층 (Application Layer) 💻 사용자 접점

  • 역할: 사용자가 네트워크 자원에 접근하는 방법을 제공 (우리가 쓰는 프로그램들: 웹 브라우저, 이메일, 파일 전송 등). 사용자와 가장 가까움.
  • 핵심: 서비스 제공, 사용자 인터페이스
  • 전송 단위: 데이터 (Data) / 메시지 (Message)
  • 암기 포인트: 앱 (Application) / 사용자 서비스

6계층: 표현 계층 (Presentation Layer) 🎭 번역가 / 포장 전문가

  • 역할: 데이터의 형식(포맷)을 결정. 데이터를 안전하고 효율적으로 보내기 위해 압축하거나 암호화/복호화 진행.
  • 핵심: 데이터 변환, 암호화, 압축
  • 전송 단위: 데이터 (Data)
  • 암기 포인트: 표현 (Presentation) / 번역, 암호화, 압축

5계층: 세션 계층 (Session Layer) 📞 대화 관리자

  • 역할: 양쪽 컴퓨터 간의 대화(세션)를 시작하고, 관리하고, 끝내는 역할. 동기화 지점 설정 (데이터 전송 중 오류 시 복구 지점).
  • 핵심: 연결 설정/종료, 동기화
  • 전송 단위: 데이터 (Data)
  • 암기 포인트: 세션 (Session) / 대화 시작/종료

4계층: 전송 계층 (Transport Layer) 🚚 택배 기사 (신뢰성 담당)

  • 역할: 데이터를 보낼 양 끝단(End-to-End) 간의 신뢰성 있는 데이터 전송 담당. 데이터를 적절한 크기로 나누고(세분화), 재조립. 오류 제어, 흐름 제어. (TCP: 신뢰성, UDP: 속도)
  • 핵심: 신뢰성, 포트 번호, 데이터 분할/재조립
  • 전송 단위: 세그먼트 (Segment - TCP) / 데이터그램 (Datagram - UDP)
  • 암기 포인트: 전송 (Transport) / 신뢰성(TCP), 포트, 분할

3계층: 네트워크 계층 (Network Layer) 🗺️ 네비게이터 / 길 찾기 전문가

  • 역할: 데이터를 목적지까지 가장 안전하고 빠른 길(경로)로 찾아주는 역할. 논리적 주소(IP 주소) 사용. 라우팅 담당.
  • 핵심: 경로 설정(라우팅), IP 주소
  • 전송 단위: 패킷 (Packet)
  • 암기 포인트: 네트워크 (Network) / 길 찾기(라우팅), IP 주소

2계층: 데이터 링크 계층 (Data Link Layer) 🔗 옆집 배달부 / 주소 확인 (MAC)

  • 역할: 바로 옆에 연결된(인접한) 장비 간의 데이터 전송 담당. 물리적 주소(MAC 주소) 사용. 데이터에 오류가 있는지 확인.
  • 핵심: 물리적 주소(MAC), 오류 검출, 프레임(데이터 묶음)
  • 전송 단위: 프레임 (Frame)
  • 암기 포인트: 데이터 링크 (Data Link) / 옆집 전달, MAC 주소, 오류 확인

1계층: 물리 계층 (Physical Layer) 🔌 전선 / 전기 신호

  • 역할: 데이터를 전기 신호(0과 1)로 바꿔서 케이블(전선)을 통해 실제로 전송. 하드웨어(케이블, 허브 등) 관련.
  • 핵심: 전기 신호 전송, 케이블, 하드웨어
  • 전송 단위: 비트 (Bit)
  • 암기 포인트: 물리 (Physical) / 전선, 전기 신호(0, 1)

1. 스푸핑 (Spoofing)

  • 별명: 🎭 가면 쓰기신분 위장
  • 핵심: 공격자가 자신의 정체를 속이고 다른 사람이나 시스템인 척 위장하는 것. IP 주소, MAC 주소, 이메일 주소 등을 속여서 신뢰 관계를 악용하거나 접근 권한을 얻으려 함. (예: IP 스푸핑, ARP 스푸핑, DNS 스푸핑, 이메일 스푸핑)
  • 목표: 접근 제어 우회, 중간자 공격 준비, 피싱 등
  • 암기 포인트: 스푸핑 = 위장 / 속임수 / IP, MAC, DNS 등 / 신분 도용

2. 스니핑 (Sniffing)

  • 별명: 👂 몰래 엿듣기패킷 도청
  • 핵심: 네트워크 상을 지나다니는 데이터 패킷을 몰래 엿듣는 행위. 특히 암호화되지 않은 통신(평문 통신)에서 ID, 비밀번호 등의 중요 정보를 쉽게 탈취 가능.
  • 목표: 정보 도청 (특히 계정 정보)
  • 암기 포인트: 스니핑 = 엿듣기 / 도청 / 패킷 / 네트워크 / 비암호화 정보 탈취

3. 서비스 거부 공격 (DoS - Denial of Service)

  • 별명: 🚫 문 앞 가로막기업무 방해
  • 핵심: 특정 서버나 네트워크 자원을 고갈시켜 정상적인 사용자가 서비스를 이용하지 못하게 방해하는 공격. (혼자서 공격)
  • 목표: 서비스 마비, 서비스 중단
  • 암기 포인트: DoS = 서비스 거부 / 자원 고갈 / 단일 공격자

4. 분산 서비스 거부 공격 (DDoS - Distributed DoS)

  • 별명: 🚦 떼로 몰려와 길 막기
  • 핵심: 여러 대의 감염된 PC(좀비 PC, 봇넷)를 이용하여 DoS 공격을 수행. 공격 규모가 훨씬 크고 방어가 더 어려움.
  • 목표: 서비스 마비 (대규모)
  • 암기 포인트: DDoS = 분산 공격 / 좀비 PC / 대량 트래픽 / 서비스 마비
    • (참고: 스머핑, Ping of Death, Land Attack, Teardrop 등은 DoS/DDoS의 구체적인 방법 중 하나)

5. Ping of Death (죽음의 핑)

  • 별명: 💀 거대 핑 공격
  • 핵심: 정상적인 ICMP Ping 패킷의 최대 크기(65,535 바이트)보다 큰 패킷을 조각내어 보냄. 수신 시스템이 재조립 과정에서 오류/다운됨. (구형 시스템 대상)
  • 목표: 서비스 거부 (DoS)
  • 암기 포인트: Ping of Death = 큰 Ping / ICMP / 조각화 / 재조립 오류 / DoS

6. Land Attack

  • 별명: ↔️ 자기 자신에게 보내는 편지
  • 핵심: 패킷의 출발지 IP/Port와 목적지 IP/Port를 동일하게 설정하여 전송. 시스템이 자기 자신에게 계속 응답하려다 루프에 빠져 다운됨.
  • 목표: 서비스 거부 (DoS)
  • 암기 포인트: Land Attack = 출발지=목적지 / IP/Port 동일 / 루프 / DoS

7. Teardrop Attack

  • 별명: 💧 조각난 눈물재조립 방해
  • 핵심: IP 패킷 조각(Fragment)의 순서나 크기 정보(Offset)를 비정상적으로 조작하여 전송. 수신 시스템이 재조립 중 오류 발생/다운됨.
  • 목표: 서비스 거부 (DoS)
  • 암기 포인트: Teardrop = IP 조각(Fragment) / 재조립 오류 / 오프셋 조작 / DoS

8. 버퍼 오버플로우 (Buffer Overflow)

  • 별명: 📥 넘치는 상자메모리 침범
  • 핵심: 프로그램이 처리할 수 있는 양보다 많은 데이터를 메모리 공간(버퍼)에 밀어 넣어 넘치게 함. 넘친 데이터 영역에 악성 코드를 심어 실행시켜 시스템 제어권 획득 시도.
  • 목표: 시스템 제어권 획득, 악성 코드 실행
  • 암기 포인트: 버퍼 오버플로우 = 메모리 / 버퍼 / 넘침 / 데이터 초과 / 코드 실행 / 취약점

9. SQL 인젝션 (SQL Injection)

  • 별명: 💉 데이터베이스 명령 주입
  • 핵심: 웹사이트의 입력 칸에 악의적인 SQL 명령어를 삽입하여 데이터베이스를 비정상적으로 조작하거나 정보를 탈취.
  • 목표: 데이터베이스 정보 유출/조작/삭제, 인증 우회
  • 암기 포인트: SQL 인젝션 = DB 명령 주입 / 웹 입력 값 / 데이터 유출/조작

10. 크로스 사이트 스크립팅 (XSS - Cross-Site Scripting)

  • 별명: 📝 웹사이트에 악성 낙서하기
  • 핵심: 공격자가 웹사이트(게시판 등)에 악성 스크립트를 삽입. 다른 사용자가 해당 페이지를 볼 때, 그 사용자 브라우저에서 스크립트가 실행되어 쿠키 정보 탈취 등을 시도.
  • 목표: 사용자 세션 탈취 (쿠키 등), 악성 사이트 리다이렉션
  • 암기 포인트: XSS = 악성 스크립트 주입 / 웹사이트 / 사용자 브라우저 공격

11. 악성코드 (Malware) / 크라임웨어 (Crimeware)

  • 별명: 🦠 나쁜 프로그램 종합세트 / 💰 범죄 도구 세트
  • 핵심: 시스템에 해를 끼치거나 정보를 빼가기 위한 모든 악의적 소프트웨어. 크라임웨어는 특히 금전적 이득을 목적으로 하는 악성코드(랜섬웨어, 뱅킹 트로이 목마 등)를 지칭.
    • 바이러스 (Virus): 정상 파일에 기생, 파일 실행 시 동작.
    • 웜 (Worm): 자가 복제 및 스스로 네트워크 전파.
    • 트로이 목마 (Trojan Horse): 정상 프로그램으로 위장, 사용자 실행 유도.
    • 랜섬웨어 (Ransomware): 파일 암호화 후 금전 요구.
    • 스파이웨어 (Spyware): 사용자 정보 몰래 수집.
    • 애드웨어 (Adware): 광고 노출 목적.
  • 목표: 정보 유출, 시스템 파괴, 금전 갈취, 좀비 PC화 등 다양.
  • 암기 포인트: 악성코드/크라임웨어 = 악의적 프로그램 / 바이러스, 웜, 트로이 등 / 침투/피해 유발 (금전 목적)

12. 루트킷 (Rootkit)

  • 별명: 👻 숨바꼭질 전문가은신술 키트
  • 핵심: 시스템 침투 후, 자신의 존재나 악성 행위를 숨기기 위해 시스템 핵심 기능(OS 커널 등)을 조작/변조하는 도구 모음. 탐지를 매우 어렵게 만듦.
  • 목표: 침입 사실 은폐, 지속적인 접근 유지, 탐지 회피
  • 암기 포인트: 루트킷 = 은폐 / 숨김 / 시스템 조작 / 커널 레벨 / 탐지 회피

13. 제로데이 공격 (Zero-Day Attack)

  • 별명: 🚀 패치 없는 날 공격신상 취약점 공격
  • 핵심: 소프트웨어 취약점이 발견되었지만 아직 보안 업데이트(패치)가 만들어지지 않은 상태(Zero-Day)에서 해당 취약점을 이용하는 공격. 방어가 매우 어려움.
  • 목표: 시스템 침투, 권한 상승 등 (취약점에 따라 다름)
  • 암기 포인트: 제로데이 = 패치 없음 / 알려지지 않은 취약점 / 신속 공격

 

 

 

 

React 개발을 하다 보면 useCallback이라는 훅을 심심치 않게 만나게 됩니다. "함수를 메모이제이션한다"는데, 이걸 왜 써야 하고 언제 써야 효과적일까요? 오늘은 useCallback의 필요성과 사용법을 쉽고 명확하게 알아보겠습니다.

 

함수가 새로 태어난다고? React 렌더링의 비밀

 

먼저 React 컴포넌트가 어떻게 동작하는지 떠올려 봅시다. 컴포넌트는 자신의 상태(state)나 부모로부터 받은 속성(props)이 변경되면 다시 렌더링됩니다. 이때 중요한 점은, 컴포넌트 함수 본문 전체가 다시 실행된다는 것입니다. 즉, 컴포넌트 내부에 정의된 함수들은 기본적으로 렌더링될 때마다 새로 만들어집니다.

function MyComponent() {
  const [count, setCount] = useState(0);

  // MyComponent가 리렌더링될 때마다 이 함수는 '새로운' 함수가 됩니다.
  const handleClick = () => {
    console.log("클릭!");
  };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>카운트 증가</button>
      {/* 자식 컴포넌트에 함수를 props로 전달 */}
      <MyButton onClick={handleClick} />
    </div>
  );
}

// React.memo로 최적화된 자식 컴포넌트
const MyButton = React.memo(({ onClick }) => {
  console.log("자식 버튼 리렌더링!");
  return <button onClick={onClick}>자식 버튼</button>;
});

 

위 코드에서 "카운트 증가" 버튼을 누르면 MyComponent가 리렌더링되고, handleClick 함수도 새로 만들어집니다.

 

 

그래서 뭐가 문제인데? 불필요한 리렌더링!

 

MyButton 컴포넌트는 React.memo로 감싸져 있습니다. React.memo는 props가 이전 렌더링과 정확히 같다면(참조 동일성) 리렌더링을 건너뛰는 똑똑한 기능이죠.

하지만 handleClick 함수는 렌더링마다 새로 만들어지기 때문에, MyButton 입장에서는 onClick prop이 계속 **다른 값(다른 함수의 참조)**으로 전달되는 셈입니다. JavaScript에서 함수는 객체이고, 새로 만들어진 함수는 이전 함수와 내용이 똑같아도 다른 객체니까요.

결국, MyButton은 실제로는 아무것도 변하지 않았는데도 부모(MyComponent)가 리렌더링될 때마다 불필요하게 같이 리렌더링됩니다. (콘솔에 "자식 버튼 리렌더링!"이 계속 찍히는 것을 확인해보세요.)

 

구세주 등장! useCallback

 

useCallback은 바로 이 문제를 해결하기 위해 탄생했습니다.

  • useCallback(fn, deps)은 함수 fn 자체를 메모이제이션합니다.
  • 의존성 배열 deps 안의 값이 변경되지 않는 한, 함수를 새로 만들지 않고 이전에 만들었던 함수의 참조를 그대로 반환합니다.
function MyComponent() {
  const [count, setCount] = useState(0);

  // useCallback으로 handleClick 함수를 메모이제이션
  // 의존성 배열이 비어있으므로([]) 이 함수는 처음 마운트될 때만 생성되고,
  // 이후에는 계속 같은 참조를 유지합니다.
  const handleClick = useCallback(() => {
    console.log("클릭!");
    // 만약 함수 안에서 count 같은 상태를 사용한다면 deps에 넣어줘야 합니다!
    // 예: console.log(count); -> useCallback(..., [count]);
  }, []); // 의존성 배열

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>카운트 증가</button>
      <MyButton onClick={handleClick} /> {/* 이제 동일한 참조가 전달됨 */}
    </div>
  );
}

// 자식 컴포넌트 (React.memo 사용)
const MyButton = React.memo(({ onClick }) => {
  console.log("자식 버튼 리렌더링!");
  return <button onClick={onClick}>자식 버튼</button>;
});

 

이제 "카운트 증가" 버튼을 눌러도 handleClick 함수의 참조는 동일하게 유지됩니다. React.memo는 onClick prop이 변경되지 않았다고 판단하고, MyButton은 더 이상 불필요하게 리렌더링되지 않습니다! (콘솔에 "자식 버튼 리렌더링!"이 찍히지 않아요!)

 

언제 useCallback을 써야 할까?

 

  1. React.memo로 최적화된 자식 컴포넌트에 함수를 props로 전달할 때: 자식 컴포넌트의 불필요한 리렌더링을 막아 성능을 개선할 수 있습니다. (가장 흔한 사용 사례!)
  2. 함수를 다른 Hook(useEffect, useMemo)의 의존성 배열에 포함시킬 때: 함수의 참조가 안정적이지 않으면 해당 Hook이 불필요하게 계속 실행될 수 있습니다. useCallback으로 함수 참조를 안정화시키면 이를 방지할 수 있습니다. (위의 시간 버그 해결 사례에서도 사용되었죠!)

 

주의! 남용은 금물

useCallback 자체도 함수를 비교하고 메모리에 저장하는 약간의 비용이 듭니다. 따라서 모든 함수를 useCallback으로 감쌀 필요는 없습니다. 위에서 설명한 것처럼 **참조 동일성이 꼭 필요한 경우(성능 최적화가 필요한 경우)**에 사용하는 것이 좋습니다.

 

마무리

useCallback은 React 애플리케이션의 성능을 최적화하는 데 유용한 도구입니다. 함수의 참조 동일성을 유지해야 하는 상황을 잘 파악하고 적재적소에 활용한다면, 더 부드럽고 효율적인 사용자 경험을 제공할 수 있을 것입니다.

+ Recent posts