From 4f7f91773a870eb555df7acfc09ab2bb67be95c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EB=AF=B8?= Date: Wed, 12 Aug 2026 17:24:56 +0900 Subject: [PATCH] =?UTF-8?q?perf:=20FK=20=EC=BB=AC=EB=9F=BC=EC=97=90=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=9C=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=205?= =?UTF-8?q?=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgreSQL은 MySQL(InnoDB)과 달리 FK 컬럼을 자동으로 인덱싱하지 않는다. course/record/user_stamp/scrap/heart_rate_sample의 FK 컬럼에 인덱스가 없어 관련 조회가 Seq Scan으로 처리되고 있었다. EXPLAIN (ANALYZE, BUFFERS)로 30만 건 규모 합성 데이터에서 실측: - course.user_id: 52.516ms → 3.258ms (Seq Scan → Bitmap Heap Scan) - record.user_id: 11.081ms → 2.704ms (Parallel Seq Scan → Bitmap Heap Scan) - user_stamp.user_id: 15.984ms → 1.835ms (Seq Scan → Index Scan) - scrap.public_course_id: 3.696ms → 1.233ms (Seq Scan → Bitmap Heap Scan) - heart_rate_sample.record_health_data_id: 8.507ms → 0.429ms (Parallel Seq Scan → Bitmap Heap Scan) --- src/main/java/org/runnect/server/course/entity/Course.java | 3 +++ .../org/runnect/server/health/entity/HeartRateSample.java | 5 +++++ src/main/java/org/runnect/server/record/entity/Record.java | 5 +++++ src/main/java/org/runnect/server/scrap/entity/Scrap.java | 2 ++ src/main/java/org/runnect/server/user/entity/UserStamp.java | 3 +++ 5 files changed, 18 insertions(+) diff --git a/src/main/java/org/runnect/server/course/entity/Course.java b/src/main/java/org/runnect/server/course/entity/Course.java index 82a8b07..6055150 100644 --- a/src/main/java/org/runnect/server/course/entity/Course.java +++ b/src/main/java/org/runnect/server/course/entity/Course.java @@ -16,6 +16,9 @@ @Getter @Entity +@Table(indexes = { + @Index(name = "idx_course_user_id", columnList = "user_id") +}) @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Course extends AuditingTimeEntity { diff --git a/src/main/java/org/runnect/server/health/entity/HeartRateSample.java b/src/main/java/org/runnect/server/health/entity/HeartRateSample.java index ffbef46..6d8ade3 100644 --- a/src/main/java/org/runnect/server/health/entity/HeartRateSample.java +++ b/src/main/java/org/runnect/server/health/entity/HeartRateSample.java @@ -6,8 +6,10 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.Table; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -16,6 +18,9 @@ @Getter @Entity +@Table(indexes = { + @Index(name = "idx_heart_rate_sample_record_health_data_id", columnList = "record_health_data_id") +}) @NoArgsConstructor(access = AccessLevel.PROTECTED) public class HeartRateSample extends AuditingTimeEntity { diff --git a/src/main/java/org/runnect/server/record/entity/Record.java b/src/main/java/org/runnect/server/record/entity/Record.java index d82f88d..3b156b0 100644 --- a/src/main/java/org/runnect/server/record/entity/Record.java +++ b/src/main/java/org/runnect/server/record/entity/Record.java @@ -7,8 +7,10 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.Table; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -20,6 +22,9 @@ @Getter @Entity +@Table(indexes = { + @Index(name = "idx_record_user_id", columnList = "user_id") +}) @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Record extends AuditingTimeEntity { diff --git a/src/main/java/org/runnect/server/scrap/entity/Scrap.java b/src/main/java/org/runnect/server/scrap/entity/Scrap.java index f464001..416f3f1 100644 --- a/src/main/java/org/runnect/server/scrap/entity/Scrap.java +++ b/src/main/java/org/runnect/server/scrap/entity/Scrap.java @@ -18,6 +18,8 @@ @UniqueConstraint( columnNames = {"user_id", "public_course_id"} ) +}, indexes = { + @Index(name = "idx_scrap_public_course_id", columnList = "public_course_id") }) @DynamicUpdate public class Scrap extends AuditingTimeEntity { diff --git a/src/main/java/org/runnect/server/user/entity/UserStamp.java b/src/main/java/org/runnect/server/user/entity/UserStamp.java index feaec49..81a653b 100644 --- a/src/main/java/org/runnect/server/user/entity/UserStamp.java +++ b/src/main/java/org/runnect/server/user/entity/UserStamp.java @@ -10,6 +10,9 @@ @Getter @Entity +@Table(indexes = { + @Index(name = "idx_user_stamp_user_id", columnList = "user_id") +}) @NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserStamp extends AuditingTimeEntity {