현재 다노에서 다노샵 서버 개발을 하고 있는 대구올빼미입니다.(올빼미지만 밤 10시에 자는 건 비밀)

그리고 현재 다노샵 서버는 python을 베이스로 django 프레임워크를 활용하여 구성되어 있습니다.
많은 분들이 아시다시피 Django는 정말 좋은 기능들을 많이 제공해줍니다.

그중에서 초보 개발자에게 정말 좋은 것은 바로 ORM...!!
ORM은 객체(Object)와 관계(Relation)을 연결(Mapping)해주는 개념입니다(이라고 합니다).
객체와 관계형데이터베이스를 변형 및 연결해주는 작업이라고 말할 수 있습니다. 즉 개발하는 데 있어서
관계형 데이터베이스의 제약을 최대한 받지 않으면서, 객체를 클래스로 표현하는 것과 같이 관계형 데이터베이스를
객체처럼 쉽게 사용할 수 있도록 해줍니다.

# 30세 이상 멤버 정보 가져오기 in sql
select * from member where age >= 30;

# 30세 이상 멤버 정보 가져오기 in django orm
Member.objects.filter(age__gte=30)


여러 프레임워크에서도 이 ORM을 제공해 줍니다.

그렇다 보니, 실제 서비스에서도 보통 ORM을 통해서 개발을 많이 진행합니다.
하지만 이 ORM의 가장 무서운 점은 실제 안에서 어떤 쿼리문을 발생시켜서 저렇게 쉽게 쓸 수 있도록 해주는지
알 수 없다는 것입니다.

그동안 제가 했던 작업선에서는 평상시에는 전혀 성능적으로 이슈가 크게 되지 않다 보니
너무나도 편하게 ORM을 사용해왔습니다. (사실 ORM이 아직도 너무 좋습니다 흑....)

실제 ORM 쿼리가 어떻게 요청이 되는지, 사수분이 여러 번 이야기해주셨지만
직접적으로 와 닿지가 않아 그렇게 넘어갔습니다. (죄송합니다... 반성합니다ㅠㅠ)

그리고 그 문제는 생각보다 크리티컬 하게 일어났습니다.

최근에 다노샵에서는 옵션별 할인 프로모션과,
기존에 있었던 대용량 할인 정보를 함께 표시해주도록 하는 업무를 진행하게 되었습니다.
그리고 그동안에 잘 사용하던 ORM에서 드디어 문제를 일으키고 말았습니다.

기존에는 각각의 제품 단위로 프로모션 할인이 들어가게 되었고, 제품 단위로 할인율을 구해주면 되었지만,
이제는 제품 하위의 옵션 단위로, 그리고 해당 옵션이 대용량 할인을 하고 있는지 일일이 계산하여,
옵션별로 할인율을 표기해주어야 하였습니다.(그만큼 기존보다 더 많은 로직을 타야 했고, 제품에 대한 할인 정보이다 보니 
굉장히 많은 곳에서 해당 부분을 사용하고 있었습니다.)

해당 작업을 진행하며, DUE도 짧았지만 나름 정말 몰입하여(심지어 아침에 운동할 때도 이 생각만 하고), 구조를 짜고
코드를 구성하였습니다. (물론 ORM을 그동안도 잘 사용해왔기에, 성능은 신경 쓰지 않았습니다.)

그리고 그 결과는 아주 처참하게 나타났습니다. 

테스팅을 할 때는 잘 작동하는 것에 만족하면서 테스팅을 진행하였고(현재는 QA시스템이 도입되었습니다.),
빠듯하게 실제 live가 되었을 때 곧 웹사이트가 느려진 것을 알게 되었습니다. 그리고 다급하게 New Relic(New Relic은 SaaS 기반의 APM(Application Performance Management) 서비스를 제공하는 회사)을 살폈습니다.

Newrelic의 web transaction time( 사용자가 웹 페이지를 클릭했을 때, 그 페이지에 대한 응답을 받는데까지의 시간)을 보았을 때 기존보다 적게는 2배에서 많게는 5배까지 느려진 지표를 보여주었다.

신입 개발자를 등을 땀으로 범벅이게 만들었던 그날의 지표들...


이때서야 저는 ORM이 얼마나 무서운지 깨닫게 되었습니다. 그동안 ORM을 아무 생각 없이 사용하였던 나 자신에 대한 깊은 후회와,
반성이 밀려들어왔습니다. 다급하게 전체 캐시를 적용해주어 성능적인 이슈는 막고 있었지만, 이번 프로모션 기간이 끝나기 전에 해당 부분을 개선해주어야 하는 2번째 미션이 주어졌습니다. (즉 일을 2번 하게 되었습니다.)

그렇게 반성을 하며 해당 부분에 대한 리팩토링을 시작하였습니다.
크게 저는 4가지의 방법을 통해 성능을 개선시켰습니다. 지금부터 그 이야기를 해보려고 합니다.

먼저 ORM에서 실제 발생시키는 쿼리를 보지 못하면 절대 개선을 할 수 없습니다. 따라서 ORM에 대해서 잘 모를 때는 무조건 실행 쿼리문을 볼 수 있는 창을 따로 띄어놓고 개발하시기를 추천드립니다.(네... 물론 저는 그렇지 않았습니다. ㅠㅠ)

별첨 : ORM에서 실제 발생시키는 쿼리문 보기 (Django + mysql)

# mysql client에서
show processlist; # 현재 mysql에서 실행 중인 process 보기

show variables like 'general_log%'; # 현재 general_logs의 상태를 볼 수 있습니다.

set GLOBAL general_log='ON'; #general_log를 켤 때

set GLOBAL general_log='OFF'; #general_log를 끌 때

set GLOBAL gnenral_logs = 'ON'으로 하고 나면 어디에 로그를 쌓는지 보이게 됩니다.

general_log_file의 경로를 볼 수 있습니다.

이제 docker(아마 docker 환경에서 개발할 것으로 생각됩니다.)에서 해당 경로로 가서 해당 파일의 log를 tail로 보게 되면
실행되는 쿼리들을 볼 수 있습니다. 

# docker 환경이 아니시거나 혹시 다른 방법으로는 

django-logging : docs.djangoproject.com/en/3.0/topics/logging/
print문으로 바로 찍어 보기 : stackoverflow.com/questions/3748295/getting-the-sql-from-a-django-queryset

와 같은 방법을 이용해보시면 좋을 것 같습니다.

첫 째 : 그 동안 크게 생각하고 있지 않던 select_related에 대해 다시 공부하여 적용해주었습니다. (1+N쿼리문제 해결)

관계형 데이터베이스의 장점은 Foreign Key를 묶을 수 있고 해당 Foreign key로 다른 테이블에도 접근할 수 있다는 것입니다.
하지만 이렇게 Foregin Key로 묶인 데이터를 ORM에서 가지고 올 때 주의해야 할 부분이 있습니다.

간단한 예시를 들어보겠습니다.

class Member(models.Model):
	name = models.CharField(max_length=32)
    age = models.SmallIntegerField()
    detail = models.ForeignKey('MemberDetail', on_delete=SET_NULL)
    
	class Meta:
    	db_table = "member"
        
class MemberDetail(models.Model):
	address = models.CharField(max_length=32)
    
    class Meta:
    	db_table = "member_detail"

가장 쉽게 볼 수 있는 DB 모델을 하나 만들었습니다.
고객의 중요한 정보를 Member 모델에 넣고, 추가 정보들을 MemberDetail로 따로 저장하도록 모델을 구성하였습니다.

나이가 30살 이상인 고객들의 address를 가지고 오고 싶으면 어떻게 해야 할까요?

def get_member_address_list():
	result = []

    member_list = Member.objects.filter(age__gte=30)

    for member in member_list:
        member_address = member.detail.address
        result.append(member_address)

    return result

간단한 함수를 하나 만들고 실행시켜보면, 굉장히 놀랄 수도 있습니다. (물론 위의 ORM에 의해 발생하는 로그들을 보고 있을 때입니다.)
왜냐하면 member.detail.address 이 부분에서 Foregin key에 묶인 MemberDetail 정보를 가지고 오기 위해 새로운 쿼리를 발생시키는데, 
해당 for문을 돌 때마다 1번씩 발생시키기 때문입니다. (해당하는 고객이 1만 명이라면, 1만 번 발생하게 됩니다.)

숫자가 적을 때는 괜찮지만 숫자가 많아지게 되면 어마어마한 쿼리를 발생시키게 됩니다. (물론 컴퓨터는 굉장히 빠르기 때문에, 평소에는 잘 느끼지 못합니다.)

이것을 1+N 쿼리 문제라고 합니다.

이 부분을 개선해주기 위해서는 어떻게 해야 할까요? 바로 select_related를 써주어 처음 member_list를 query 해올 때, 함께 Foregin Key로 묶인 MemberDetail 정보를 함께 쿼리 해오는 것입니다. (filter를 적었다고 쿼리문이 발생하지 않습니다. 위에서는 for문을 돌아야 하는 시점에 해당 filter의 query가 실행되게 됩니다.)

해당 코드를 select_related를 통해 개선해보겠습니다.

def get_member_address_list():
	result = []

    member_list = Member.objects.select_related('detail').filter(age>30)

    for member in member_list:
        member_address = member.detail.address
        result.append(member_address)

    return result


ORM에서 filter를 해올 때 select_related를 통해 Foreign key 묶인 것을 한 번에 query를 해오면 해당 Foregin key가 필요할 때 굳이 또 query문이 생성되지 않아도 된다는 것입니다. 해당 함수를 실행시키면, 딱 1회 query 문이 실행되는 것을 볼 수 있습니다. Member에서 age가 30 초과인 사람들을 filter 해올 때 해당하는 사람들의 detail에 묶인 정보도 함께 query 해와 메모리에 저장해놓고 이후에 지속적으로 사용하게 됩니다. (select_related 이외에서 prefetch_related도 있으나 여기서는 다루지 않겠습니다.)

둘 째 : 매개변수를 넘겨줄 때 객체 형태로 넘겨주면 또 한 번의 쿼리를 줄일 수 있습니다.

보통 우리는 매개변수에 값을 전달해줄 때, 정수를 넘겨주는 경우가 많다. 예를 들면 member_id를 넘겨주는 것이다.
이것도 예시를 먼저 들어보면 좋을 것 같다.

이번에는 특정 멤버의 address를 가지고 오는 함수를 생성하였다.

class GetMemberInfo:
	
    # 특정 나이보다 큰 멤버들의 주소 정보를 가지고 오는 함수
    def get_member_address_list_from_age(age):
        result = []
        member_list = Member.objects.filter(age>30)

        for member in member_list:
            member_address = self.get_member_address(member.id)
            result.append(member_address)
	
    # 특정 멤버의 주소 정보를 가지고 오는 함수
    @staticmethod
    def get_member_address(member_id):
        member = Member.objects.get(id=member_id)
        member_address = member.detail.address
        return member_address

위의 get_member_address 함수를 보면 member_id라는 argument를 받아서, 그 안에서 member_id를 통해 member 정보를 다시 가지고 오고, 그리고 묶인 Foreign Key를 통해 그 멤버의 address를 뽑아내고 있습니다.

이렇게 되면 get_member_address 함수를 실행할 때마다 member에 대한 쿼리 1번과 member의 Foreign key로 묶인 detail(Member_Detail)에 대한 쿼리 1번 총 2번의 쿼리가 실행되게 됩니다. 이것을 1번으로 줄이는 것은 위의 select_related를 사용해주면 1번으로 줄일 수 있습니다. (member = Member.objects.select_related('detail').get(id=member_id)

그럼 1번은 꼭 어쩔 수 없이 실행되어야 하는 걸까요?

이 쿼리 1번도 아예 줄여 버릴 수 있습니다. 그 방법은 바로 argument로 member_id를 넘겨주는 대신 객체 자체를 넘겨주는 방법입니다.
위의 함수를 좀 수정해보도록 하겠습니다.

class GetMemberInfo:
	
    # 특정 나이보다 큰 멤버들의 주소 정보를 가지고 오는 함수
    def get_member_address_list_from_age(age):
        result = []
        member_list = Member.objects.select_related('detail').filter(age>30)

        for member in member_list:
            member_address = self.get_member_address(member)
            result.append(member_address)
	
    # 특정 멤버의 주소 정보를 가지고 오는 함수
    @staticmethod
    def get_member_address(member):
        member_address = member.detail.address
        return member_address

member_id를 argument로 넘겨주어서 다시 한번 get_member_address 함수에서 쿼리를 실행시키는 게 아니라,
애초에 argument로 쿼리 되어 뽑힌 객체 자체를 넘겨주는 것입니다.
그리고 추가적으로 member_list를 filter 해오는 단계에서 select_related를 통해 detail 정보를 함께 쿼리 해오게 되면, 객체 정보를 넘겨줄 때 Foreign key로 묶인 detail에 대한 정보도 함께 넘겨지게 되고, 놀랍게도 get_member_address 함수에서는 아예 쿼리가 발생하지 않게 됩니다. 그럼 쿼리는 member_list에 대한 쿼리 딱 1번으로 마무리할 수 있습니다. 

ORM을 사용하여 아무 생각 없이 코드를 작성하다 보면 쿼리의 효율이 굉장히 나빠질 수 있습니다. (또 한 번 반성합니다..)

셋 째 : 필요한 부분에는 캐시를 적용해 줍니다.

현재 다노에서는 Redis를 캐시 DB로 활용하고 있습니다.
필요한 적재적소의 부분에 캐시를 적용해주면 성능을 굉장히 높일 수 있습니다. 위의 과정들을 통해 성능을 개선하였다면, 더 비약적으로 
성능 개선을 위해서는 캐시 설정하는 것만큼 극단적인 효과를 보기가 쉽지 않습니다. 물론 캐시 역시 만들어서 특정 데이터베이스에 저장해 놓는 것이기 때문에 데이터에 접근하기 위한 시간이 소모되긴 하지만 적절한 위치에 캐시를 적용해 놓기만 하면 훨씬 개선된 성능을 볼 수 있습니다. 

이것도 예를 한번 들어보겠습니다.
제품에 대한 정보를 가지고 올 때, 단지 제품에 대한 정보만 가지고 오면 될까요? 사실 제품에는 묶여 있는 것들이 많습니다.
제품에 대한 정보뿐만 아니라 제품 옵션들 각각에 대한 정보, 그리고 배송에 관한 정보, 재고에 관한 정보, Q&A에 대한 정보, 후기에 대한 정보 등등 굉장히 다양한 데이터 테이블들이 묶여서 우리가 보는 제품에 대한 페이지를 구성합니다.

그럼 필연적으로 해당 테이블들을 모두 쿼리 해올 수밖에 없습니다. 하지만 사실 재고에 대한 정보를 제외하고는 제품에 대한 정보, 그리고 옵션에 대한 정보, 배송에 대한 정보 등은 한번 설정을 하고 나면 잘 바뀌지 않습니다.(물론 리뉴얼 등으로 바뀔 수도 있습니다.) 

이렇게 바뀌지 않는 정보들에 대한 것은 모두 모아 캐시로 설정해놓고, 재고에 대한 정보만을 쿼리 해와서 함께 붙여주기만 하면 성능이 훨씬 개선될 수 있습니다. 

여기서 캐시 설정에 주의할 점들이 있습니다.
먼저 해당 캐시의 시간을 적절하게 세팅해주는 것(너무 긴 시간을 설정해주면, 정보가 변경되었지만 반영이 안 되게 되고, 너무 짧은 시간 설정해주면 의미가 없게 됩니다.)
그리고 적절한 위치에 캐시를 설정해주고 (잘 변경되지 않는 데이터들), 또 특정한 순간(상품에 대한 정보가 바뀔 때 등)에 캐시를 깨 주는 로직을 추가해주어야 합니다. 

넷 째 : 원시 SQL문 적용해주기

django에서도 원시 SQL?을 사용하여 쿼리를 요청할 수 있습니다. 방법은 크게 2가지가 있습니다.
1. Manger.raw ()를 사용하여 원시 쿼리를 수행하고 모델 인스턴스를 반환하는 것
2. 모델 레이어를 완전히 파괴하고 사용자 정의 SQL을 직접 실행시키는 것

2가지의 방법을 통해 직접 SQL 문을 작성하여 실행시킬 수 있습니다. 현재 다노에서도 많은 데이터를 쿼리 해와야 할 때는
직접 SQL을 작성하여 DB에 connection을 뚫어서 직접 SQL을 요청하고 있습니다. 

다만 해당 부분은 ORM을 직접적으로 사용하는 방법이 아니므로, 이번 글에서는 자세히 다루지 않겠습니다.


이번에 잘못된 부분들에 대해서 리팩토링을 진행하면서는 크게 위의 1,2,3번의 요소들을 반영해 주었습니다.
그리고 실제 개선된 부분들에 대해서 이야기를 해보고자 합니다.

과연 그 결과는 ...!! 두둥

기존 결과 값과 비슷하거나 더 개선된 수치들도 존재하였다. (로직이 많이 추가된 것에 비해서 많은 부분 개선되었다고 판단하였습니다!)
다시 리팩토링을 하여서 비록 작업을 2번 하게 되었지만, 이번 기회를 통해 정말 정말 정말... ORM의 무서움을 직접 몸으로 겪게 되었고,
현재는 위의 규칙들을 잘 생각하며 코딩하고 있습니다..

ORM은 너무 편하고 좋지만, ORM의 실제 쿼리들이 어떻게 생성되는지 초창기에는 창을 함께 띄어놓고
보시면서 개발을 하시면 저와 같은 시행착오를 겪지 않으실 거라 생각하고,
개발하시는데도 많은 도움이 되실 거라 생각합니다.

읽어주셔서 감사합니다.

2019.05.13 장고 페이지 서칭 기능 구현하기

서칭 기능 구현하기

  1. 데이터베이스에 질의문 (query 문)/ 애초에 해당애들만 뽑아오기
    1. documents = Document.objects.filter(title__contains = "1")
    2. documents = get_list_or_404(Document, title__contains = "1")
  2. 코드레벨 검색 (DB에서 다 가지고 온 다음에 뽑아내기 )
    1. 파이썬 코드레벨에서 검색
    2. 다 가지고 온 다음에 for문 같은 것을 돌려야 한다.
  3. DB에서 가지고 오는게 제일 빠른데 항상 병목 현상은 db에서 발생한다.
    1. 서비스가 느려지고 그럴 수 있다.
    2. 데이터베이스의 쿼리질의문을 줄인다.?
      1. 하나씩 물어보는 방법과 한번에 물어보는 방법이 있다.
      2. 하지만 그렇게 하면 질의문이 길어져서 해석하는데 시간이 오래 걸릴 수 있다.

로직 생각

  1. 검색어가 들어온다.
  2. 검색어를 받아서 제대로 받았는지 확인한다.
  3. filter 메소드를 사용한다.
    1. 어떤 항목을 검색할 것이냐?
      1. 내용/ 작성자 등
    2. 어떤 옵션으로 검색할 것이냐?
      1. 특정단어가 들어가있는지/ 처음에 시작하는지/ 대문자 / 소문자 등등

쉘에서 filter 연습하기

  1. objects는 manage이다. 모델명.objects. 인 경우
  2. filter( a, b) : a이고 b 인것
  3. filter(filed이름) = 매칭
  4. filter(필드이름__옵션)
  5. filter(title__startswitch = "") : 제목이 특정 문구로 시작하는 것
    1. (title__istartswitch) : 제목이 대소문자 구분 없이 특정 문구로 시작
    2. "" % : 뒤에 특정 애들이 붙을 수 있다.
  6. filter(title__endswitch = "") : 특정 문구로 끝나는지
  7. filter(title__iexact=="" ) : 대소문자 구분없이 특정 제목이 있나?
  8. filter(title__icontains = "") : 대소문자 구분없이 중간에 포함하고 있나?
  9. 우리가 이렇게 매칭한 것은 로컬필드가 검색이 가능하다.
    1. 기본 필드들이 로컬 필드 이다.
    2. 그럼 연결된 foreign key혹은 primary key는 어떻게 해야하나?
  10. filter(category__name="c1") : 카테고리 필드의 이름이 c1인 것
  11. filter(category__ name__endswith =" ") : 카테고리의 이름이 "" 으로 끝나는 애들
    1. %endswith : 앞에 특정 애들이 붙을 수 있다.
  12. 여러개 모델에서 찾는 것도 추가적으로 구현할 수 있다.
    1. by 일래스틱서치 - 우리의 스팩이 한줄 더 늘 수 있다.
    2. 한글 검색이 특히

정리(위의 예시를 바탕으로 정리한 것입니다)

  1. 필드명 = "값" 매칭
  2. 필드명__exact = "값" 매칭
  3. 필드명__iexact = "값" 대소문자 구분 없이 매칭
  4. 필드명__ startswich,필드명 __istartswich : "값"으로 시작
  5. 필드명__ endswitch, 필드명__iendswitch : "값"으로 끝
  6. 필드명__ contains , 필드명__icontains : "값"을 포함하느냐?
  7. Foreignkey 매칭
    1. 필드명__해당모델의 필드명 : 매칭
    2. 필드명__ 해당모델의 필드명__ 옵션 : 해당 모델의 필드명이 해당 옵션으로 매칭
  8. 필드명__ gt=값, 필드명__gte= 값 크다, 크거나 같다.
    1. ex) created__gt = 오늘 : 작성일이 오늘보다 크다. (오늘 00시 이후)
  9. 필드명__ lt =값 , 필드명__lte = 값 작다, 작거나 같다.
    1. ex) created__lt = 오늘 : 작성일이 오늘보다 이전이다. (datetime.now())
    2. ex) 판매시작일 __lt = 오늘 : 판매시작일 설정값이 오늘보다 작거나 같으면 판매시작

서칭 버튼 구현 (템플릿)

스크린샷 2019-05-14 오후 12 02 22

서칭 views.py 구현

26EFCA46-24B9-400F-BA1F-FA2653B79856
  • 서칭은 기본적으로 list를 띄어주는 상황에서 가능하다.
  • search_key가 없으면 그냥 get_list를 통해 모든 list를 가지고 온다.

서칭 심화 구현 - OR 구현하기

  • 특정 필드가 아닌 제목, 본문, 사용자이름과 관련해서 동시에 서칭하고 싶을 때!

Q 객체 사용하기

>>> from django.db.models import Q
>>> Q(title__icontains='1')
<Q: (AND: ('title__icontains', '1'))>
>>> default_q = Q(title__icontains='1')
>>> second_q = Q(text__icontains='1')
>>> default_q | second__q
<Q: (OR: ('title__icontains', '1'), ('text__icontains', '1'))>
>>> Document.objects.filter(default_q|second_q)

Q객체는 우리가 기존에 쓰던 필터를 그대로 쓰게 해주는데, 일반 필더안에서는 and만 가능해서 Q객체를 활용해서 or을 사용가능하게 된다.

  1. objects.filter() : filter 매서드에 들어가는 매개변수들은 항상 and 연산을 한다.
  2. or 연산을 하고 싶퍼서 Q객체를 사용한다.
  3. 사용법은 filter에 들어가는 매개변수의 작성법과 똑같다.
    1. Q() | Q() : OR
    2. Q() & Q() : AND
    3. ~Q() :NOT
    4. 구글 검색 연산자
      1. 작성자, 제목, 본문에 대해서 다 검색할 수 있도록
      2. 체크박스 보이기
      3. 체크박스 값확인
      4. Q객체를 어떻게 만들 것인가?

제목, 본문에서 해당 내용 같이 검색하도록 하기 (by Q객체 - views.py)

13E315E3-4C12-4F4F-85F3-D58C61BB2C93
  • title_q와 text_q를 변수로 받고 Q 객체를 활용하여 OR 필터가 가능하도록 한다.
  • 그 이외는 모두 한개의 필터를 사용할 때와 같다.

체크박스로 작성자/ 제목/ 내용 등을 구분할 때

views.py 구현하기

4C77E9AE-3BE4-4663-BBCD-309372DBBD94
  1. request.METHOD.get : 마지막 들어오는 1개만 표시
  2. request.METHOD.getlist : 리스트 형태로 모두 가지고 온다.
    1. search_type = request.GET.getlist('search_type',None)
  3. 기본적으로 search_type이 없으면 text에서 검색하도록 한다.
  4. 각각 title/ text/ username이 들어왔을 때에 따라서 분기하여 구현한다.

templates 구현하기

스크린샷 2019-05-14 오후 8 45 29
  • form을 get 방식으로 받아오고 3가지를 모두 search_type이라는 이름으로 받아온다.
  • 따라서 views.py에서 getlist를 통해 모두 가지고 와야 한다.

2019.05.10 장고 정적파일에 관한 이야기

장고 정적 파일 : Static file 과 Media file

참고 : 6. Django 정적 파일 기능 이해하기 · Kay on the rails

  1. 장고는 정적 파일을 제공하는 실 서비스용 기능을 제공하지 않는다.
843801B8-F61F-492C-8523-0C854876DF52
  • 정적 파일을 제공하는 것은 웹 서버의 전문 영역이기 때문에 개발 단계에서 쓸 정적 파일을 제공하는 기능을 한다.
  • 장고를 이때까지 웹 서버라고 생각했는데 장고는 웹 애플리케이션 서버였구나!!

장고의 정적 파일의 2종류

Static file

  1. 웹 서비스에서 사용하려고 미리 준비해 놓은 정적 파일
  2. setting.py에 관련 항목 3가지 사용
    1. STATICFILES_DIRS
      1. 개발 단계에서 사용하는 정적 파일이 위치한 경로들을 지정하는 설정 항목
    2. STATIC_URL
      1. 웹 페이지에서 사용할 정적 파일의 최상위 URL
    3. STATIC ROOT
      1. Django 프로젝트에서 사용하는 모든 정적 파일을 한 곳에 모아 넣는 경로
      2. 개발과정에서 DEBUG가 True가 되어있다면 동작하지 않고 별 문제가 없지만 실 서비스 환경에서는 중요하다
    4. python manage.py collectstatic 이라는 명령어를 통해 모든 Static file을 모은다.
    5. 여기에 모인 정적 파일들은 장고가 아닌 웹서버에서 활용한다..

Media file

  1. 이용자가 웹에서 올리는 upload 파일
  2. setting.py에 관련 항목 2가지 사용
    1. MEDIA_ROOT
      1. 업로드가 끝난 파일을 배치할 최상위 경로를 지정
    2. MEDIA_URL
      1. Media file의 최상위 URL
    3. MEDIA_URL + MEDIA_ROOT : 실제 접근 주소
  3. urls.py에 urlpatterns를 추가해줘야지 접근할 수 있다.
  4. urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

추가 사항

  1. Static file와 Media file은 정적 파일이라는 점에서 같지만 정적 파일을 제공하는 상황을 예측할 수 있는지 여부는 다르다.
  2. 개발단계에서 쓸 정적파일을 제공하는 기능(DEBUG=True인 상황)
    1. urls.py에 urlpatterns에 URL패턴을 추가해준다. (for media file)
    2. Static file의 경우 위와 같은 처리를 하지 않아도 개발 단계에서 잘 제공되나, media 파일은 위의 단계를 꼭 거쳐줘야 한다.

대구올빼미의 이번 주에 있었던 일들..!!

패스트캠퍼스를 다니면서,

정말 많은 것들을 느끼고 있다. 

일단 정말로 내가!!! 어떤 서비스를 출시를 했다는 것. 

개인 인맥관리 플랫폼 Connect가 바로 그것이다.(커넥트 바로가기)

마지막에는 디자인을 몇번이나 엎어가면서 겨우 겨우 완성한 Connect프로젝트. 오히려 디자인에서 더 시간이 걸렸다고 할만큼 ㅎㅎ... 막상 출시하려고 하니 디자인이 너무 마음에 안 드는 부분들이 많다. 

수업 시간에 배운 것들을 하나 하나씩 적용해가면서 만들기 시작하였고, (어떻게 보면 거이 수업을 시작하자마자 프로젝트를 시작하였다) 한달정도의 시간이 걸려 완성을 한 것 같다. 그러는 동안 학원 선생님부터 시작하여, 개발자들을 만날 때마다 안되는 기능들에 대해 문의하고 도움받고 이야기를 나누었다. 

그렇게 하다보니 출시할 때가 되니 슬프게도 나의 코드들은 거이 없어졌었다.... ㅋㅋㅋㅋㅋㅋ ㅠㅠ 선생님께서는 원래 처음에는 다 그런 것이라고 위로해주셨는데... 진짜겠죠....??

아무튼 정말 딱 수강한지 한달만데 Connect라는 서비스를 배포하였고, 지금도 잘 작동되고 있다. 아마존에서 매달 서버비로 가져가는게 사악하지만 그래도 어디가나 내 서비스를 말할 수 있다는 것은 정말 큰 장점인 것 같다.

그리고 지난 주 주말에는 엔젤핵헤커톤을 나갔는데 (물론 개발자로 참여했다)이틀 동안 코드 한 줄 치지 못하고 발표 준비만 하다가 왔다. 팀에 4년차 개발자가 있어버리니, 내가 할 일이 없었고 그렇게 찾다 찾다 한 것이 기획 및 발표를 맡은 것이다. 후.... 학원에서 하는 해커톤이 아니라 처음 개인적으로 참여하는 해커톤이라 팀에 좋지 못한 영향을 미치면 어쩔까 전전긍긍하며, API 쏘는 방법에 대해서 공부하고, 프론트와 협업 할 때 어떻게 해야하나 고민하고 걱정했던 것이 다 필요가 없어지게 되었다. 

2틀 동안 밤을 세어가면서 옆에서 코딩하는 것을 지켜만 보고 있는 심정이란... 그리고 나의 맥북은 PPT를 만들고 있는 모습을 보면서 많은 자괴감을 느끼기도 하였다. 그래도 각자 맡은 역할을 충실히 수행하면서 잘 마무리 할 수 있었다.

그렇게 나의 첫 해커톤은 마무리되었다.

그리고 이번 주에는 정말 바쁜 날들을 보냈다. 새로운 프로젝트를 기획하고 만들어가기 시작하였고(친구가 준 아이디어인데 데드라인도 같이 줬다. 나쁜놈), 네이버 채용연계형 인턴 역시 지원서를 적었다.  그리고 패스트캠퍼스에서도 이제 포트폴리오 사이트를 구성하기 시작하였고, 그동안 했던 것들을 정리하기 시작하였다. 포트폴리오사이트는 천천히 만들어도 되었는데, 네이버 인턴 지원서에 첨부파일로 개인 포트폴리오 혹은 사이트를 넣어라는 란이 있어서 좀 더 서두르게 되었다. 

이제 수업이 2주도 채 남지 않았고(참 시간이 빠르다 ㅠㅠ), 수업이 끝난 이후에는 2달 동안 개인 프로젝트 및 팀프로젝를 진행해야 하는데, 나는 사정이 생겨 좀 더 가능하면 좀 더 일찍 취업을 생각하고 있다. 아니 취업이 되는게 문제니 ㅎㅎㅎ 내가 생각한대로 잘 취업준비를 해야 될 것 같다. 그리고 이래저래 포트폴리오 사이트도 일단 완성하였다.!! ( 대구 올빼미의 포트폴리오 사이트 )

그 동안 쌓아온 경력들이 개발보다는 다른 쪽이 많아서 포트폴리오를 구성하는데 있어서, 이게 개발자 포트폴리오가 많나 라는 고민도 하였다. 

그리고 무엇보다 이번 주의 큰 행사였던 !! 바로 내 생일이다 ㅎㅎ!!!!

올해는 서울로 올라와서 쓸쓸한 생일을 보낼 것이라 생각하고 있었는데... 너무나도 고맙게도 패스트캠퍼스 친구들이 생일파티를 해주었다. 정말 이 남자들의 우정이란.. 무엇!!!! 정말 오랜만에 생각지도 못했던 케이크도 받아보았다.

패스트캠퍼스 친구들이 준 생일케이크

갑자기 뜬금없이 편의점 간다고 했다가 케이크를 들고 나타났다 ㅋㅋㅋㅋㅋ 진짜 예상도 못했는데, 이렇게 케이크 까지 줄지는 몰랐다. 너무 고마웠다. 그리고 정말 생일이라고 쉴새 없이 많은 분들이 축하 메시지를 보내와주셨는데, Connect의 영향도 컷던 것 같다 ㅎㅎㅎㅎ. 오랜만에 Connect에 추가하니 모든 사람들이 다 연락해야 될 사람들이었고, 그렇게 한분 한분 연락드리다보니 많은 분들과 다시 인연의 끈이 이어지게 된 것 같다.

아무튼 정말 많은 사람들의 축하도 받고, 너무 행복한 하루였다.

이제 이 생활이 얼마 남지 않았다는 것이 아쉬울 정도로, 행복한 나날들이다. 언제 또 이렇게 내가 원하는 것만 하면서 지낼 수 있을까? 빨리 취업해야하는 이유 중에 한개도 이제 생활할 수 있는 돈이 얼마 남지 않은 것이 가장 큰 이유지만... 지금 현재는 너무나도 행복하게 잘 지내고 있다. 

남들과 비교하기 보다는 내가 어제보다 한 가지를 더 배운 것에 행복감을 느끼고, 지금 내가 이렇게 새로운 기술을 익히고 있는 시간들이 너무 감사하다.

아무튼 이번 한 주는 정말 너무 바쁜 한 주였고...!!

이제 그 한 주도 끝나간다. 짜이찌엔!

*  추가

그래서 저는 개발자가 되었을까요? 못 되었을까요? 결과가 궁금하시죠?

그 결과는...!!

[패스트캠퍼스 웹프로그래밍 스쿨를 마무리하며 :: 쌀 팔다 개발자](https://daeguowl.tistory.com/17?category=796233)

 

패스트캠퍼스 웹프로그래밍 스쿨를 마무리하며

이 이야기는 올해 1월 개발을 처음 시작한, 그리고 3월부터 패스트캠퍼스 웹 프로그래밍 스쿨에 대한 이야기의 마지막 이다. (+취업 이야기의 연장) 저마다 개발을 하는 이유는 있을 것이다. 그리

daeguowl.tistory.com

 

나는 패스트캠퍼스 웹 프로그래밍 스쿨 10기로 활동 중에 있다.! 

닉네임은 대구 올빼미로 활동 중이다 ㅎㅎ. 그동안 몇 번의 글들을 모두 높임으로 적었었는데 내 성격상 어울리지도 않고 ㅠㅠ

이제는 조금은 편하게 글을 적어보려고 한다. 

이번 주에는 굉장히 많은 일들 (과제 발표를 맡게 된 일, 반 회식 등)이 있어서 이번 주에 있었던 일들을 조금 적어보려고 한다.

일단 이번 주에 본의 아니게 과제 발표를 맡게 되었다. 송종근 강사님은 거의 매일 과제를 내주시는데 그날 배운 것을 바탕으로 하여 우리가 심화 기능들을 구현해 볼 수 있도록 과제를 내어주신다. 음... 결코 쉬운 과제들은 아니다 ㅎㅎ...

아무튼 과제를 해 온 사람들을 확인 하는 과정에서 나도 모르게 손을 들었고 그대로 내일 발표 준비해오세요 ㅠㅠ 라는 말을 들었다.ㅎㅎㅎ 사실 나는 대학교를 다니면서 남들 앞에 서는 과정도 많았고, 이야기할 수 있는 기회도 많아서 이런 발표에 대해서 전혀 부담감을 느끼지 않는데... 저번에 별 준비 없이 과제 발표를 했다가 정말 어디에 숨고 싶을 정도로 못해서 ㅠㅠ 같이 수강하고 있는 학우분들에게도 정말 미안하고 나 스스로도 굉장히 속상했던 기억이 났다. 내가 발표한 10분의 시간이 20명의 함께 하고 있는 친구들이 들어야 하므로 나는 무려 그 모든 사람들의 시간을 10분씩 빼앗은 게 되기 때문에  어느 순간부터 발표를 준비할 때 그것을 들어주는 사람들의 시간이 아깝지 않게 하기 위해 굉장히 많은 준비를 하려고 노력했다. 하지만 프로그래밍은 내 전문분야도 아니고..(결국 다 핑계겠지만 ㅠㅠ) 아무튼 그렇게 지난번 발표는 나름 준비했다고 생각했는데 완전... 어버버 해버렸다.

이번 과제는 바로 수업시간에 배운 filter를 활용하여 서칭기능을 구현하고 그것을 적용하여 전화번호부를 만들어보는 것이었다.

내가 이 과제를 남들보다  손쉽게 했던 이유는 바로 내 프로젝트에 사람들의 전화번호를 넣는 부분이 있기 때문에 ㅎㅎㅎ 자연스럽게 구현할 수 있었다. 내 프로젝트를 진행하며 수업을 듣다 보니 수업을 듣는 중에 나오는 여러 기능들을 나도 모르게 적용하기 위해 노력하고, 그렇게 filter 기능을 넣어 서칭을 할 수 있도록 하였다. 

아무튼 이번 발표는 그것을 하는 것이었는데... 수업 시간에 한 것만 발표하는 것은 다른 사람들도 모두 같이 들었던 부분을 반복해서 이야기하는 것이라 도움이 별로 되지 못할 것 같다는 생각이 들었다. 거기에 생각이 미치자 이번에는 정말 스토리를 구성해서 발표를 해야겠다는 생각이 들었고 크게 3가지에 대해 나누어서 이야기하였다.

먼저 나의 프로젝트이야기였다. 나는 현재 우리들의 소중한 인맥을 관리해줄 수 있는 웹을 구현 중에 있었고, 내 웹사이트에 서칭 기능을 구현했는 것을 보여주기에 앞서 왜 이런 프로젝트를 진행하고 있는지 설명하는 시간을 잠시 가졌다. 그래도 내가 많은 생각을 가지고 진행하고 있는 프로젝트라 어느 정도 스토리텔링을 가미하여 잘 이야기할 수 있었다. 

그리고 그 다음에 이어서 실제 코드를 보여주며 어떻게 filter 기능을 구현하였는지에 대한 이야기를 이어갔다. 단순히 수업시간에 했던 filter에 대해서 이야기하는 것을 지양하며 추가적으로 내가 어떤 기능을 더 구현하려고 했는지에 대해서도 이야기해주었다. 또한 실제로 내 프로젝트에서 서칭 기능 이외에도 filter 기능을 통해 구현한 다른 부분에 대해서도 발표하였다. 

그리고 참 충격적인 사건이 있었는데... ㅎㅎ 바로 발표 준비를 하면서 겪었던 오픈 채팅방 사건이다. 

장고 오픈 채팅방

발표 준비를 하면서 모르는 것이 생겨서 장고 오픈 채팅방에 익명으로 들어가서 궁금한 것들을 물어보았다. 근데 정말 감사하게도 한분이 너무나도 친절하게 대답을 해주셨고!! 정말 감사하다는 생각을 가졌다.

하지만 오픈 채팅에서 가르쳐주신 것들을 바로 적용해보고 싶었으나 ㅠㅠ 나는 패스트캠퍼스 웹 프로그래밍 스쿨 회식자리로 향하고 있었다. 그리고 친절하게 대답해주신 분은 나에게 잘 성공했냐는 질문을 하셨고, 나는 죄송한 마음에 잠시 일이 있다는 핑계를 대며 좀 있다가 해본다고 하였다. 근데 어떻게 알았는지 ㅠㅠ 회식을 맛있게 하고 오라는 이야기도 하시고.... 와 진짜 이분은 귀신이시구나... 딱 걸렸네 ㅠㅠ 라고 속으로 생각하며 이거 회식 끝나고 와서라도 꼭 해봐야 되겠다는 생각을 가지고 회식에 참석하였다.

잠시 딴 곳으로 새어서 회식 이야기를 이어서 해보면 ㅎㅎ

처음 스쿨을 시작하고 한 달 쯤 뒤에 회식을 했었고 이번에 또 진행을 했는데 이번에도 재미있었다 ㅎㅎ

ㅎㅎㅎ 1차로 족발 집에 갔는데 정말 족발과 보쌈을 1시간 만에 다 먹고 2차로 이동을 했다. 2차로 간 곳은 성수역에서 정말 내가 추천하는 맛집인 닭도리탕 집으로 갔는데 자리가 만석이라 ㅠㅠ 어쩔 수 없이 2차 대안으로!!! 포차에 갔다 ㅎㅎ

정말 여러 분야에서 다양한 사람들이 모여서 함께 하고, 서로의 이야기를 나누며 그렇게 밤이 깊어 갔다. 참 이런 힘든 과정들을 함께 할 수 있다는 것에 감사하다. 모두가 열심히 하는 분위기 속에서 지내다 보니 나도 자연스럽게 열심히 해야겠다는 생각이 들고, 이런 것들이 쌓이면 우리도 좋은 곳에 취업할 수 있지 않을까? ㅎㅎ

아무튼 서로의 이야기들을 나누며 밤은 깊어 갔고 회식이 끝난 다음에 나는 아까의 문제를 해결하기 위해... 정말 집에 가고 싶은 것을 참고 ㅠㅠ 다시 발표 준비를 하였다.

대망의 다음 날 아침!!. 발표라는게 참 그렇게 별거 아니라고 생각하면 아닌데 저번에 망친 나에게는 조금 더 다르게 다가왔다. 선생님이 오셔서 "어제 질문하신 거 해결하셨어요?"라고 갑자기 물어보셨다. "응??" 갑자기 무슨 이야기시지@.@ 어제 슬랙으로 강사님께 직접 물어보려고 고민하다가 수업 시간에도 너무 질문을 많이 해서 ㅠㅠ 죄송한 마음에 질문을 적었다가 지웠었는데 혹시 나도 모르는 사이에 보내졌나? 하면서 슬랙을 확인했다. 하지만 역시나 따로 보낸 것은 없었다. 그래서 "내?? 무슨 말씀하세요?? "라고 물어보면서 나도 모르게 상황 파악이 되었다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 

바로 단톡방에서 익명으로  정성스럽게 답변해주었던 분이 송종근 강사님이셨던 것이다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 선생님은 익명 단톡방에 대구 올빼미로 이름을 해놓은 나를 알아보고 대답을 해주셨던 것이다. ㅋㅋㅋㅋㅋㅋㅋㅋㅋ 아 너무 웃음이 나왔다 ㅋㅋㅋㅋㅋ 참 진짜... 이렇게 걸릴 줄이야 ㅎㅎㅎㅎㅎ 그렇게 어떻게 나의 회식을 알았는지까지 단번에 이야기 잘 되었다 ㅎㅎㅎ... 강사님 진짜 대박 ㅎㅎㅎㅎ익명 단톡방에서도 학생을 신경써주셨넹 @.@ ㅎㅎㅎㅎㅎ

아무튼 그렇게 웃긴 에피소드를 하나 가지고 수업이 진행되었고 수업이 끝나면서 이어서 나도 발표를 하게 되었다. 

이번에는 그래도 저번보다 훨씬 잘 할 수 있었다. 여전히 많이 떨긴 떨었지만...!!

정말 감사하게도 나의 이야기를 잘 들어주셨고, 나도 신이 나서 발표할 수 있었다.!! 정말 감사하다 ㅠㅠ. 

아무튼 ㅎㅎ 정말 이번주는 너무나도 많은 재미있는 일들이 있었고, 개발이라는 공부를 하기 참 잘했다는 생각이 드는 한 주였다.

요즘 굉장히 슬럼프였는데...!! 슬럼프도 나름 극복하게 된 것 같다. ㅎㅎ 다음 주에는 내가 왜 슬럼프를 겪고 있는지에 대해서 적어보려고 한다. 그럼 이만!! 

*  추가

그래서 저는 개발자가 되었을까요? 못 되었을까요? 결과가 궁금하시죠?

그 결과는...!!

[패스트캠퍼스 웹프로그래밍 스쿨를 마무리하며 :: 쌀 팔다 개발자](https://daeguowl.tistory.com/17?category=796233)

 

패스트캠퍼스 웹프로그래밍 스쿨를 마무리하며

이 이야기는 올해 1월 개발을 처음 시작한, 그리고 3월부터 패스트캠퍼스 웹 프로그래밍 스쿨에 대한 이야기의 마지막 이다. (+취업 이야기의 연장) 저마다 개발을 하는 이유는 있을 것이다. 그리

daeguowl.tistory.com

 

+ Recent posts