diff --git a/_data/navigation.yml b/_data/navigation.yml index 4b318f5..ebb6bea 100644 --- a/_data/navigation.yml +++ b/_data/navigation.yml @@ -149,12 +149,12 @@ pop: # - title: "with" # url: /series/principles_of_python/ # -# - title: 고급 파이썬 문법 -# children: + - title: 고급 파이썬 문법 + children: # - title: "데코레이터" # url: /series/principles_of_python/decorator/ # - title: "프로퍼티" # url: /docs/pages/ -# - title: "async await" -# url: /series/principles_of_python/async_await/ + - title: "async await" + url: /series/principles_of_python/async_await/ diff --git a/_posts/2021-08-17-django-1-4-augmented-field.markdown b/_posts/2021-08-17-django-1-4-augmented-field.markdown new file mode 100644 index 0000000..acfef78 --- /dev/null +++ b/_posts/2021-08-17-django-1-4-augmented-field.markdown @@ -0,0 +1,111 @@ +--- +layout: single +title: "S3에 바로 올라가는 ORM 파일 필드 만들기" +date: 2021-08-07 11:10:03 +0900 +categories: [Postgresql, MVCC] +--- + + +## Environment +python 2.7.14 +Django 1.4 (고대 쟝고) +South 1.0.2 +기존 코드를 + + +## 쟝고에 FileField 는 있는데 왜 S3FileField 는 없을까!! + +기존 코드에서는 FileField 를 사용한 뒤, Form 의 save_new() 함수에서 s3 업로드를 진행한 뒤, +S3 경로의 FileField 의 url 을 바꿔치기 하는 방식을 사용하고 있었습니다. + +이 방식을 사용했을 때 + +* 필드가 해야 할 일을 form 이 대신한다. +* column 만 봤을 때는 파일이 s3 에 올라가는지, 로컬(그럴리 없지만)에 저장되는지 알 수 없다. + +라는 문제점이 남게 됩니다. + +```python +# coding=utf-8 +from datetime import datetime + +from django.forms.models import BaseInlineFormSet +from util import upload_image_to_s3 # s3 에 파일을 업로드 하는 util 함수 +from const import UPLOAD_DIRS # S3 버킷 안의 폴더 + +class ImageInlineFormset(BaseInlineFormSet): + + def save_new(self, form, commit=True): + # 기타 여러가지 form set 이 하는 작업들 ... + + # + S3 업로드 작업 + obj = super(ImageInlineFormset, self).save_new(form, commit=False) + image = form.files.get(u'{}-image_url'.format(form.prefix)) + + if image: + filename = u'{:%Y%m%d%H%M%S%f}_{}'.format( + datetime.now(), + image.name, + ) + s3_path = upload_image_to_s3(UPLOAD_DIRS, image, filename) + obj.image_url = s3_path + + if commit: + obj.save() + + return obj +``` + + +## S3FileField +그래서 S3FileField 를 만들었습니다~ + +```python +from datetime import datetime + +from django.db.models import FileField +from south.modelsinspector import add_introspection_rules + +from utils import upload_image_to_s3 + + +class S3FileField(FileField): + + def __init__(self, *args, **kwargs): + super(S3FileField, self).__init__(*args, **kwargs) + + def _upload_to_s3(self, image): + filename = u'{:%Y%m%d%H%M%S%f}_{}'.format( + datetime.now(), + image.name, + ) + return upload_image_to_s3(self.upload_to, image, filename) + + def pre_save(self, model_instance, add): + file = super(FileField, self).pre_save(model_instance, add) + if file and not file._committed: + file.name = self._upload_to_s3(file) + return file + + +add_introspection_rules([], ["^dowant\.promotion\.fields\.S3FileField"]) + +``` + +* pre_save() 를 override 합니다. 여기서 s3 에 업로드 합니다. +* add_introspection_rules 를 추가해야 마이그레이션 파일이 만들어집니다. + + +## 사용 +필드를 만든 후에는 일반 FileField 처럼 사용하면 됩니다. +```python + image = S3FileField( + max_length=300, + upload_to='s3-버킷-안의-폴더/', + storage=FileSystemStorage(location='', base_url='https://s3-bucket-경로'), + ) +``` + + + + diff --git a/_posts/2021-09-19-mysql_char_varchar_text.markdown b/_posts/2021-09-19-mysql_char_varchar_text.markdown new file mode 100644 index 0000000..2580397 --- /dev/null +++ b/_posts/2021-09-19-mysql_char_varchar_text.markdown @@ -0,0 +1,104 @@ +--- +layout: single +title: "Mysql Char vs Varchar vs TEXT 언제 쓰는게 좋을까?" +date: 2021-09-19 11:10:03 +0900 +categories: [Mysql] +--- + +# Char vs Varchar vs TEXT +언제 쓰는게 좋을까? 직접 테스트 해봤습니다. + +# TL; DR + +* (mysql8, utfmb4 에서...) 16000자 이하: varchar < 65535자 이하: TEXT (CHAR 는 왜 쓰는지 모르겠음) +* char 가 varchar 보다 성능이 좋다는 얘기가 있는데, 테스트 해봤을 때 유의미한 차이는 없었습니다. +* varchar 는 저장 공간이 가변이라 disk 낭비가 적으며, index도 됩니다. +* TEXT 는 index 도 안되고 default 값도 안 됩니다만 (^__^) varchar 보다 많이 저장할 수 있습니다. + + +# Char +* 고정 길이 +* 0~255 까지의 길이를 지정할 수 있습니다. (byte 가 아니라 길이) +* 고정 길이 만큼의 disk space 를 차지합니다. +* CHAR(10) 에 'abcd' 를 저장하면, 'abcd' 와 함께 빈 6자리를 채우기 위해 6개의 space (padding) 가 뒤에 채워집니다. +* 채워진 스페이스는 해당 row 를 가져올 때 빼고 가져옵니다. +* PAD_CHAR_TO_FULL_LENGTH 설정이 켜져 있다면 빼지 않고 스페이스도 가져옵니다. +* index 가능 + +# Varchar +* 저장된 문자열 만큼의 disk space 만 차지합니다. +* Char 와 다르게 prefix byte 를 사용합니다. (1~2 byte 남짓을 추가적으로 사용) +* 최대 65535 byte 를 저장합니다. (길이가 아니라 byte) 어떤 character set 을 사용하느냐에 따라 max 길이가 달라집니다. +* 테스트 해보니 (약)16000자가 최대였습니다. (mysql 8.0.25에서 utfmb4 charset 사용) +* index 가능 + +# Text +* varchar 보다 더 큰 값을 가질 수 있습니다. +* index 불가 +* default 값을 가질 수 없습니다. (눈물) +* 테스트 해보니 65535 자를 (길이) 저장할 수 있었습니다. (mysql 8.0.25에서 utfmb4 charset 사용) +* stack overflow 에서 65535 byte 를 저장한다는 글을 많이 볼 수 있었는데, 사실은 byte 가 아니라 글자 수가 65535자 입니다. + + +# VARCHAR 와 Text 길이 테스트 +* python 으로 고정 길이의 문자열을 만듭니다. `'a' * 16125` +* utf8mb4 에서 어떨 때는 16125 보다 긴 컬럼으로 ALTER 할 때 에러 발생, 어떨 때는 16179 보다 길 때 에러 발생... 정확한 기준을 모르겠습니다. +* varchar(16125) 의 경우 정말 `'a' * 16125` 는 할당되지만 `'a' * 16126` 는 할당 안 됬습니다. + + +# CHAR VS VARCHAR 테스트 +애초에 CHAR 의 최대 길이가 255 입니다. 이렇게 작은 값을 read 해 올 때 성능 비교는 의미가 없는 것 같습니다. +500 row 이상을 한 번에 불러오는 경우도 생각해 볼 수 있겠으나... 이건 성능의 문제보다는 pagination 을 안해서 +생기는 문제라고 생각합니다. +* CHAR(200) 과 VARCHAR(200) 을 컬럼을 만듭니다. +``` +create table string_table +( + my_char char(200) null, + my_varchar varchar(16179) null, + my_text text null, + id int auto_increment + primary key +); +``` +* my_char 에 'a' 200 개로 이루어진 문자열을 저장한 컬럼 250개 생성 +* my_carchar 에도 'a' 200 개로 이루어진 문자열을 저장한 컬럼 250개 생성 +* 두 쿼리의 성능 비교 + * `SELECT my_char FROM string_table WHERE my_char IS NOT NULL;` + * `SELECT my_varchar FROM string_table WHERE my_varchar IS NOT NULL;` + +``` +> SELECT my_char FROM string_table WHERE my_char IS NOT NULL +[2021-09-18 23:07:11] 250 rows retrieved starting from 1 in 48 ms (execution: 8 ms, fetching: 40 ms) +> SELECT my_char FROM string_table WHERE my_char IS NOT NULL +[2021-09-18 23:07:36] 250 rows retrieved starting from 1 in 77 ms (execution: 8 ms, fetching: 69 ms) +> SELECT my_char FROM string_table WHERE my_char IS NOT NULL +[2021-09-18 23:07:37] 250 rows retrieved starting from 1 in 70 ms (execution: 9 ms, fetching: 61 ms) +> SELECT my_char FROM string_table WHERE my_char IS NOT NULL +[2021-09-18 23:07:38] 250 rows retrieved starting from 1 in 59 ms (execution: 9 ms, fetching: 50 ms) +> SELECT my_varchar FROM string_table WHERE my_varchar IS NOT NULL +[2021-09-18 23:07:51] 252 rows retrieved starting from 1 in 57 ms (execution: 10 ms, fetching: 47 ms) +> SELECT my_varchar FROM string_table WHERE my_varchar IS NOT NULL +[2021-09-18 23:07:52] 252 rows retrieved starting from 1 in 60 ms (execution: 8 ms, fetching: 52 ms) +> SELECT my_varchar FROM string_table WHERE my_varchar IS NOT NULL +[2021-09-18 23:07:53] 252 rows retrieved starting from 1 in 51 ms (execution: 9 ms, fetching: 42 ms) +> SELECT my_varchar FROM string_table WHERE my_varchar IS NOT NULL +[2021-09-18 23:07:54] 252 rows retrieved starting from 1 in 53 ms (execution: 9 ms, fetching: 44 ms) +> SELECT my_varchar FROM string_table WHERE my_varchar IS NOT NULL +[2021-09-18 23:07:54] 252 rows retrieved starting from 1 in 63 ms (execution: 9 ms, fetching: 54 ms) +``` + +결론: `또이또이` + +# Reference + +https://stackoverflow.com/questions/25300821/difference-between-varchar-and-text-in-mysql/25301046#25301046 + +https://dev.mysql.com/doc/refman/8.0/en/char.html + + + + + + + diff --git a/package-lock.json b/package-lock.json index 5ef93a2..9a4eb66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,24 +47,6 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", - "dev": true - }, - "array-map": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", - "dev": true - }, - "array-reduce": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", - "dev": true - }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -631,7 +613,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -652,12 +635,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -672,17 +657,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -799,7 +787,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -811,6 +800,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -825,6 +815,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -832,12 +823,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -856,6 +849,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -936,7 +930,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -948,6 +943,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1033,7 +1029,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1069,6 +1066,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1088,6 +1086,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1131,12 +1130,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -1443,12 +1444,6 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -1901,16 +1896,10 @@ "dev": true }, "shell-quote": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", - "dev": true, - "requires": { - "array-filter": "~0.0.0", - "array-map": "~0.0.0", - "array-reduce": "~0.0.0", - "jsonify": "~0.0.0" - } + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true }, "snapdragon": { "version": "0.8.2",