<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>내일 더 잘하는 개발자</title>
    <link>https://rookieno.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 25 Jun 2026 01:18:16 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>rookieno</managingEditor>
    <image>
      <title>내일 더 잘하는 개발자</title>
      <url>https://tistory1.daumcdn.net/tistory/5096745/attach/b5cc03d5ff904ec7bf3d4cbb9bf858b6</url>
      <link>https://rookieno.tistory.com</link>
    </image>
    <item>
      <title>DJANGO + NGINX DDOS 방지 동일 IP중복 요청 제한</title>
      <link>https://rookieno.tistory.com/191</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;limit_req 모듈을 사용하여 동일 IP중복요청을 제한하여 DDOS공격을 방지하는법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699947381768&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;limit_req_zone : 단위 시간당 요청의 수를 제한하는데 사용
limit_conn_zone : 동시 연결 수를 제한하는데 사용

# 모두 사용하여 요청 속도 제한, 동시 연결 수를 제아하여 서버를 보호하는게 좋긴하다고 합니다.
# 서로 보완적으로 작동함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699946098800&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# nginx.conf

http {
    limit_req_zone $binary_remote_addr zone=ddos:10m rate=5r/s;

    server {

        location / {

            limit_req zone=ddos burst=5;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1699946370428&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$binary_remote_addr : 변수에 의해 설정된 클라이언트 IP 주소입니다.
zone=ddos:10m : 존의 이름을 ddos로 정의하고 공유 메모리 영역의 용량을 정의합니다.
rate=5r/s : 초당 5개이상의 요청이 들어오면 제한합니다.
burst=5 : 5개까지는 큐에 보관하고 요청이 burst수를 넘어갈 경우 에러를 반환합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 요청을 넘겼을때 발생하는 에러는 503입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1699946720677&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;limit_req               zone=ddos burst=5 nodelay;
limit_req_status        429;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 반환하는 에러를 정의해줄 수 있습니다.&lt;b&gt; 429 Too Many Requests&lt;/b&gt; 로 변환했습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nginx.org/en/docs/stream/ngx_stream_limit_conn_module.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nginx.org/en/docs/stream/ngx_stream_limit_conn_module.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1699946132962&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Module ngx_stream_limit_conn_module&quot; data-og-description=&quot;Module ngx_stream_limit_conn_module The ngx_stream_limit_conn_module module (1.9.3) is used to limit the number of connections per the defined key, in particular, the number of connections from a single IP address. Example Configuration stream { limit_conn&quot; data-og-host=&quot;nginx.org&quot; data-og-source-url=&quot;https://nginx.org/en/docs/stream/ngx_stream_limit_conn_module.html&quot; data-og-url=&quot;https://nginx.org/en/docs/stream/ngx_stream_limit_conn_module.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://nginx.org/en/docs/stream/ngx_stream_limit_conn_module.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nginx.org/en/docs/stream/ngx_stream_limit_conn_module.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Module ngx_stream_limit_conn_module&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Module ngx_stream_limit_conn_module The ngx_stream_limit_conn_module module (1.9.3) is used to limit the number of connections per the defined key, in particular, the number of connections from a single IP address. Example Configuration stream { limit_conn&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nginx.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Django</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/191</guid>
      <comments>https://rookieno.tistory.com/191#entry191comment</comments>
      <pubDate>Tue, 14 Nov 2023 16:39:04 +0900</pubDate>
    </item>
    <item>
      <title>Django Redis cache</title>
      <link>https://rookieno.tistory.com/190</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;django-redis 라이브러리를 사용해용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jazzband/django-redis&quot;&gt;https://github.com/jazzband/django-redis&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672713496710&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jazzband/django-redis: Full featured redis cache backend for Django.&quot; data-og-description=&quot;Full featured redis cache backend for Django. Contribute to jazzband/django-redis development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jazzband/django-redis&quot; data-og-url=&quot;https://github.com/jazzband/django-redis&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k48xD/hyQ83RhEpe/0CgNrbwCefSkJH4KqwX5H1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jazzband/django-redis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jazzband/django-redis&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k48xD/hyQ83RhEpe/0CgNrbwCefSkJH4KqwX5H1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jazzband/django-redis: Full featured redis cache backend for Django.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Full featured redis cache backend for Django. Contribute to jazzband/django-redis development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 도까로 로컬 레디스를 띄우고 해볼게용 따라해용&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;docker pull redis # 레디스 이미지를 풀 받아용

docker images # 잘 받아졌는지 확인해용

docker network create redis-net # redis cli로 레디스 안에 데이터 확인하고 이것 저것 해보고싶으면 이것도 해용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도까로 레디스를 돌려용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://settings.py&quot;&gt;settings.py&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;CACHES = {
    &quot;default&quot;: {
        &quot;BACKEND&quot;: &quot;django_redis.cache.RedisCache&quot;,
        &quot;LOCATION&quot;: &quot;redis://127.0.0.1:6379/1&quot;,
        &quot;OPTIONS&quot;: {
            &quot;CLIENT_CLASS&quot;: &quot;django_redis.client.DefaultClient&quot;,
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 예제에용&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from rest_framework import viewsets, mixins
from apps.redis_ex.models import RedisEx
from .serializer import RedisExSerializer
from rest_framework.response import Response
from django.core.cache import cache

class RedisExViewSet(
    viewsets.GenericViewSet, mixins.ListModelMixin
):
    queryset = RedisEx.objects.all()
    serializer_class = RedisExSerializer

    def list(self, request, *args, **kwargs):
        queryset = cache.get_or_set(
            &quot;redis_ex&quot;, self.queryset.is_active()
        )

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용해용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cache.set(key, value) : key에 value를 저장해용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cache.get(key) : key값으로 조회하여 value를 가져와용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cache.get_or_set(key, value): key 값을 조회 후 그 키가 없으면 value를 할당하고 가져와용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키값을 확인 하려면 도까 레디스 터미널에 들어가용&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;redis-cli 

keys *
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱에는 전략이 필요해용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에는 속도를 향상 시키기 위해서에용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 캐싱되었다는 것은 데이터의 실시간성을 어느정도 포기하고 성능을 올리는 거에용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 여러가지 전략이 있고 잘 생각해서 전략을 세워야해용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;읽기 전략&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Look Aside or Cache Aside 패턴&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 기본적인 전략이에용&lt;/li&gt;
&lt;li&gt;데이터를 조회 할때 캐시를 우선으로 확인 후 없으면 DB에서 조회하는 전략이에용&lt;/li&gt;
&lt;li&gt;반복적인 읽기가 많으면 적합해용&lt;/li&gt;
&lt;li&gt;데이터 정합성에 문제가 발생할 수 있어용&lt;/li&gt;
&lt;li&gt;캐시에 장애가 발생해도 DB를 조회함으로 캐시 장애로 인한 서비스 문제는 없어용&lt;/li&gt;
&lt;li&gt;초기 조회시 DB를 호출해야함으로 단건 호출빈도가 높은 서비스에는 별로고 반복적으로 동일 쿼리를 수행하는 서비스에 딱이에용&lt;/li&gt;
&lt;li&gt;DB에서 캐시로 미리 데이터를 넣어주는 작업을 하기도해용 Cache Warming이라고 해용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Read Through 패턴&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시에서만 데이터를 읽어오는 전략이에용&lt;/li&gt;
&lt;li&gt;데이터 동기화를 라이브러리 or 캐시 제공자에게 위임해용&lt;/li&gt;
&lt;li&gt;조회시 전체적으로 속도가 느려용&lt;/li&gt;
&lt;li&gt;대신 DB와 캐시간의 동기화가 항상 이루어져 데이터 정합성 문제가 없어용&lt;/li&gt;
&lt;li&gt;redis 서버가 다운되면 서비스에 문제가 생기는 단점이 있어용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쓰기 전략&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Write Back or Write Behind 패턴&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시와 DB 동기화를 바로 하는게 아니라 모아두어서 배치작업으로 DB에 반영해서 동기화해용&lt;/li&gt;
&lt;li&gt;캐시에 모아놓았다가 반영하기 때문에 쓰기 쿼리 회수랑 부하를 줄일 수 있어용&lt;/li&gt;
&lt;li&gt;쓰기가 많고 읽기를 할때 많은 양의 리소스가 드는 서비스에 적합해용&lt;/li&gt;
&lt;li&gt;데이터 정합성이 확보되용&lt;/li&gt;
&lt;li&gt;자주 사용되지 않는 불필요한 리소스가 저장될 수 있어용&lt;/li&gt;
&lt;li&gt;캐시에서 에러나면 데이터 다 날아가용&lt;/li&gt;
&lt;li&gt;대신 디비가 터져도 서비스는 괜찮겠지용? 하지만 고쳐야겠죵?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Write Through 패턴&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB와 Cache에 동시에 데이터를 저장해용&lt;/li&gt;
&lt;li&gt;캐시에 먼저 저장을 하는건 같지만 모아두지않고 바로 DB에도 저장해용&lt;/li&gt;
&lt;li&gt;그러면 캐시의 데이터가 항상 최신 상태겠지용?&lt;/li&gt;
&lt;li&gt;데이터의 일관성을 유지할 수 있어서 안정적이에용&lt;/li&gt;
&lt;li&gt;데이터가 날아가면 큰일나는 서비스에 적합해용&lt;/li&gt;
&lt;li&gt;자주 사용되지 않는 불필요한 리소스가 저장될 수 있어용&lt;/li&gt;
&lt;li&gt;매 요청마다 Cache 한번 Db 한번 두번의 쓰기가 생겨서 자주 쓰기, 수정이 발생하는 서비스에는 성능이 안좋아용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Write Around 패턴&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빨라용&lt;/li&gt;
&lt;li&gt;모든 데이터를 DB에만 저장해용&lt;/li&gt;
&lt;li&gt;Cache miss가 발생하는 경우에만 Cache에도 저장해용&lt;/li&gt;
&lt;li&gt;Cache miss는 조회했을때 캐시에 데이터가 없다는 뜻이에용&lt;/li&gt;
&lt;li&gt;그래서 DB와 캐시의 데이터가 다를 수 있어용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;읽기 + 쓰기 조합&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Look Aside + Write Around : 일반적으로 자주 쓰이는 조합이에용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Through + Write Around: 항상 DB에 쓰고 캐시에서 읽을 때 DB를 먼저 조회함으로 정합성이 좋아용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Through + Write Through: 데이터를 쓸때 캐시에 먼저 쓰므로 최신 데이터를 보장해용, 데이터를 항상 캐시에서 DB로 보내므로 정합성이 보장되용&lt;/p&gt;</description>
      <category>Django</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/190</guid>
      <comments>https://rookieno.tistory.com/190#entry190comment</comments>
      <pubDate>Tue, 3 Jan 2023 11:43:07 +0900</pubDate>
    </item>
    <item>
      <title>Python AES256 암호화, 복호화</title>
      <link>https://rookieno.tistory.com/189</link>
      <description>&lt;pre id=&quot;code_1671672677597&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC


class AES256:
    def __init__(self):
        self.key = &quot;secret&quot;
        self.salt_bytes = bytes([64] * 20)
        self.iv = bytes([64] * 16)

    def get_cipher(self):
        kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=32, salt=self.salt_bytes, iterations=70000)
        secret_key = kdf.derive(self.key.encode())
        cipher = Cipher(algorithm=algorithms.AES(secret_key), mode=modes.CBC(self.iv), backend=default_backend())
        return cipher

    def encrypt_ase(self, base):
        encryptor = self.get_cipher().encryptor()
        padder = padding.PKCS7(128).padder()
        padded_data = padder.update(base.encode()) + padder.finalize()
        encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
        buffer = self.salt_bytes + self.iv + encrypted_data
        return base64.b64encode(buffer).decode()

    def decrypt_ase(self, base):
        decryptor = self.get_cipher().decryptor()
        buffer = base64.b64decode(base)[len(self.salt_bytes) + len(self.iv) :]
        decryptor_data = decryptor.update(buffer) + decryptor.finalize()
        padder = padding.PKCS7(128).unpadder()
        unpadded_data = padder.update(decryptor_data) + padder.finalize()
        return unpadded_data.decode()


aes = AES256()

encrypt = aes.encrypt_ase(base=&quot;test@test.com&quot;)

print(encrypt)

decrypt = aes.decrypt_ase(base=encrypt)

print(decrypt)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Python</category>
      <category>70000</category>
      <category>AES256</category>
      <category>Python</category>
      <category>복호화</category>
      <category>암호화</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/189</guid>
      <comments>https://rookieno.tistory.com/189#entry189comment</comments>
      <pubDate>Thu, 22 Dec 2022 10:31:29 +0900</pubDate>
    </item>
    <item>
      <title>파이썬 오버로딩과 오버라이딩</title>
      <link>https://rookieno.tistory.com/185</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오버 라이딩은 이해하고 있었으나 오버 로딩이 정확히 무엇인지 이해하지 못하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 이유를 보았을 때 파이썬에서는 오버 로딩을 정식으로 지원하지 않았기 때문인듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버 로딩과 오버 라이딩을 정리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오버 로딩, 오버 라이딩&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의로는 동일한 이름의 함수를 매개변수에 따라 다른 기능으로 동작하도록 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버 라이딩은 상속 시에 클래스를 수정하는 것이고 오버 로딩은 하나의 메서드에 다형성을 부여하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 이해하기 편하게 생각해보면 오버 로딩은 클래스 내에 같은 이름의 메서드를 여러 개 선언하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서는 오버 로딩을 지원하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 파이썬에서는 어떤 식으로 해결할까?&lt;/p&gt;
&lt;pre id=&quot;code_1668056698505&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class calculator():
	def add(self, a, b):
    	return a + b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제처럼 계산기 클래스에 더하기 함수가 있을 때 예제대로라면 숫자 두 개씩만 합할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 두 개의 숫자뿐만 아니라 임의의 개수의 숫자를 더 한다면 어떻게 할까?&lt;/p&gt;
&lt;pre id=&quot;code_1668056947698&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class calculator():
	def add(self, *args):
    	return sum(args)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시대로 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 더하기에 원하는 기능들이 있다면 분기 처리를 하거나 MultipleDispatch 패키지를 이용해 오버 로딩을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/185</guid>
      <comments>https://rookieno.tistory.com/185#entry185comment</comments>
      <pubDate>Thu, 10 Nov 2022 14:16:11 +0900</pubDate>
    </item>
    <item>
      <title>[lambda x: i * x for i in range(4)]</title>
      <link>https://rookieno.tistory.com/184</link>
      <description>&lt;pre id=&quot;code_1667798550227&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def create_multipliers(x):
    return [lambda x : i * x for i in range(4)]

for multiplier in create_multipliers():
    print(multiplier(2))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 값이 어떻게 나올까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0,2,4,6이라 생각했지만 6,6,6,6이 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한참을 생각했고 도저히 이해가 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보다 python closure 때문이라는 것을 찾고 개념을 보았다. 배경지식부터 [lambda x: i * x for i in range(4)] 의 출력 값의 이유를 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중첩 함수&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1667799080995&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def first():
    def hello_world():
        print(&quot;hello world!&quot;)
    hello_world()

first()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hello_world라는 함수를 first 함수로 감싸서 hello_world를 호출하고 있다. 이것이 중첩 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 중첩 함수이면 다 클로저라고 알았지만 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1급 객체 (first class object)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 들어보는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 다른 개체에 통용 가능한 동작이 지원되는 개체라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통용 가능한 동작이란 크게 3가지이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;함수의 인자로 전달&lt;/li&gt;
&lt;li&gt;함수의 반환 값이 됨&lt;/li&gt;
&lt;li&gt;수정되고 할당되는 것들을 전제로 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨 소린지 감이 안 온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 흔히 사용하던 자료형들 int, str, list 등 위의 3가지 동작이 가능하다. 1급 객체이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서는 함수도 1급 객체이다.&lt;/p&gt;
&lt;pre id=&quot;code_1667799880281&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def add(a, b):
    return a + b

def execute(func, *args):
    return func(*args)

func = add

print(execute(func, 3, 5))&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;func라는 함수를 함수의 인자로 전달했다.&lt;/li&gt;
&lt;li&gt;함수 내부에서 전달받은 func함수를 문제없이 사용한다.&lt;/li&gt;
&lt;li&gt;add 함수를 func라는 새 이름에 할당했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 특성이 있어야 closure가 성립될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nonlocal&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 중첩 함수가 가능한 것을 확인했다.&lt;/p&gt;
&lt;pre id=&quot;code_1667800248363&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;z = 3

def outer(x):
    y = 2
    def inner():
        x = 1
        return x
    return inner()

print(outer(10))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시대로 한다면 인자를 10으로 줬을 때 반환되는 값은 몇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 scope에 관한 이야기이다. inner함수의 입장에서 보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;inner 함수 안에 있는 영역은 local scope라고 불린다. 안의 모든 개체들은 inner의 제어 아래에 있다&lt;/li&gt;
&lt;li&gt;outer안에 있고, inner 밖에 있는 영역은 nonlocal scope라고 불린다.&lt;/li&gt;
&lt;li&gt;outer 함수 밖의 영역은 global scope이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 scope의 구분은 자신의 영역에 자유롭고 다른 영역의 변수나 객체에 대해서는 제한적인 제어를 가지게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1667800535671&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def first(x):
    def hello_world():
        print(x)
    hello_world()

first(&quot;헬로 월드&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제없이 헬로 월드가 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 영역 즉 외부 스코프에 대해 &quot;읽기&quot;가 문제없이 가능한 것을 확인했다.&lt;/p&gt;
&lt;pre id=&quot;code_1667800647344&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def first(x):
    def hello_world():
        x = x + &quot;!!&quot;
        print(x)
    hello_world()

first(&quot;헬로 월드&quot;)

# UnboundLocalError: local variable 'x' referenced before assignment&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐가 문제일까? local 영역에서 다른 영역에 대한 값을 할당하거나 수정 즉 &quot;쓰는 것&quot;은&amp;nbsp;불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이렇게 설계가 되어있을까? 코드 영역의 책임과 권한을 명확히 나누는 것이 좋기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Closure&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신을 둘러싼 scope의 상태 값을 기억하는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 세 가지 조건을 만족해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;해당 함수는 어떤 함수 내의 중첩된 함수 이어야 한다.&lt;/li&gt;
&lt;li&gt;해당 함수는 자신을 둘러싼(enclose) 함수 내의 상태 값을 반드시 참조해야 한다.&lt;/li&gt;
&lt;li&gt;해당 함수를 둘러싼 함수는 이 함수를 반환해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저의 특징은 무엇일까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클로저는 자신을 둘러싼 함수 스코프의 상태 값을 참조하는데 이 값은 메모리에서 사라져도 값이 유지가 된다.&lt;/li&gt;
&lt;li&gt;클로저의 내부 변수가 아닌 감싸고 있는 함수의 변수에 접근하는 것을 지원한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1667801828563&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;someting.__closure__[0].cell_contents&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저 사용 시 장점은 무엇일까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관리와 책임을 명확히 할 수 있다.&lt;/li&gt;
&lt;li&gt;변수가 섞여 발생하는 충돌을 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;임의대로 내부구조를 조정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그럼 마지막으로 해결해야 하는 [lambda x: i * x for i in range(4)]의 이유를 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 이유는 파이썬의 late binding closure 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 위의 함수를 풀어보면&lt;/p&gt;
&lt;pre id=&quot;code_1667802059830&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def create_multipliers(x):
    multipliers = []

    for i in range(4):
        def multiplier(x):
            return i * x
        multipliers.append(multiplier)

    return multipliers&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 함수와 같다. 즉 클로저 안에서 사용되는 변수는 안에서 호출될 때 정해진다. 그러므로 for loop가 끝나고 마지막인 3이 i값이 된다.&lt;/p&gt;</description>
      <category>Python</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/184</guid>
      <comments>https://rookieno.tistory.com/184#entry184comment</comments>
      <pubDate>Mon, 7 Nov 2022 15:24:39 +0900</pubDate>
    </item>
    <item>
      <title>카디널리티와 인덱싱</title>
      <link>https://rookieno.tistory.com/183</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;카디널리티&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 칼럼에 중복 수치를 나타내는 지표이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복도에 따라 높다 낮다고 표현을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 칼럼을 기준으로 중복도가 높으면 카디널리티가 낮다.&lt;/li&gt;
&lt;li&gt;특정 칼럼을 기준으로 중복도가 낮으면 카디널리티가 높다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 테이블이 있다고 가정해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;칼럼으로는 id, name, gender, age, location&amp;nbsp; 다섯 가지가 존재할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성별은 남, 녀로 카디널리티가 2라고 할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역이 만약 도를 기준으로 전국 8도가 있다고 하면 카디널리티는 8이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카디널리티가 높다, 낮다를 비교하는 것은 상대적이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gender는 location 칼럼에 비해 중복도가 높으므로 &quot;location칼럼보다는 카디널리티가 낮다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다면 인덱싱을 할 때 카디널리티를 이용해 어떻게 거는 것이 좋을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로는 여러 칼럼을 동시에 인덱싱을 할 때 카디널리티가 높은 칼럼을 우선순위로 인덱싱을 하는 것이 유리하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이유가 무엇일까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 인덱싱을 하는 경우는 근본적으로 full scan을 하지 않으려고 인덱싱을 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL의 동작 순서를 보면 인덱스를 사용해 SELECT를 하면 WHERE절의 순서와 상관없이 인덱싱이 걸린 칼럼부터 먼저 탐색하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결과적으로 카디널리티가 높은 즉 중복도가 낮은 칼럼부터 인덱싱을 해주면 더 적은 분기로 탐색하여 빠른 결과를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/183</guid>
      <comments>https://rookieno.tistory.com/183#entry183comment</comments>
      <pubDate>Thu, 3 Nov 2022 21:57:23 +0900</pubDate>
    </item>
    <item>
      <title>Django timezone</title>
      <link>https://rookieno.tistory.com/178</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;django는 기본적으로 utc시간을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국 시간을 사용하기 위해 세팅에 아래와 같이 설정을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1665729349292&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TIME_ZONE = &quot;Asia/Seoul&quot;

USE_TZ = True&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 위의 설정을 하고 datetime 연산을하면 시간이 맞지않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB를 확인해보면 utc 시간으로 설정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에도 설정한 timezone으로 저장하려면 어떻게 할까?&lt;/p&gt;
&lt;pre id=&quot;code_1665729555827&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TIME_ZONE = &quot;Asia/Seoul&quot;

USE_TZ = False&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시대로 사용하면 DB에도 설정한 timezone으로 저장이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘의 차이가 무엇일까?&lt;/p&gt;
&lt;pre id=&quot;code_1665729753328&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;USE_TZ = True # 보여지는 부분 템플릿과 폼에만 적용된다.

USE_TZ = False # 모든 경우 즉 datetime 객체애도 적용되어 DB에 timezone설정으로 저장됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 중 무엇이 권장되는 방법일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 DB에 저장하는 시간, 연산하는 시간 등 다루는 모든 시간은 UTC로 통일하는 것이 좋다고한다. 즉 True로 설정하는 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 시간을 다룰 때 일반적으로 어떤 사건의 시간을 지정하는 경우가 많지만 대부분의 경우 해당 시간은 추후 어떤 연산의 소스가 되는 경우가 대부분일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 사용자별로 다양한 시간대에 따라 어떤 사용자는 UTC, 어떤 사용자는 GMT+9 등과 같이 시간을 저장하는 경우 분명히 추후 더 큰 문제가 되어 시간을 허비하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 옵션들도 존재한다.&lt;/p&gt;
&lt;pre id=&quot;code_1665730150366&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LANGUAGE_CODE = &quot;ko-KR&quot; # 언어의 코드를 나타냄 USE_I18N 설정이 적용되려면 활성화되어야 함

USE_I18N = True # 번역 시스템 활성화

USE_L10N = True # 데이터의 현지화된 형식을 활성화(4.0 이상부터 더 이상 사용되지 않음 항상 활성화상태)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/dev/ref/settings/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.djangoproject.com/en/dev/ref/settings/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1665730178013&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Settings | Django documentation | Django&quot; data-og-description=&quot;Django The web framework for perfectionists with deadlines. Toggle theme (current theme: auto) Toggle theme (current theme: light) Toggle theme (current theme: dark) Toggle Light / Dark / Auto color theme Overview Download Documentation News Community Code&quot; data-og-host=&quot;docs.djangoproject.com&quot; data-og-source-url=&quot;https://docs.djangoproject.com/en/dev/ref/settings/&quot; data-og-url=&quot;https://docs.djangoproject.com/en/dev/ref/settings/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/dev/ref/settings/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.djangoproject.com/en/dev/ref/settings/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Settings | Django documentation | Django&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Django The web framework for perfectionists with deadlines. Toggle theme (current theme: auto) Toggle theme (current theme: light) Toggle theme (current theme: dark) Toggle Light / Dark / Auto color theme Overview Download Documentation News Community Code&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.djangoproject.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Django</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/178</guid>
      <comments>https://rookieno.tistory.com/178#entry178comment</comments>
      <pubDate>Fri, 14 Oct 2022 15:51:23 +0900</pubDate>
    </item>
    <item>
      <title>Python 패키징의 역사</title>
      <link>https://rookieno.tistory.com/177</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;pyproject.toml 파일을 보고 찾아보다 파이썬 패키징의 역사까지 보게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. distutils 패키징의 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬은 1991년 처음 만들어졌다. 업계에서는 상당히 옛날이다. 자바 보다 5년 빠르고 구글 검색엔진보다 6년이나 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 지금은 당연히 패키징 저장소가 달려있지만 그 당시에는 패키지 저장소는 물론 패키지를 검색할 수 있는 검색엔진조차도 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 당시 파이썬 개발자들은 파르나소스의 금고 라는 일종의 커뮤니티 사이트를 통해서 패키지를 공유했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 패키지를 설치하는 정해진 방법이 없다보니 각자 설치법을 담은 문서를 패키지와 함께 공유하는 수밖에 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 &quot;abc&quot;라는 패키지를 쓰기위해서 어떤 코드를 어디어디에 넣어라는 식으로 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에 불편함을 느껴 1998년 파이썬 코드를 패키징하고 빌드할수있는 distutils 라는 패키지를 만들게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setup.py 라는 패키지의 메타데이터를 담은 파이썬 스크립트를 사용하는 방식을 제시했다. 그 스크립트들을 이용해서 패키지를 배포하고 설치하는 표준화된 방법을 제공했다.&lt;/p&gt;
&lt;pre id=&quot;code_1665560883284&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from distutils.core import setup

setup(name='foo', version='1.0', py_modules=['foo'],)

#python setup.py sdist 패키지 소스코드 압축
#python setup.py bdist 패키지 바이너리 배포판 생성
#python setup.py install 패키지 설치&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시를 활용함으로써 개발자들은 쉽게 패키지를 공유 가능한 형태로 만들 수 있게 되었고 사용하는 패키지를 공통된 방법으로 설치를 할 수 있게 되었다. 2000년 에 distutils는 파이썬 1.6에 표준 라이브러리로 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. PyPI 패키지 저장소의 등장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 패키지를 빌드하고 설치하는 과정은 표준화 되었다. 그 다음 문제로는 어디서 패키지를 검색할 수 있는지였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 커뮤니티 사이트 등에서 패키지를 공유하고 있어 사용자 입장에서는 자신에게 필요한 패키지를 어디에서 찾아야 할 지 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이래서 탄생한 것이 2003년 파이썬 패키지들의 중앙 인덱스 서버인 PyPI(Python Packaging Index)가 탄생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PyPI는 파이썬 패키지를 쉽게 찾을 수 있게하는 목적으로 탄생했으므로 초기에는 패키지들의 메타데이터 정보들만 제공하는데 초점을 맞췄다. 그래서 찾은 패키지를 다운로드 받기위해서는 다시 외부 사이트에서 받아야하는 단점이 존재했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 불편함과 패키지를 외부 도메인에서 호스팅 하는 것은 보안 취약점 문제가 있으므로 2005년부터는 PyPI에서 패키지를 직접 다운로드 가능하게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. setuptools 새로운 빌드 시스템과 패키지 설치도구&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 빌드,설치,검색 모두 표준화 되었다. 하지만 distutils는 PyPI에서 패키지를 다운 받을 수 없고 패키지간의 의존성 처리도 할 수없는 단순히 소스코드를 패키징하고 설치하는 기능이 전부인 라이브러리였다. 그래서 탄생한 것이 2004년 setuptools이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setuptools는 distutils의 확장판이라고 볼 수 있다 기본 distutils의 기능에 PyPI에 패키지를 업로드 하거나 패키지에 대한 테스트를 수행하는&amp;nbsp; 등 부가 기능을 제공했다.&lt;/p&gt;
&lt;pre id=&quot;code_1665561717865&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from setuptools import setup, find_packages

setup(
    name = 'foo',
    version = '1.0',
    install_requires=['dependency'],
    entry_points={},
    python_requires=&quot;&amp;gt;=2.4&quot;,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시대로 setup.py의 포맷을 그대로 사용하되 문법을 확장하여 의존성, 파이썬 버전, 엔트리 포인트 등을 설정하는 기능을 추가하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 easy_install이라는 도구를 함께 제공했다. 직접 PyPI에서 다운로드 받지 않고도 쉽게 PyPI에 업로드된 패키지를 의존성을 포함하여 설치하는 기능을 제공했다. 또한 큰 의미로는 파이썬 패키징 프로세스를 개발자의 영역과 사용자의 영역으로 구분하게 되었다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 빌드, 업로드, 다운로드, 설치의 단계일때 개발자가 수행하는 빌드, 업로드는 setuptools, 사용자가 수행하는 다운로드, 설치는 easy_install이 수행하도록 도구의 역할을 나누게 된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. pip 개선된 패키지 설치도구&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2008년 파이썬하면 빼놓을 수 없는 pip가 등장하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip은 easy_install을 대체하기 위해 만들어졌다. easy_install은 패키지를 설치하는 기능에는 충실하지만 설치된 패키지를 삭제하거나 설치된 패키지들의 목록을 보여주는 기능등이 없어 패키지 관리 도구로서의 제한적인 불편함이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip은 파이썬 패키지를 설치하고 관리하는 데에 특화된 도구로서, easy_install이 가지고 있던 패키지 관리 측면의 문제들을 해결하면서 git 레포지토리에서 파이썬 패키지를 바로 설치할 수 있게 하는 등의 몇 가지 부가 기능을 함께 제공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 pip은 발표되고 얼마 지나지 않아 easy_install의 역할을 완벽히 대체했고, 2013년 PEP453를 통해 파이썬 3.4부터 파이썬의 디폴트 패키지 인스톨러가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. setuptools와 setup.py의 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 잘 작동하고 있는 것처럼 보이지만 해결할 수 없는 근본적인 문제 몇 가지를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 문제로는 setup.py 파일이 setuptools에 의존적이라는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setuptools는 파이썬의 표준 라이브러리가 아니다! 이는 의도적인 것으로 사람들이 얼마든지 새로운 패키지 빌드 시스템을 만들 수 있고 그렇게 하는 것이 권장되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존적 문제의 예시를 들어보자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;새로 만든 패키지를 빌드하기 위해 setuptools A 버전에서 지원하는 특수한 기능이 요구된다.&lt;/li&gt;
&lt;li&gt;setup.py에 적어둔다.&lt;/li&gt;
&lt;li&gt;그런데 setup.py를 파싱하기 위해서는 시스템에 setuptools가 설치되어 있어야한다.&lt;/li&gt;
&lt;li&gt;시스템에 설치된 setuptools의 버전이 A가 아니라 B라면?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. pyproject.toml의 등장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 처음에 찾아보려 했던 pyproject.toml이 등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setup.py에 근본적인 문제의 원인은 메타데이터를 저장하는 파일이 그 자체로 특정한 빌드시스템(setuptools)에 종속되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위한 방법은 특정 빌드 시스템에 종속되지 않는 선언적인 설정 파일을 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 2016년에 pyproject.toml가 탄생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일의 역할은 단순하다. TOML 포맷으로 만들어진 설정파일이며 파이썬 패키지를 어떻게 빌드하는지 어떤 빌드 시스템을 사용해야하는지를 명시하는 것이다. 물론 setuptools를 빌드 시스템으로 사용하는것도 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1665565203140&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[build-system]
requires = [&quot;setuptools&amp;gt;=42&quot;, &quot;wheel&quot;]


[build-system]
requires = [&quot;flit_core &amp;gt;=2, &amp;lt;4&quot;]
build-backend = &quot;flit_core.buildapi&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;pyproject.toml은 원래 위의 예시처럼 패키지 빌드와 관련된 정보만을 담기 위한 파일로 탄생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그러나 패키지 개발자들은 개발과 관련된 설정 값을 관리하는 용도로도 pyproject.toml을 유용하게 사용할 수 있음을 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그러면서 빌드 전에 수행되어야하는 테스트, 코드 포맷팅 등의 정보를 pyproject.toml에 적어두기 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;대표적으로 포맷팅 도구인 black 테스트 프레임워크 pytest 등이 pyproject.toml에 값을 저장하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/177</guid>
      <comments>https://rookieno.tistory.com/177#entry177comment</comments>
      <pubDate>Wed, 12 Oct 2022 18:04:24 +0900</pubDate>
    </item>
    <item>
      <title>Django UniqueConstraint</title>
      <link>https://rookieno.tistory.com/176</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;번호표라는 모델이 있다고 가정해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서를 정확히 정하기 위해 번호표에 번호는 유니크해야 할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1665124823092&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Numberticket(models.Model):
    user = models.ForeignKey()
    number = models.PositiveIntegerField(unique=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번호표의 번호를 유니크하게 하려면 모델에 필드에 unique=True 옵션을 주면 번호는 유니크하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 생각을 해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 새로운 번호표를 만들어주기위해 날짜가 있고 각 날짜에 번호표가 유니크하게 필요하다고 가정해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 날짜, 번호를 묶어서 유니크한 필드로 구성해야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;django 에서는 unique_together, UniqueConstraint로 2개 이상의 필드를 묶어 유니크하게 동작하도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그러나 장고 공식 문서에 따르면 UniqueConstraint를 사용하는 것을 권장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그 이유로는 unique_together에 이렇게 명시되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;대신 constraints 옵션과 함께 UniqueConstraint를 사용하십시오.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;UniqueConstraint는 unique_together보다 더 많은 기능을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;unique_together는 향후 더 이상 사용되지 않을 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권장하는 방식으로 구현해보자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1665124840237&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Numberticket(models.Model):
    user = models.ForeignKey()
    number = models.PositiveIntegerField()
    date = models.DateField()

    class Meta:
        constraints=[
        	models.UniqueConstraint(
            fields=[&quot;date&quot;, &quot;number&quot;],
            name=&quot;date_numberticket&quot;
            )
        ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시대로 구현해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번호표의 각 날짜별 번호는 유니크하게 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Django</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/176</guid>
      <comments>https://rookieno.tistory.com/176#entry176comment</comments>
      <pubDate>Fri, 7 Oct 2022 16:06:14 +0900</pubDate>
    </item>
    <item>
      <title>Python Celery</title>
      <link>https://rookieno.tistory.com/175</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 작업을 실행하고 완료까지 대략 10초정도가 걸리는 작업이 있다고 가정해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 작업을 실행하고 완료되기까지 10초정도 사이트가 멈출 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 비동기 작업의 필요성을 느끼고 비동기로 실행시킬 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 생각해보자 비동기 작업을 실행해놓고 다른 작업들을 할 수 있지만 동시에 대용량 작업을 처리하거나 무거운 연산이 포함된다면 서비스에 장애가 발생하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 작업마다 소요시간이 다름, 작업이 누락되지 않아야함 등 여러가지 중요한 부분들이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 비동기 작업 큐라는 것으로 작업을 관리하고 작업자에게 제대로 전달하기 위해 중간에 브로커가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;python에서 비동기 작업 큐를 활용할 수 있는게 celery이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;python celery&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;celery 공식문서보면 실시간 처리에 중점을 두고 작업 예약을 지원하는 작업 큐라고 적혀있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업예약은 celery beat라는 것으로 스케줄링 할 수 있다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;celery의 구성요소는 크게 3가지이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;broker: task를 worker에게 전달하는 역할 (메세지)&lt;/li&gt;
&lt;li&gt;client: task를 생성하는 역할&lt;/li&gt;
&lt;li&gt;worker: task를 실행하고 처리하는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;celery의 특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연결 유실이나 실패에 대해 자동으로 재시도한다.&lt;/li&gt;
&lt;li&gt;분당 수백만 개의 작업을 처리할 수 있을정도로 빠르다.&lt;/li&gt;
&lt;li&gt;확장성이 뛰어나 커스텀이 용이하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.celeryq.dev/en/latest/getting-started/first-steps-with-celery.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.celeryq.dev/en/latest/getting-started/first-steps-with-celery.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1665045944146&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;First Steps with Celery &amp;mdash; Celery 5.3.0b1 documentation&quot; data-og-description=&quot;This document describes the current stable version of Celery (5.3). For development docs, go here. First Steps with Celery Celery is a task queue with batteries included. It&amp;rsquo;s easy to use so that you can get started without learning the full complexities&quot; data-og-host=&quot;docs.celeryq.dev&quot; data-og-source-url=&quot;https://docs.celeryq.dev/en/latest/getting-started/first-steps-with-celery.html&quot; data-og-url=&quot;https://docs.celeryq.dev/en/latest/getting-started/first-steps-with-celery.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.celeryq.dev/en/latest/getting-started/first-steps-with-celery.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.celeryq.dev/en/latest/getting-started/first-steps-with-celery.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;First Steps with Celery &amp;mdash; Celery 5.3.0b1 documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This document describes the current stable version of Celery (5.3). For development docs, go here. First Steps with Celery Celery is a task queue with batteries included. It&amp;rsquo;s easy to use so that you can get started without learning the full complexities&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.celeryq.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <author>rookieno</author>
      <guid isPermaLink="true">https://rookieno.tistory.com/175</guid>
      <comments>https://rookieno.tistory.com/175#entry175comment</comments>
      <pubDate>Thu, 6 Oct 2022 17:44:12 +0900</pubDate>
    </item>
  </channel>
</rss>