Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 1 addition & 28 deletions src/main/java/com/project/domain/family/entity/Family.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.project.domain.family.entity;

import java.time.LocalDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
Expand Down Expand Up @@ -32,36 +30,11 @@ public class Family extends BaseEntity {
@Column(name = "created_by_id", nullable = false)
private Long createdById;

@Column(name = "total_quota_bytes", nullable = false)
private Long totalQuotaBytes;

@Column(name = "used_bytes", nullable = false)
private Long usedBytes;

@Column(name = "current_month", nullable = false)
private LocalDate currentMonth;

@Builder
public Family(
Long id,
String name,
Long createdById,
Long totalQuotaBytes,
Long usedBytes,
LocalDate currentMonth) {
public Family(Long id, String name, Long createdById) {
this.id = id;
this.name = name;
this.createdById = createdById;
this.totalQuotaBytes = totalQuotaBytes;
this.usedBytes = usedBytes;
this.currentMonth = currentMonth;
}

public double calculateUsedPercent() {
if (totalQuotaBytes == null || totalQuotaBytes == 0) {
return 0.0;
}
return (double) usedBytes / totalQuotaBytes * 100.0;
}

public void changeName(String name) {
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/com/project/domain/family/entity/FamilyQuota.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.project.domain.family.entity;

import java.time.LocalDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

import com.project.global.util.BaseEntity;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(
name = "family_quota",
uniqueConstraints =
@UniqueConstraint(
name = "uk_family_quota_family_month",
columnNames = {"family_id", "current_month", "deleted_at"}))
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FamilyQuota extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "family_id", nullable = false)
private Long familyId;

@Column(name = "current_month", nullable = false)
private LocalDate currentMonth;

@Column(name = "total_quota_bytes", nullable = false)
private Long totalQuotaBytes;

@Column(name = "used_bytes", nullable = false)
private Long usedBytes;

@Builder
public FamilyQuota(
Long id, Long familyId, LocalDate currentMonth, Long totalQuotaBytes, Long usedBytes) {
this.id = id;
this.familyId = familyId;
this.currentMonth = currentMonth;
this.totalQuotaBytes = totalQuotaBytes;
this.usedBytes = usedBytes;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.project.domain.family.repository;

import java.time.LocalDate;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.project.domain.family.entity.FamilyQuota;

public interface FamilyQuotaRepository extends JpaRepository<FamilyQuota, Long> {

@Query(
"""
SELECT fq
FROM FamilyQuota fq
WHERE fq.familyId = :familyId
AND fq.currentMonth = :currentMonth
AND fq.deletedAt IS NULL
""")
Optional<FamilyQuota> findActiveByFamilyIdAndCurrentMonth(
@Param("familyId") Long familyId, @Param("currentMonth") LocalDate currentMonth);

Optional<FamilyQuota> findTopByFamilyIdAndDeletedAtIsNullOrderByCurrentMonthDesc(Long familyId);

@Query(
value =
"""
SELECT fq.*
FROM family_quota fq
WHERE fq.family_id = :familyId
AND fq.deleted_at IS NULL
ORDER BY fq.current_month DESC, fq.id DESC
LIMIT 1
FOR UPDATE
""",
nativeQuery = true)
Optional<FamilyQuota> findLatestByFamilyIdForUpdate(@Param("familyId") Long familyId);

@Modifying
@Query(
"update FamilyQuota fq "
+ "set fq.usedBytes = fq.usedBytes + :bytesUsed "
+ "where fq.familyId = :familyId "
+ "and fq.currentMonth = :currentMonth "
+ "and fq.deletedAt is null")
int incrementUsedBytes(
@Param("familyId") Long familyId,
@Param("currentMonth") LocalDate currentMonth,
@Param("bytesUsed") Long bytesUsed);
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
package com.project.domain.family.repository;

import java.time.LocalDate;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.project.domain.family.entity.Family;

public interface FamilyRepository extends JpaRepository<Family, Long> {

@Modifying
@Query(
"update Family f "
+ "set f.currentMonth = case when f.currentMonth < :eventMonth then"
+ " :eventMonth else f.currentMonth end, "
+ "f.usedBytes = case "
+ "when f.currentMonth < :eventMonth then :bytesUsed "
+ "when f.currentMonth = :eventMonth then f.usedBytes + :bytesUsed "
+ "else f.usedBytes end, "
+ "f.updatedAt = CURRENT_TIMESTAMP "
+ "where f.id = :familyId "
+ "and f.deletedAt is null")
int updateUsedBytesByEventMonth(
@Param("familyId") Long familyId,
@Param("eventMonth") LocalDate eventMonth,
@Param("bytesUsed") Long bytesUsed);
}
public interface FamilyRepository extends JpaRepository<Family, Long> {}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import com.project.domain.family.repository.FamilyMemberRepository;
import com.project.domain.usage.enums.UsagePersistProcessResult;
import com.project.domain.usage.service.helper.CustomerQuotaWriter;
import com.project.domain.usage.service.helper.FamilyUsageWriter;
import com.project.domain.usage.service.helper.FamilyQuotaWriter;
import com.project.domain.usage.service.helper.UsagePersistEventValidator;
import com.project.domain.usage.service.helper.UsageRecordWriter;
import com.project.global.common.TimeConstants;
Expand All @@ -32,7 +32,7 @@ public class UsagePersistServiceImpl implements UsagePersistService {
private final UsagePersistEventValidator usagePersistEventValidator;
private final UsageRecordWriter usageRecordWriter;
private final CustomerQuotaWriter customerQuotaWriter;
private final FamilyUsageWriter familyUsageWriter;
private final FamilyQuotaWriter familyQuotaWriter;
private final LogSanitizer logSanitizer;

// usage-persist 처리의 전체 흐름을 조율
Expand Down Expand Up @@ -81,7 +81,7 @@ public void persist(EventEnvelope<UsagePersistPayload> envelope, String recordKe

// 4) 허용 이벤트의 월 누적 반영
customerQuotaWriter.persistAllowedQuota(payload, currentMonth, eventId, originEventId);
familyUsageWriter.updateFamilyUsedBytes(
familyQuotaWriter.persistAllowedQuota(
payload.familyId(), currentMonth, payload.bytesUsed(), eventId, originEventId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public class UsageSyncServiceImpl implements UsageSyncService {
private final LogSanitizer logSanitizer;
private final KafkaMetrics kafkaMetrics;

// usage-event dedup TTL
@Value("${app.kafka.dedup.usage-ttl-seconds}")
private long dedupTtlSeconds;

Expand All @@ -58,21 +57,24 @@ public void syncUsage(String eventId, String eventTime, UsagePayload payload) {
LocalDate eventMonth = resolvedEventDateTime.toLocalDate().withDayOfMonth(1);

// 2) Lua 실행에 필요한 Redis 키 생성
String infoKey = redisKeyGenerator.generateFamilyInfoKey(familyId);
String remainingKey = redisKeyGenerator.generateFamilyRemainingKey(familyId);
String infoKey = redisKeyGenerator.generateFamilyInfoKey(familyId, eventMonth);
String remainingKey = redisKeyGenerator.generateFamilyRemainingKey(familyId, eventMonth);
String monthlyKey =
redisKeyGenerator.generateFamilyCustomerMonthlyUsageKey(
familyId, customerId, eventMonth);
String constraintsKey =
redisKeyGenerator.generateFamilyCustomerConstraintsKey(familyId, customerId);
String alertsKey = redisKeyGenerator.generateFamilyAlertsKey(familyId);
String alert50Key = redisKeyGenerator.generateFamilyAlertKey(familyId, 50, eventMonth);
String alert30Key = redisKeyGenerator.generateFamilyAlertKey(familyId, 30, eventMonth);
String alert10Key = redisKeyGenerator.generateFamilyAlertKey(familyId, 10, eventMonth);
String dedupKey = redisKeyGenerator.generateUsageEventDedupKey(eventId);

// 3) Redis warmup 보장
boolean familyInfoRedisWarmup =
usageRedisWarmupHelper.ensureFamilyInfoCached(familyId, infoKey);
usageRedisWarmupHelper.ensureFamilyInfoCached(familyId, eventMonth, infoKey);
boolean familyRemainingRedisWarmup =
usageRedisWarmupHelper.ensureRemainingBytesCached(familyId, remainingKey);
usageRedisWarmupHelper.ensureRemainingBytesCached(
familyId, eventMonth, remainingKey);
boolean customerMonthlyUsageRedisWarmup =
usageRedisWarmupHelper.ensureCustomerUsageCached(
familyId, customerId, monthlyKey, eventMonth);
Expand All @@ -96,7 +98,9 @@ public void syncUsage(String eventId, String eventTime, UsagePayload payload) {
remainingKey,
monthlyKey,
constraintsKey,
alertsKey,
alert50Key,
alert30Key,
alert10Key,
dedupKey,
usageBytes,
currentHhmm,
Expand Down
Loading
Loading