diff --git a/data/generators/redis_matching_generator.py b/data/generators/redis_matching_generator.py index 439297b7..9f075ccf 100755 --- a/data/generators/redis_matching_generator.py +++ b/data/generators/redis_matching_generator.py @@ -104,6 +104,11 @@ def _get_random_tag_ids_by_type(self, tag_type, min_count=1, max_count=3): count = random.randint(min_count, min(max_count, len(available_ids))) return random.sample(available_ids, count) + @staticmethod + def _to_str_list(int_list): + """RediSearch TAG 필드는 문자열만 허용하므로 정수 리스트를 문자열 리스트로 변환""" + return [str(v) for v in int_list] if int_list else [] + def generate_brand_documents(self): print("\n[브랜드] Redis 브랜드 태그 문서 생성 중...") @@ -165,19 +170,19 @@ def generate_brand_documents(self): 'brandId': brand_id, 'brandName': brand['brand_name'], 'categories': categories, - 'preferredFashionTags': fashion_tags, - 'preferredBeautyTags': beauty_tags, - 'preferredContentTags': content_tags, + 'preferredFashionTags': self._to_str_list(fashion_tags), + 'preferredBeautyTags': self._to_str_list(beauty_tags), + 'preferredContentTags': self._to_str_list(content_tags), 'minCreatorHeight': random.choice([None, 150, 155, 160]), 'maxCreatorHeight': random.choice([None, 175, 180, 185]), - 'preferredBodyTypeTags': body_type_tags, - 'preferredTopSizeTags': [s for s in random.sample(range(44, 110, 2), 3)], - 'preferredBottomSizeTags': [s for s in random.sample(range(24, 36), 3)], + 'preferredBodyTypeTags': self._to_str_list(body_type_tags), + 'preferredTopSizeTags': self._to_str_list(random.sample(range(44, 110, 2), 3)), + 'preferredBottomSizeTags': self._to_str_list(random.sample(range(24, 36), 3)), 'minContentsAverageViews': random.choice([None, 10000, 50000, 100000]), 'maxContentsAverageViews': random.choice([None, 500000, 1000000]), - 'preferredContentsAgeTags': audience_age_tags, - 'preferredContentsGenderTags': audience_gender_tags, - 'preferredContentsLengthTags': video_length_tags, + 'preferredContentsAgeTags': self._to_str_list(audience_age_tags), + 'preferredContentsGenderTags': self._to_str_list(audience_gender_tags), + 'preferredContentsLengthTags': self._to_str_list(video_length_tags), } key = f"com.example.RealMatch.match.infrastructure.redis.document.BrandTagDocument:brand:{brand_id}" @@ -225,21 +230,21 @@ def generate_campaign_documents(self): 'rewardAmount': float(campaign['reward_amount']) if campaign['reward_amount'] else None, 'recruitEndDate': campaign['recruit_end_date'].isoformat() if campaign['recruit_end_date'] else None, 'categories': categories, - 'preferredFashionTags': fashion_tags, - 'preferredBeautyTags': beauty_tags, - 'preferredContentTags': content_tags, + 'preferredFashionTags': self._to_str_list(fashion_tags), + 'preferredBeautyTags': self._to_str_list(beauty_tags), + 'preferredContentTags': self._to_str_list(content_tags), 'minCreatorHeight': random.choice([None, 150, 155, 160]), 'maxCreatorHeight': random.choice([None, 175, 180, 185]), - 'preferredBodyTypeTags': body_type_tags, + 'preferredBodyTypeTags': self._to_str_list(body_type_tags), 'minCreatorTopSizes': random.choice([None, 44, 50, 55]), 'maxCreatorTopSizes': random.choice([None, 100, 105, 110]), 'minCreatorBottomSizes': random.choice([None, 24, 26, 28]), 'maxCreatorBottomSizes': random.choice([None, 32, 34, 36]), 'minContentsAverageViews': random.choice([None, 10000, 50000, 100000]), 'maxContentsAverageViews': random.choice([None, 500000, 1000000]), - 'preferredContentsAgeTags': audience_age_tags, - 'preferredContentsGenderTags': audience_gender_tags, - 'preferredContentsLengthTags': video_length_tags, + 'preferredContentsAgeTags': self._to_str_list(audience_age_tags), + 'preferredContentsGenderTags': self._to_str_list(audience_gender_tags), + 'preferredContentsLengthTags': self._to_str_list(video_length_tags), 'startDate': campaign['start_date'].isoformat() if campaign['start_date'] else None, 'endDate': campaign['end_date'].isoformat() if campaign['end_date'] else None, 'quota': campaign['quota'], diff --git a/src/main/java/com/example/RealMatch/match/infrastructure/redis/RedisDocumentHelper.java b/src/main/java/com/example/RealMatch/match/infrastructure/redis/RedisDocumentHelper.java index adba817f..0dff7a1b 100644 --- a/src/main/java/com/example/RealMatch/match/infrastructure/redis/RedisDocumentHelper.java +++ b/src/main/java/com/example/RealMatch/match/infrastructure/redis/RedisDocumentHelper.java @@ -32,6 +32,7 @@ public class RedisDocumentHelper { private static final String CAMPAIGN_INDEX = "com.example.RealMatch.match.infrastructure.redis.document.CampaignTagDocumentIdx"; private static final int SEARCH_LIMIT = 200; + private static final int TOP_MATCH_COUNT = 10; public List findCandidateBrands(UserTagDocument userDoc) { String query = buildTagOverlapQuery( @@ -39,7 +40,7 @@ public List findCandidateBrands(UserTagDocument userDoc) { userDoc.getBeautyTags(), "preferredBeautyTags", userDoc.getContentTags(), "preferredContentTags" ); - return executeSearch(BRAND_INDEX, query, BrandTagDocument.class); + return searchWithFallback(BRAND_INDEX, query, BrandTagDocument.class); } public List findCandidateCampaigns(UserTagDocument userDoc) { @@ -48,7 +49,41 @@ public List findCandidateCampaigns(UserTagDocument userDoc) userDoc.getBeautyTags(), "preferredBeautyTags", userDoc.getContentTags(), "preferredContentTags" ); - return executeSearch(CAMPAIGN_INDEX, query, CampaignTagDocument.class); + return searchWithFallback(CAMPAIGN_INDEX, query, CampaignTagDocument.class); + } + + private List searchWithFallback(String indexName, String query, Class clazz) { + List results = executeSearch(indexName, query, clazz); + + if (results.size() < TOP_MATCH_COUNT && !"*".equals(query)) { + log.info("Tag-based search returned only {} results, falling back to full search. index={}", + results.size(), indexName); + List allResults = executeSearch(indexName, "*", clazz); + + // 태그 매칭된 결과의 key를 기억해서 중복 방지 + Set existingIds = new java.util.HashSet<>(results.stream() + .map(this::extractDocId) + .toList()); + + for (T doc : allResults) { + Object docId = extractDocId(doc); + if (!existingIds.contains(docId)) { + results.add(doc); + } + } + } + + return results; + } + + private Object extractDocId(Object doc) { + if (doc instanceof BrandTagDocument brandDoc) { + return brandDoc.getBrandId(); + } + if (doc instanceof CampaignTagDocument campaignDoc) { + return campaignDoc.getCampaignId(); + } + return doc; } @Deprecated