배민, 당근 테크 스택 히스토리

배달의 민족 [우아콘 2020] 배달의민족 마이크로서비스 여행기 발표영상 을 정리한 글이다. 5년동안 배달의 민족의 트래픽은 매년 평균 2.3배가 증가할 정도로 급성장한 서비스이다. 이 과정동안 개발팀에서는 어떤 일이 있었는지을 알아본다. 2015년 하루 주문수 5만 이하 MSSQL + PHP, ASP 루비 DB(MSSQL)를 사용. 테이블이 700개가 넘었음. 대부분 루비 DB의 스토어드 프로시저 방식으로 사용함. 4000개 정도 사용하고 있었음. 굉장히 거대한 모놀리틱 시스템. 루비 DB 장애시 전체 서비스 장애로 이어짐. 한 예시로 리뷰 시스템에 장애가 났는데, 고객이 주문을 못하는 상태가 됨. ...

January 10, 2021 · 8 min

Django 쿼리 최적화 방법들

백앤드 API의 성능을 개선하고 싶을 때 가장 만만한 곳이 데이터베이스 쿼리이다. Django에서 쿼리 최적화를 할 수 있는 부분을 정리해보자. Database 최적화 인덱스 인덱스를 추가하는 것은 가장 우선적으로 고려해볼만한 사항이다. Meta.indexes 또는 Field.db_index 를 사용하여 인덱스를 추가하고 filter, exclude, order_by 등을 사용하여 자주 쿼리하는 필드에 인덱스를 추가하면 조회 속도를 높일 수 있다. 일반적으로 인덱스를 속도가 향상되는 것은 맞지만 항상 좋은 것은 아니다. 일단 인덱스를 저장하기 위해 데이터베이스에 추가 저장 공간이 필요하며 많은 인덱스를 생성할 경우에는 부담이 될 수도 있다.그리고 데이터를 삽입, 업데이트, 삭제 할 때는 인덱스도 함께 업데이트 해줘야하므로, 작업의 성능이 저하될 수도 있다. 그래서 처음부터 바로 인덱스를 도입하는 것보다 인덱스 없이 쿼리하다가 특정 쿼리가 자주 사용되거나, 읽기 작업 위주로만 사용되는 테이블이 있다면 그 때 인덱스를 도입하는 것을 권장한다. ...

October 11, 2020 · 14 min

Django REST Framework(DRF) Serializer 뜯어보기

왜 DRF를 사용해야할까? Django REST Framework(DRF)는 RESTful API 개발을 위해 django 위에 추가된 라이브러리로 RESTful API 개발을 위해 필요한 공수를 줄여준다. API를 개발할 때 필요한 authentication, permission, throttling 등을 쉽게 구현할 수 있도록 해주는데 아래에 DRF에서 정의하는 settings만 봐도 API 개발에 필요한 것들을 쉽게 추가할 수 있음을 알 수 있다. DEFAULTS = { # Base API policies 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ], 'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ], 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', ], 'DEFAULT_THROTTLE_CLASSES': [], 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata', 'DEFAULT_VERSIONING_CLASS': None, # Generic view behavior 'DEFAULT_PAGINATION_CLASS': None, 'DEFAULT_FILTER_BACKENDS': [], # Schema 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema', # Throttling 'DEFAULT_THROTTLE_RATES': { 'user': None, 'anon': None, }, 'NUM_PROXIES': None, # Pagination 'PAGE_SIZE': None, # Filtering 'SEARCH_PARAM': 'search', 'ORDERING_PARAM': 'ordering', # Versioning 'DEFAULT_VERSION': None, 'ALLOWED_VERSIONS': None, 'VERSION_PARAM': 'version', # Authentication 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 'UNAUTHENTICATED_TOKEN': None, # View configuration 'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name', 'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description', # Exception handling 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', 'NON_FIELD_ERRORS_KEY': 'non_field_errors', # Testing 'TEST_REQUEST_RENDERER_CLASSES': [ 'rest_framework.renderers.MultiPartRenderer', 'rest_framework.renderers.JSONRenderer' ], 'TEST_REQUEST_DEFAULT_FORMAT': 'multipart', # Hyperlink settings 'URL_FORMAT_OVERRIDE': 'format', 'FORMAT_SUFFIX_KWARG': 'format', 'URL_FIELD_NAME': 'url', # Input and output formats 'DATE_FORMAT': ISO_8601, 'DATE_INPUT_FORMATS': [ISO_8601], 'DATETIME_FORMAT': ISO_8601, 'DATETIME_INPUT_FORMATS': [ISO_8601], 'TIME_FORMAT': ISO_8601, 'TIME_INPUT_FORMATS': [ISO_8601], # Encoding 'UNICODE_JSON': True, 'COMPACT_JSON': True, 'STRICT_JSON': True, 'COERCE_DECIMAL_TO_STRING': True, 'UPLOADED_FILES_USE_URL': True, # Browsable API 'HTML_SELECT_CUTOFF': 1000, 'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...", # Schemas 'SCHEMA_COERCE_PATH_PK': True, 'SCHEMA_COERCE_METHOD_NAMES': { 'retrieve': 'read', 'destroy': 'delete' }, } Serializer 클래스로 DB 인스턴스를 python native 데이터 타입으로 변환할 수 있고 그 반대의 과정도 쉽게 구현할 수 있다. 그리고 다양한 추상화 단계에서 API를 개발할 수 있도록 돕는데, 뷰를 예를들면 APIView, GenericAPIView, GenericViewSet 으로 갈수록 추상화 단계가 높아지고 customization의 정도에 따라 필요한 클래스를 상속받아 API를 구현할 수 있다. 특히 viewset과 router를 통해 코드 몇줄로 CRUD를 구현할 수 있어 코드의 중복을 줄일 수 있는데 실제로 아래 예시는 DRF 공식문서 튜토리얼 > Quickstart 의 코드로 viewset과 router 의 조합으로 API를 작성했다. 먼저 serializer를 구현하여 Viewset의 serializer_class에 할당하고, DefaultRouter에 viewset을 등록하여 urlconf를 생성할 수 있다. 그리고 Django에서 기본적으로 제공하는 HttpRequest, HttpResponse 클래스를 확장하여 API 요청을 처리하는데 필요한 기능을 추가로 제공한 Request, Response 클래스를 사용하는데 실제로 Request 클래스를 초기화할 때 parsers, authenticators, negotiator 인자 등을 추가로 받을 수 있다 ...

June 3, 2020 · 6 min

Django 에서 요청이 처리되는 과정

Django의 서버가 실행되는 과정과 그 서버에 요청이 어떻게 처리되는지를 알아보자. Django 서버가 실행되는 과정 manage.py 커맨드가 실행되는 과정 runserver를 포함한 command가 실행되자마자 가장 먼저 manange.py 파일 내의 execute_from_command_line함수가 호출된다. command 명령어가 실행될 때는 크게 3가지 과정을 거치게된다. 설정 값을 불러오고 django.setup() 이 호출되고 command 가 실행된다. 위 과정이 실행되는 실질적인 로직은 django > core > management > __init__.py의 ManagementUtility.execute메소드에 정의되어있다. 첫번재로 설정 값을 불러오는 단계를 보자. 이 메소드가 정의된 __init__.py 파일에는 from django.conf import settings 을 통해 settings 변수를 import 하지만 이 시점에는 settings.py에 정의된 설정 값들이 실제로 로딩되지 않는다. 그 이유는 settings는 LazySettings 인스턴스가 할당되어 있는데, 이 LazySettings 클래스는 setting이 실제로 사용되는 시점에 설정 값들을 로딩하게끔 lazy loading이 적용되어 있다. 또한 Singleton 패턴이 적용되어 django 어플리케이션 내에서 한번 인스턴스화 되고나면 django 코드 내에서 global하게 사용할 수 있다. 실제로 settings.INSTALLED_APPS를 호출하게 되면서 모든 설정 값을 불러오게 된다. 두번째로 django.setup() 이 호출되는 과정이다. 이 과정에서는 설정 파일의 INSTALLED_APPS 내에 정의된 django app들의 configuration과 model을 저장하는 registry를 생성한다. 세번째로 command를 실행한다. 입력을 string 형태로 받지만 실행될 때는 django > core > management > commands 에 정의된 Command 인스턴스로 변환되어 실행된다. Command 클래스는 기본적으로 add_arguments와 handle 메소드를 구현하고 handle에서 실질적인 command 로직을 작성한다. ...

March 4, 2020 · 7 min