Coding Garden - Theory

김기훈·2026년 3월 26일

개인프로젝트

목록 보기
9/10
post-thumbnail

개인 프로젝트를 진행하면서 사용하거나 알게 된 이론


ORM 📌

Q 객체

  • Django ORM에서 복잡한 데이터베이스 쿼리를 작성할 때 사용하는 강력한 도구

    • 기본적으로 Django의 filter()exclude() 메서드에
      • 여러 개의 키워드 인자를 넘기면 AND(&) 조건으로만 묶임
    • 하지만 데이터베이스를 다루다 보면 OR(|) 이나 NOT(~) 같은 조건이 필요할 때가 많은데
      • 이때 Q 객체 를 사용

언제 사용?

  • OR 조건이 필요할 때

    • "제목에 'Django'가 포함되어 있거나(OR) 조회수가 100 이상인 글"
  • NOT 조건이 필요할 때

    • "작성자가 'admin'이 아닌(NOT) 글"
  • 복잡한 논리 연산이 필요할 때

    • "(A OR B) AND (C OR NOT D)" 와 같은 다중 조건
  • 동적 쿼리 생성

    • 조건문(if/else)에 따라 쿼리 조건을 동적으로 이어 붙여야 할

기본 사용법 및 연산자

  • import

    • django.db.models 에서 임포트하여 사용하며, 파이썬의 비트 연산자를 활용하여 조건을 결합
from django.db.models import Q
from myapp.models import Article
  • OR 연산 - | (파이프)

    • filter() 로는 불가능한 OR 조건 검색
  • NOT 연산 - ~ (틸드)

    • exclude()를 사용할 수도 있지만, 복잡한 쿼리 내에서 특정 부분만 부정하고 싶을 때 유용
  • AND 연산 - & (앰퍼샌드)

    • 복합 연산 (AND, OR, NOT 조합) - 괄호를 사용하여 연산의 우선순위를 명확히 지정 가능
# 제목에 'Django'가 포함되어 있거나, 조회수가 100 이상인 게시글 검색
articles = Article.objects.filter(
    Q(title__icontains='Django') | Q(views__gte=100)
)

# 제목에 'Python'이 포함되지 않은 게시글 검색
articles = Article.objects.filter(
    ~Q(title__icontains='Python')
)

# (제목에 'Django' 또는 'Python'이 포함) AND (작성자가 'admin'이 아님)
articles = Article.objects.filter(
    (Q(title__icontains='Django') | Q(title__icontains='Python')) & 
    ~Q(author__username='admin')
)

동적 쿼리 만들기

  • 검색 필터처럼 사용자의 입력에 따라 조건이 달라지는 경우
    • 비어있는 Q 객체를 생성하고 조건을 동적으로 추가 가능
search_keyword = "Django"
min_views = 50

# 빈 Q 객체 생성
query = Q()

if search_keyword:
    # OR 조건으로 추가
    query |= Q(title__icontains=search_keyword)
    query |= Q(content__icontains=search_keyword)

if min_views:
    # AND 조건으로 추가
    query &= Q(views__gte=min_views)

# 최종 쿼리 실행
results = Article.objects.filter(query)

icontains

  • Django ORM에서 텍스트를 검색할 때 사용하는 필드 룩업(Field Lookup) 중 하나

    • 대소문자를 구분하지 않고(Case-insensitive) 특정 문자열이 포함되어 있는지를 확인할 때 사용
  • ex

    • title__icontains='django'
      • 제목 필드 안에 'Django', 'DJANGO', 'django', 'dJaNgO' 등
      • 대소문자 형태와 상관없이 알파벳 배열만 맞으면 모두 검색 결과에 포함시킴

contains

  • 대소문자를 엄격하게 구분

# ex

1. "Hello Django"
2. "Learning django"
3. "DJANGO IS AWESOME"

# 'Django'라는 정확한 대소문자 패턴만 찾습니다. -> 1번("Hello Django")만 검색됨
Article.objects.filter(title__contains='Django')

# 대소문자 상관없이 'django' 철자만 포함되어 있으면 모두 찾음 -> 1번, 2번, 3번 모두 검색됨
Article.objects.filter(title__icontains='django')

.distinct()

  • 쿼리 결과(QuerySet)에서 중복된 데이터를 제거하고 고유한(Unique) 레코드만 반환하도록 하는 메서드

언제 사용?

  • 다른 테이블과 조인(Join)을 통해 필터링하거나, 특정 필드만 추출할 때 중복 데이터가 흔하게 발생함
    • 이때 .distinct()가 필요

예시

- 1. 게시글(Article)과 태그(Tag)가 다대다(M:N) 관계라고 가정
- 2. 'Python'이나 'Django' 태그가 달린 게시글 검색
- 3. 다중 테이블 조인 시 중복 제거

# 만약 1번 게시글에 두 태그가 모두 달려있다면, 1번 게시글은 결과에 2번 중복 출력됨
articles = Article.objects.filter(tags__name__in=['Python', 'Django'])

# 마지막에 distinct()를 붙여서 중복된 1번 게시글을 하나로 병합함
unique_articles = Article.objects.filter(tags__name__in=['Python', 'Django']).distinct()

———————————————————————————————————————————————————————————————————————————————
- 1. 특정 필드값의 중복 제거 
- 2. values와 함께 사용

# 모든 게시글의 '카테고리' 목록을 가져옵니다. ('공지', '일반', '일반', '공지' ...)
categories = Article.objects.values_list('category', flat=True)

# 중복을 제거하여 고유한 카테고리 종류만 가져옵니다. ('공지', '일반')
unique_categories = Article.objects.values_list('category', flat=True).distinct()

주의

  • 성능 문제 (오버헤드)

    • .distinct() 를 사용하면 데이터베이스 내부적으로 데이터를 정렬하고 비교하여
      • 중복을 제거하는 연산 과정을 거침
    • 불필요하게 남용하면 쿼리 속도가 느려질 수 있으므로
      • 조인으로 인해 중복 발생이 확실한 경우에만 사용하는 것이 좋음
  • PostgreSQL 전용 기능 (DISTINCT ON)

    • SQLite나 MySQL 등 대부분의 데이터베이스에서는 .distinct() 괄호 안에 인자를 넣을 수 없으며,
      • 조회된 행 전체의 데이터가 완전히 똑같을 때만 중복으로 간주함
    • 하지만 PostgreSQL을 사용 중에는 괄호 안에 특정 필드명을 지정할 수 있음
      • 참고
        • PostgreSQL에서 특정 필드로 distinct('field_name')을 사용할 때는
        • 반드시 해당 필드가 order_by()의 첫 번째 정렬 기준으로 들어가야 에러가 발생하지 않음
# (PostgreSQL 전용) 'category' 필드를 기준으로 중복 제거 후, 가장 최신 글 1개씩만 남기기
Article.objects.order_by('category', '-created_at').distinct('category')

code 📌

Type Hinting

user = cast(User, request.user)

  • "실행 시점(Runtime)에는 아무 일도 하지 않지만, 개발 시점(Static Analysis)에는 매우 중요한 역할"
    • 타입 힌트 제공 (Type Hinting)

      • Python의 typing.cast 함수는 정적 타입 검사기(Mypy, Pyright 등)와
      • IDE(PyCharm / VSCode)에게 "이 변수(request.user)는 지금부터 무조건 User 클래스의
      • 인스턴스로 취급해줘"라고 강제로 지정하는 것
    • 자동 완성 활성화

      • 일반적으로 프레임워크에서 request.user는 로그인하지 않은 경우
        • AnonymousUser일 수도 있기 때문에
        • IDE가 User 모델에 정의한 필드(예: user.nickname)를 자동으로 추천해주지 못할 때가 많음
        • cast를 쓰면 이때부터 자동 완성이 완벽하게 동작함
  • 주의 ⚠️

    • 타입 안정성을 확보하는 데는 아주 유용하지만, 남용하면 위험

      • cast는 강제로 타입을 규정하는 것이라
        • 실제로 request.user가 익명 사용자(AnonymousUser)인 상태에서
          • 이 코드가 실행되어도 오류를 뱉지 않음
        • 나중에 user.nickname 같은 속성에 접근할 때 비로소 AttributeError가 발생

model 📌

Field

BigIntegerField

  • models.BigIntegerField

    • -922경부터 922경 까지의 숫자를 저장

PositiveIntegerField()

  • models.PositiveIntegerField()

    • 일반적인 크기의 양수 정수를 저장
    • 0부터 2,147,483,647 (약 21억) 까지 저장 가능(보통 4바이트(Byte)를 차지)
    • 숫자가 꽤 커질 수 있는 경우에 사용

PositiveSmallIntegerField()

  • models.PositiveSmallIntegerField()

    • 비교적 작은 크기의 양수 정수를 저장(보통 2바이트(Byte)를 차지)

through

through="tags.PostTag" 옵션

  • Django가 자동으로 숨겨진 중간 테이블을 만들도록 내버려 두지 않고
    • tags 앱에 정의된 PostTag라는 모델을 연결 테이블로 직접 명시해서 사용
# models.py
class Post(TimeStampedModel):
	
    ...
    
    # Tag와의 M:N 관계 (Through 설정)
    tags = models.ManyToManyField(
        "tags.Tag", through="tags.PostTag", related_name="posts"
    )

on_delete

models.SET_NULL

  • on_delete=models.SET_NULL

    • 시리즈를 삭제하더라도 그 안에 속했던 포스트들은 지워지지 않고 단지 '시리즈 없음' 상태로 안전하게 남게 됨
class Post(TimeStampedModel):
    class Visibility(models.TextChoices):
        PUBLIC = "PUBLIC", "전체 공개"
        PRIVATE = "PRIVATE", "비공개"

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="posts"
    )
    series = models.ForeignKey(
        Series, on_delete=models.SET_NULL, null=True, blank=True, related_name="posts"
    )
    series_order = models.PositiveSmallIntegerField(null=True, blank=True)

	...

index

UniqueConstraint

  • UniqueConstraint(fields=["user", "name"])

    • "한 명의 유저는 중복된 이름의 시리즈를 만들 수 없지만 서로 다른 유저는 같은 이름의 시리즈를 가질 수 있다"
  • UniqueConstraint(user, name) 제약 조건오류: IntegrityError

class Series(TimeStampedModel):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="series"
    )
    name = models.CharField(max_length=100)

    class Meta:
        db_table = "series"
        constraints = [
            models.UniqueConstraint(fields=["user", "name"], name="uk_series_user_name")
        ]

Image 📌

S3

  • "용량이 무한대로 늘어나는 인터넷 상의 외장 하드디스크"

    • 사진, 동영상, 텍스트 문서 등 어떤 파일(Object)이든 마음껏 저장하고
    • 고유한 인터넷 주소(URL)를 통해 언제 어디서나 꺼내볼 수 있는 AWS의 대표적인 클라우드 스토리지 서비스

S3를 사용하는 이유

  • 서버의 휘발성 (데이터 증발 방지)

    • 문제

      • 요즘 서버들은 업데이트를 하거나 트래픽이 몰리면 기존 서버를 껐다가 새로운 서버를 켬
      • 만약 서버 내부 폴더에 사용자가 올린 이미지를 저장해 뒀다면,
        • 서버가 재시작되는 순간 사진이 전부 날아가는 대참사 발생 가능
    • S3의 해결

      • 서버(Django)와 저장소(S3)를 완전히 분리
      • 서버가 100번 꺼졌다 켜져도, 사진은 S3라는 튼튼한 금고에 안전하게 보관
  • 서버 확장성 (다중 서버 문제 해결)

    • 문제

      • 블로그가 유명해져서 Django 서버를 3대(A, B, C)로 늘렸다고 가정
      • 유저가 'A 서버'에 접속해서 사진을 올리면 A 서버에만 사진이 저장됨
      • 다음날 유저가 'B 서버'로 접속하게 되면 엑스박스(사진 깨짐) 뜸 (B 서버에는 사진이 없어서)
    • S3의 해결

      • A, B, C 서버 모두 유저가 사진을 올리면 무조건 중앙의 S3로 보냄
      • 유저가 어느 서버로 접속하든 똑같은 S3 URL에서 사진을 불러오기 때문에 엑스박스가 뜨지 않음
  • 비용과 성능의 최적화

    • 서버 컴퓨터(EC2 등)의 하드디스크 용량을 늘리는 것은 비쌈
    • 게다가 이미지를 서빙(전송)하는 데 서버의 CPU와 네트워크를 낭비 가능
    • S3는 쓴 만큼만 돈을 내며(GB당 아주 저렴함)
    • 서버를 거치지 않고 사용자의 브라우저와 S3가 직접 사진 데이터를 주고받게 할 수 있어
      • 서버의 부담을 크게 줄여줌

S3의 3가지 핵심 용어

  • 버킷 (Bucket)

    • 파일을 담는 '최상위 폴더'이자 '프로젝트 단위' (예: my-django-blog-bucket)
    • 전 세계에서 유일한 이름을 가져야 함
  • 객체 (Object)

    • 버킷 안에 저장되는 파일 그 자체 (예: profile_img.png, post_thumbnail.jpg)
    • S3는 폴더라는 개념이 없고, 모든 것이 객체
  • 엔드포인트 (Endpoint URL)

    • 파일에 접근할 수 있는 고유한 인터넷 주소
      • (예: https://my-django-blog-bucket.s3.ap-northeast-2.amazonaws.com/post_thumbnail.jpg)
    • 프론트엔드 HTML의 <img src="...">에 바로 이 주소가 들어가게 됨

presigned_url

  • S3를 훨씬 더 똑똑하고 안전하게 쓰기 위해 AWS가 제공하는 '핵심 기능(기술)'

기존 이미지 업로드 과정

  • 프론트엔드

    • 무거운 고양이 사진(5MB)을 백엔드(Django)로 전송
  • 백엔드

    • 그 무거운 사진을 낑낑대며 받아서 메모리에 올린 뒤, 다시 S3로 전송
  • 문제점

    • 트래픽이 몰리면 백엔드 서버가 이미지 파일들을 옮기느라 뻗어버립니다.
    • 백엔드는 데이터베이스 통신이나 로직 처리를 해야 하는데
    • 단순 '택배 배달부' 역할을 하느라 자원을 다 낭비

Presigned URL 방식

  • 프론트엔드

    • "백엔드야, 나 '고양이.jpg' S3에 올릴 건데 딱 한 번만 쓸 수 있는 주소(출입증) 좀 만들어줘."
  • 백엔드(Django)

    • 자기가 가진 마스터 권한으로 S3에게 물어봅니다.
    • "S3야, 얘한테 딱 5분 동안만 업로드할 수 있는 일회용 주소 하나만 발급해 줘."
    • 이때 S3가 만들어주는 임시 주소가 바로 presigned_url
  • 프론트엔드

    • 백엔드에게 그 presigned_url을 받으면
    • 무거운 고양이 사진을 백엔드가 아닌 S3 주소로 직접 전송해 버립니다.

장점

  • 서버 비용 절감 & 성능 향상

    • 백엔드(Django)는 아주 가벼운 '문자열(URL)'만 하나 만들어주고 끝납니다.
    • 무거운 파일 트래픽은 모두 AWS S3가 감당하므로 백엔드 서버가 쾌적해집니다.
  • 완벽한 보안

    • 프론트엔드(클라이언트) 코드에 AWS 해킹의 주범인 '비밀키(Secret Key)'를 숨겨둘 필요가 없음
    • 백엔드만 비밀키를 가지고 임시 출입증만 발급해 주면 됨

이미지 url 얻기

방법 1. S3 기능 구현

  • 본문(에디터) 입력창에 내가 원하는 이미지를 드래그 앤 드롭으로 끌어다 놓습니다.
  • 코드가 잠시 S3에 업로드를 진행한 뒤, 에디터에 아래와 같은 마크다운 코드가 생성됨
    • ex. ![image](https://내-s3-버킷주소.../어쩌구.png)
    • 여기서 괄호 () 안에 있는 https://... 로 시작하는 주소만 복사

방법 2. 깃허브(GitHub) 이슈 활용하기

  • 내 GitHub 레포지토리 아무 곳이나 들어가서 Issues -> New issue를 누릅니다.
  • 내용(Write) 탭에 원하는 이미지를 마우스로 끌어다 놓습니다(드래그 앤 드롭)
    • ![image](https://github.com/user-attachments/assets/...)
    • 형태로 텍스트가 생김
  • 이슈는 등록하지 않아도 주소는 영구 유지됨

방법 3. 디스코드(Discord) 활용하기

  • 디스코드의 '나와의 채팅방'이나 아무 개인 서버에 이미지를 업로드
  • 올라간 이미지를 클릭하여 크게 띄운 뒤, 우측 하단의 [브라우저에서 열기] 를 누릅니다.
  • 새 인터넷 창이 열리면서 이미지가 보이면
    • 맨 위 인터넷 주소창에 있는 주소를 그대로 복사해서 사용

프론트 📌

Image

Toast UI Editor와 이미지 동작

  • 프론트엔드 (글쓰기)

    • 사용자가 에디터에 사진을 드래그 앤 드롭
  • 가로채기 (Hook)

    • Toast UI Editor가 사진을 화면에 띄우기 전에 만든 함수(addImageBlobHook)가 사진을 가로챔
  • API 통신

    • 프론트엔드가 백엔드(Django)의 이미지 업로드 API로 사진 파일을 보냄
  • 백엔드 (S3/로컬)

    • Django가 사진을 S3(또는 로컬 폴더)에 예쁘게 저장하고
    • 접속 가능한 URL(https://s3.../my-image.jpg)을 프론트엔드에 응답으로 돌려줌
  • 에디터 렌더링

    • 프론트엔드가 받은 URL을 에디터에 ![이미지](URL) 형태로 넣어주면, 마침내 화면에 사진이 뜸

Serializer 📌

Field

PrimaryKeyRelatedField

  • 클라이언트(프론트엔드)와 서버 간에 "ID(숫자)를 주고받으면
    • 백엔드에서 알아서 실제 데이터베이스 객체(인스턴스)로 변환해 주는 마법 같은 번역기" 역할
  • 요청을 받을 때 (Deserialization)

    • 프론트엔드에서 {"series": 5} 처럼 시리즈의 ID(PK) 값만 숫자로 보내면
      • 이 필드가 데이터베이스를 뒤져서 "5번 시리즈 객체"를 찾아옵니다.
      • (만약 5번 시리즈가 DB에 없다면 자동으로 에러를 뱉어냅니다.)
  • 응답을 보낼 때 (Serialization)

    • 반대로 백엔드에서 프론트엔드로 데이터를 줄 때는 무거운 전체 객체 정보 대신
    • 깔끔하게 ID 숫자(5) 로 변환해서 보내줍니다.
  • 사용 안하면?

    • view / service에서 아래와 같은 코드를 추가해야 함
# [최선이 아닌 나쁜 예시] 수동으로 DB를 뒤져야 함
series_id = request.data.get('series_id')
if series_id:
    try:
        series = Series.objects.get(id=series_id) # 직접 찾기
    except Series.DoesNotExist:
        raise ValidationError("존재하지 않는 시리즈입니다.") # 직접 에러 처리

EmailField

  • 이메일 형식이 맞는지 자동으로 검사


옵션

# 클라이언트가 숫자로 된 ID를 보내면, 이를 Series 모델의 객체로 변환해주는 필드입니다.
series = serializers.PrimaryKeyRelatedField(
    
    # 1. queryset: 클라이언트가 보낸 ID(예: 5)가 유효한지 검사할 '탐색 범위'를 지정합니다.
    # "Series 테이블에 존재하는 모든 데이터(all) 중에서 클라이언트가 보낸 ID와 일치하는 것을 찾아라"는 뜻입니다.
    queryset=Series.objects.all(),
    
    # 2. required: 글을 작성할 때 반드시 시리즈를 지정해야 하는지 묻는 옵션입니다.
    # 시리즈 없이 단독으로 포스트를 작성할 수도 있어야 하므로 False로 설정하는 것이 최선입니다.
    required=False,
    
    # 3. allow_null: 클라이언트가 값을 비워서(null) 보내는 것을 허용할지 묻는 옵션입니다.
    # '선택 안 함' 상태를 처리하기 위해 null을 허용하는 것이 좋은 설계입니다.
    allow_null=True
)
    password = serializers.CharField(
        write_only=True, 
        required=True, 
        style={"input_type": "password"}
    )

queryset

  • 클라이언트가 보낸 ID(예: 5)가 유효한지 검사할 '탐색 범위'를 지정

required

  • 글을 작성할 때 반드시 ~를 지정해야 하는지 묻는 옵션

allow_null

  • 클라이언트가 값을 비워서(null) 보내는 것을 허용할지 묻는 옵션

write_only

  • 응답(Response)에 포함 안됨

style={'input_type': 'password'}

  • Browsable API에서 비밀번호 입력창을 마스킹 처리함

class Meta

extra_kwargs

  • 특정 필드에 대한 추가 설정을 지정
    class Meta:
        model = Post
        fields = [
            "title",
            "content",
            "thumbnail",
            "summary",
            "is_temp",
            "tags",
        ]
        # 특정 필드에 대한 추가 설정을 지정
        extra_kwargs = {
	        # 요약은 필수가 아니며 빈 값도 허용
            'summary': {'required': False, 'allow_blank': True}, 
            # 기본적으로는 정식 발행(False) 상태로 처리
            'is_temp': {'default': False}, 
        }

View 📌

옵션

is_valid(raise_exception=True)

  • 유효성 검사 실패 시 자동으로 400 Bad Request를 반환

인증 📌

JWT


uthenticate

  • authenticate(email=email, password=password)

    • 이 함수는 이메일과 비밀번호가 DB와 일치하는지 확인하고, 일치하면 User 객체를 반환함
    • 일치하지 않으면 None을 반환함

RefreshToken.for_user(user)

  • efreshToken.for_user(user) 호출하면 해당 유저를 위한 Refresh/Access 토큰 쌍이 생성됨

service 📌

메서드

create_user

        user = User.objects.create_user(
            email=email,
            nickname=nickname,
            password=password
        )
  • create_user 메서드는 UserManager(managers.py)에 정의된 로직을 따름

    • 내부적으로 set_password()를 호출하여 비밀번호를 암호화(hashing) 저장
# 간략 예시
def create_user(self, username, email=None, password=None, **extra_fields):
    # 1. 유저 객체 생성 (아직 DB 저장 전)
    user = self.model(username=username, email=email, **extra_fields)
    
    # 2. 비밀번호 설정 (여기서 set_password가 호출됨!)
    user.set_password(password)
    
    # 3. DB에 최종 저장
    user.save(using=self._db)
    return user

create

  • 전달된 인자를 데이터베이스에 그대로 저장

    • 비밀번호를 평문(Plain Text)으로 넣으면 암호화되지 않은 채로 저장되어 보안에 치명적
user = User.objects.get(username='gemini')
user.set_password('new_password123') # 암호화 처리
user.save()
  • 위의 예시처럼 이미 존재하는 유저의 비밀번호를 변경할 경우에는 set_password만 따로 사용하기도 함

데코레이터 📌

@property

메서드를 마치 클래스의 속성(Attribute)처럼 접근할 수 있게 만들어줌

  • 단순히 값을 읽는 것 이상의 제어권을 가질 수 있어, 데이터 캡슐화와 유지보수 측면에서 필수

핵심역할

  • 보통 메서드를 호출할 때는 obj.get_name()처럼 괄호()를 붙여야 하지만
    • @property 를 사용하면 obj.get_name처럼 괄호 없이 변수처럼 호출할 수 있음

주요 이점

  • 캡슐화: 내부 데이터를 보호하면서 외부에는 인터페이스만 제공
  • 유효성 검사: 값을 설정할 때 미리 정의한 조건에 맞는지 확인 가능
  • 계산된 속성: 실제 변수로 저장되어 있지는 않지만, 호출 시점에 계산된 값을 반환

예시

class SmartPhone:
    def __init__(self, price):
        self._price = price  # 내부에서만 사용할 변수는 관례상 _(언더바)를 붙입니다.

    @property
    def price(self):
        """메서드를 속성처럼 읽게 해주는 Getter 역할입니다."""
        print("가격을 조회합니다.")
        return f"{self._price:,}원"

    @price.setter
    def price(self, value):
        """속성에 값을 할당할 때 호출되는 Setter 역할입니다. 유효성 검사가 가능합니다."""
        if value < 0:
            raise ValueError("가격은 0원보다 작을 수 없습니다!")
        print(f"가격을 {value}원으로 업데이트합니다.")
        self._price = value

# 실행 코드
phone = SmartPhone(1000000)

# 1. @property 덕분에 괄호 없이 호출 (Getter)
print(phone.price)  # 출력: 가격을 조회합니다. -> 1,000,000원

# 2. @price.setter 덕분에 대입 연산자로 값 변경 (Setter)
phone.price = 1200000  # 출력: 가격을 1200000원으로 업데이트합니다.

배포 📌

nginx

  • 가볍고 높은 성능을 자랑하는 웹 서버(Web Server)이자 리버스 프록시(Reverse Proxy) 서버 프로그램

Nginx의 핵심 이론

  • 어떻게 압도적인 성능을 내는가?

    • Nginx가 전 세계적으로 가장 많이 쓰이는 웹 서버가 된 이유는
      • '이벤트 기반(Event-Driven)'의 비동기(Asynchronous) 아키텍처를 사용하기 때문
  • 기존 방식 (Apache 등)

    • 클라이언트의 요청이 들어올 때마다 새로운 프로세스나 스레드를 생성하는
      • 스레드 기반(Thread-Driven) 모델이었음
    • 동시 접속자가 많아지면 스레드가 무한정 생성되어 메모리가 고갈되고
      • 서버가 뻗어버리는 문제(C10K 문제)가 발생하기 쉬움
  • Nginx 방식

    • 소수의 고정된 워커 프로세스(Worker Process)만 띄워놓고
      • 들어오는 수많은 요청들을 '이벤트'로 취급하여 비동기 방식으로 처리
    • 하나의 요청이 디스크 I/O 등으로 대기 상태에 들어가면 프로세스가 쉬지 않고
      • 즉시 다른 이벤트를 처리함, 덕분에 적은 메모리로 수만 개의 동시 접속을 가볍게 처리할 수 있음

Nginx가 꼭 필요한 이유

  • 웹 애플리케이션(Spring Boot, Node.js, Django 등)을 만들고 나면, 그 자체로도 서버를 띄울 수는 있음
    • 하지만 실제 서비스 환경에 배포할 때는 반드시 그 앞에 Nginx를 두는 것이 표준 명세처럼 자리 잡았음
  • 이유

    • ① 리버스 프록시 (Reverse Proxy) 역할을 통한 보안 및 포트 숨김

      • Nginx를 인터넷 망과 내부 백엔드 서버(앱 서버) 사이에 둠
      • 보안 강화
        • 사용자는 내부 서버의 진짜 IP나 실행 중인 포트(ex. 3000, 8080)를 알 수 없음
        • Nginx가 80(HTTP) 또는 443(HTTPS) 포트로만 요청을 받아
          • 내부망에 있는 앱 서버로 몰래 전달(Proxy)해주기 때문에
          • 백엔드 서버가 해커에게 직접 노출되는 것을 막아줌
    • ② 정적 파일(Static Content)의 효율적인 분리 처리

      • HTML, CSS, JavaScript, 이미지 파일 같은
        • 정적 리소스는 굳이 무거운 백엔드 애플리케이션 서버가 처리할 필요가 없음
      • Nginx는 웹 서버로서 정적 파일을 제공하는 데 매우 특화되어 있음
        • Nginx가 앞단에서 정적 파일을 바로 클라이언트에게 던져주고
          • 백엔드 서버는 DB 조회나 복잡한 로직(동적 처리)에만 집중할 수 있게 하여
          • 전체 시스템의 퍼포먼스를 극대화
    • ③ 로드 밸런싱 (Load Balancing)

      • 블로그가 유명해져서 트래픽이 감당 안 돼 백엔드 서버를 3대(서버 A, B, C)로 늘렸다고 가정
        • Nginx는 들어오는 엄청난 양의 트래픽을 서버 A, B, C로 골고루 분산시켜 줌
        • 특정 서버에만 부하가 몰려 서버가 다운되는 것을 방지
    • ④ SSL/TLS 암호화 처리 (HTTPS 적용)

      • 현대 웹 환경에서 보안을 위한 HTTPS 적용은 필수임
        • 이를 위해서는 트래픽을 암호화하고 복호화하는 연산 과정이 필요함
      • 이 무거운 암복호화 작업을 백엔드 서버가 직접 하게 되면 서버의 부담이 매우 커짐
        • Nginx를 앞에 두면 Nginx가 클라이언트와의 HTTPS 통신(SSL Termination)을 전담하고
        • Nginx와 내부 백엔드 서버 사이에는 가벼운 HTTP 통신을 하게 만들어 앱 서버의 성능 저하를 막을 수 있음
    • ⑤ 비정상 트래픽 차단 및 속도 제한 (Rate Limiting)

      • 악의적인 디도스(DDoS) 공격이나 비정상적인 크롤링 봇의 접근이 발생했을 때
        • Nginx 단에서 특정 IP의 초당 접속 횟수를 제한하거나
        • 요청 버퍼 크기를 제한하여 뒤에 있는 진짜 서버를 보호하는 1차 방어선 역할을 함

profile
안녕하세요.

0개의 댓글