diff --git a/config/settings.py b/config/settings.py index 04f3e1d..d92bc75 100644 --- a/config/settings.py +++ b/config/settings.py @@ -331,4 +331,16 @@ APP_LOGGER='django' # YOLO 모델 디렉터리 설정 -MODEL_DIR = os.path.join(BASE_DIR, "mission", "yolomodels") \ No newline at end of file +MODEL_DIR = os.path.join(BASE_DIR, "mission", "yolomodels") + +# 캐시 설정 +CACHES = { + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': f'redis://{env('CHANNEL_HOST')}:6379/1', + 'OPTION': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + 'TIMEOUT': 600, # cache 유효 기간을 10분으로 설정합니다. + } + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 285e231..8e47d54 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/tour/consumers.py b/tour/consumers.py index 999c69f..1d4bdd0 100644 --- a/tour/consumers.py +++ b/tour/consumers.py @@ -5,9 +5,31 @@ from services.tour_api import * import urllib.parse import logging +from django.core.cache import cache logger = logging.getLogger(APP_LOGGER) class TaskConsumer(AsyncWebsocketConsumer): + needCachingCategories = [] + sigunguName = '' + areaCode = 1 + result_dic = dict() + """ + '관광지': '12', + '문화시설': '14', + '축제공연행사': '15', + '레포츠': '28', + '숙박': '32', + '쇼핑': '38', + '음식점': '39' + { + "12": [ + { + "address": "서울특별시 종로구 사직로 161 (세종로)", + "areaCode": "1", + }, + ] + } + """ async def connect(self): """ ##query_string## @@ -34,7 +56,9 @@ async def connect(self): # 파라미터 파싱 areaCode = params.pop('areaCode', [None])[0] + self.areaCode = areaCode sigunguName = params.pop('sigunguName', [None])[0] + self.sigunguName = sigunguName categoryName = params.pop('categoryName', [None])[0] if areaCode is None or categoryName is None: @@ -62,24 +86,55 @@ async def connect(self): # categoryName → 리스트로 변환 후 task 호출 categoryNames = categoryName.split(',') - task_result = app.send_task( - 'tour.tasks.get_recommended_place_by_category_task', - args=[self.user_id, areaCode, categoryNames, sigunguCodes, Arrange.TITLE_IMAGE.value, self.user_id] # ← 추가됨 - ) + self.needCachingCategories = [] + self.result_dic = dict() + # 캐싱된 데이터는 미리 빼놓고, 캐싱되지 않은 데이터만 AI 요청을 보냅니다. + # celery 코드를 보아하니 카테고리별로 장소들을 가지고 오는 행태를 보이므로 최소한의 카테고리만 보내는 것이 효율적일 것으로 보임 + for each in categoryNames: + value = cache.get(f"{self.areaCode}&{self.sigunguName}&{each}") + if value is None: + self.needCachingCategories.append(each) + else: + logger.debug('key: ' + f'{self.areaCode}&{self.sigunguName}&{each}\n' + + f'value: {value}\ncache 사용됨') + self.result_dic[each] = value + logger.debug('Need AI list: ' + str(self.needCachingCategories)) + if len(self.needCachingCategories) != 0: + task_result = app.send_task( + 'tour.tasks.get_recommended_place_by_category_task', + args=[self.user_id, areaCode, categoryNames, sigunguCodes, Arrange.TITLE_IMAGE.value, self.user_id] # ← 추가됨 + ) - await self.send(text_data=json.dumps({ - 'state': 'OK', - 'Message': { - 'task_id': task_result.task_id, - } - })) + await self.send(text_data=json.dumps({ + 'state': 'OK', + 'Message': { + 'task_id': task_result.task_id, + } + }, ensure_ascii=False)) + else: + await self.send(text_data=json.dumps({ + 'state': 'CACHE_HIT', + 'result': self.result_dic, + }, ensure_ascii=False)) async def disconnect(self, close_code): await self.channel_layer.group_discard(self.user_id, self.channel_name) async def task_update(self, event): + + data = event["message"] + # cache에 데이터를 저장합니다. + for each in self.needCachingCategories: + cache.set( + f'{self.areaCode}&{self.sigunguName}&{each}', + data['result'][each] + ) + self.result_dic[each] = data['result'][each] + logger.debug('key: ' + f'{self.areaCode}&{self.sigunguName}&{each}\n' + + f'value: {self.result_dic[each]}\n이 cache에 저장됨') + data['result'] = self.result_dic # celery 컨테이너에서 보낸 메시지를 클라이언트로 전송 - await self.send(text_data=json.dumps(event["message"], ensure_ascii=False)) + await self.send(text_data=json.dumps(data, ensure_ascii=False)) async def receive(self, text_data=None, bytes_data=None): """