Skip to content

Task 1-1: Externalize Redis TTL từ Hardcoded 11 Seconds

Phase: 1 Priority: Medium Module: RedisMachineRepository Depends on: Không có Reference: docs/BountyHunter-ControlServer/details/feature-machine-management/SPEC.md

Background

RedisMachineRepository.save() gọi jedis.expire(key, 11) — TTL 11 giây được hardcode trực tiếp trong source code. Nếu heartbeat interval của machine được điều chỉnh (ví dụ từ 5s lên 15s do network optimization), Redis key sẽ expire trước khi heartbeat tiếp theo tới, khiến machine bị mark OFFLINE sai trên dashboard. Ngược lại, nếu muốn giảm TTL để phát hiện disconnection nhanh hơn, developer phải sửa code và redeploy thay vì chỉ thay đổi config. Magic number 11 không có documentation về lý do chọn giá trị này.

Tasks

Note: RedisMachineRepository là Spring Bean — dùng constructor injection để nhận redisTtlSeconds từ @Value annotation. Không dùng field injection (@Autowired trên field) để tránh khó test. RedisConfig hoặc RedisProperties class nên encapsulate tất cả Redis-related config. Validate TTL > 0 trong constructor để fail fast.

  • [ ] Thêm property vào config.properties:
# Redis TTL for machine status keys.
# Rule: TTL >= 2.5 * heartbeat_interval_seconds to allow for network jitter.
# Default heartbeat: 5s → TTL should be >= 12s. Set 30s for safe margin.
redis.machine.ttl-seconds=30
  • [ ] Update RedisConfig (hoặc tạo RedisProperties) để load property:
public class RedisConfig {
    private final int machineTtlSeconds;

    public static RedisConfig fromProperties(Properties props) {
        int ttl = Integer.parseInt(props.getProperty("redis.machine.ttl-seconds", "30"));
        if (ttl <= 0) {
            throw new IllegalArgumentException("redis.machine.ttl-seconds must be positive, got: " + ttl);
        }
        return new RedisConfig(/* other fields */, ttl);
    }

    public int getMachineTtlSeconds() { return machineTtlSeconds; }
}
  • [ ] Update RedisMachineRepository constructor và save() method:
public class RedisMachineRepository {
    private final JedisPool jedisPool;
    private final int ttlSeconds;  // từ config

    public RedisMachineRepository(JedisPool jedisPool, int ttlSeconds) {
        this.jedisPool = jedisPool;
        this.ttlSeconds = ttlSeconds;
    }

    public void save(int status, String macIp) {
        String key = "RCraneMachineStatus:" + macIp;
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.hset(key, "status", String.valueOf(status));
            jedis.expire(key, ttlSeconds);  // ← dùng config value thay vì 11
        }
    }
}
  • [ ] Document trong code comment: TTL recommendation formula:
// TTL should be at least 2.5× the machine heartbeat interval.
// Example: heartbeat every 5s → TTL = 13s minimum, recommended 30s for safety.
// Configure via redis.machine.ttl-seconds in config.properties.
jedis.expire(key, ttlSeconds);
  • [ ] Tìm tất cả hardcoded 11 trong RedisMachineRepository và thay thế bằng ttlSeconds

Verification / Acceptance Criteria

  • [ ] jedis.ttl("RCraneMachineStatus:<mac>") trả đúng giá trị từ redis.machine.ttl-seconds config (không phải 11)
  • [ ] Thay đổi redis.machine.ttl-seconds=60 trong config → sau khi restart, TTL là 60 giây — không cần code change
  • [ ] Giá trị redis.machine.ttl-seconds=0 hoặc âm → application fail fast với IllegalArgumentException khi startup
  • [ ] Unit test: RedisMachineRepository(mockJedisPool, 30).save(0, "AA:BB") → verify jedis.expire("RCraneMachineStatus:AA:BB", 30) được gọi
  • [ ] Không còn magic number 11 trong source code của RedisMachineRepository

Files to Modify

  • src/main/java/.../RedisMachineRepository.java
  • src/main/java/.../RedisConfig.java (hoặc RedisProperties.java)
  • src/main/resources/config.properties