How to Build a High‑Performance Sign‑In System with Spring Boot & Redis BitMap

This article walks through the design and production‑grade implementation of a high‑throughput sign‑in service using Spring Boot, Redis BitMap, Lua scripts, and asynchronous reward processing, covering why traditional relational tables fall short, key generation, offset calculation, continuous‑sign‑in logic, architecture layering, scaling, monitoring, and testing strategies.

Cloud Architecture
Cloud Architecture
Cloud Architecture
How to Build a High‑Performance Sign‑In System with Spring Boot & Redis BitMap

Why a Sign‑In System Needs Careful Design

Although a sign‑in button looks simple, in real‑world traffic it must support high DAU, daily unique sign‑ins, calendar queries, continuous‑day counts, reward distribution, and survive peak loads. Using a relational table for each sign‑in quickly leads to storage bloat, heavy indexes, costly month‑calendar queries, write hotspots, and tightly coupled business logic.

Why Redis BitMap Fits the Problem

Redis BitMap is built on the String type and provides O(1) SETBIT and GETBIT operations. One bit represents a single day, so a month needs only 31 bits (~4 bytes) and a year ~46 bytes. For one million users a year of data occupies roughly 46 MB, offering massive space compression and constant‑time reads/writes.

Key Design and Offset Calculation

Key pattern: sign:uid:{userId}:{yyyyMM} (monthly sharding)

Offset: date.getDayOfMonth() - 1 (1 st day → offset 0)

public final class SignKeyBuilder {
    private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");
    private SignKeyBuilder() {}
    public static String monthlyKey(Long userId, LocalDate date) {
        return "sign:uid:" + userId + ":" + date.format(MONTH_FORMATTER);
    }
    public static int offset(LocalDate date) {
        return date.getDayOfMonth() - 1;
    }
}

Atomic Sign‑In with Lua

To guarantee idempotency under concurrency the check‑and‑set must be a single atomic operation. A Lua script does three things: check today’s bit, set it if absent, and set an expiration.

local key = KEYS[1]
local offset = tonumber(ARGV[1])
local expireSeconds = tonumber(ARGV[2])
local old = redis.call('GETBIT', key, offset)
if old == 1 then
    return 0
end
redis.call('SETBIT', key, offset, 1)
if expireSeconds > 0 then
    redis.call('EXPIRE', key, expireSeconds)
end
return 1

Core Service Implementation

The sign() method builds the key and offset, executes the Lua script, and on success records an event in MySQL and publishes an asynchronous reward event. On duplicate sign‑in it returns the current continuous‑day count and month‑sign count.

public SignResult sign(Long userId, LocalDate signDate, String source) {
    String key = SignKeyBuilder.monthlyKey(userId, signDate);
    int offset = SignKeyBuilder.offset(signDate);
    long expireSeconds = TimeUnit.DAYS.toSeconds(expireDays);
    Long scriptResult = stringRedisTemplate.execute(signInScript,
        Collections.singletonList(key),
        String.valueOf(offset), String.valueOf(expireSeconds));
    boolean success = Long.valueOf(1L).equals(scriptResult);
    if (!success) {
        int continuous = calculateContinuousDays(userId, signDate);
        int monthCount = monthlySignedCount(userId, signDate);
        return SignResult.builder()
            .userId(userId).signDate(signDate).success(false)
            .alreadySigned(true).continuousDays(continuous)
            .monthlySignedDays(monthCount).build();
    }
    // record event and publish reward
    // ... (omitted for brevity)
    return SignResult.builder()
        .userId(userId).signDate(signDate).success(true)
        .alreadySigned(false).continuousDays(calculateContinuousDays(userId, signDate))
        .monthlySignedDays(monthlySignedCount(userId, signDate)).build();
}

Continuous‑Sign‑In Calculation

Redis BITFIELD reads the bits for the current month up to today, then a simple loop counts trailing 1s from the least‑significant bit, which corresponds to the most recent days.

public int calculateContinuousDays(Long userId, LocalDate date) {
    String key = SignKeyBuilder.monthlyKey(userId, date);
    int day = date.getDayOfMonth();
    List<Long> result = stringRedisTemplate.opsForValue().bitField(
        key, BitFieldSubCommands.create()
            .get(BitFieldSubCommands.BitFieldType.unsigned(day))
            .valueAt(0));
    if (result == null || result.isEmpty() || result.get(0) == null) return 0;
    long value = result.get(0);
    int count = 0;
    for (int i = 0; i < day; i++) {
        if ((value & 1) == 0) break;
        count++;
        value >>= 1;
    }
    return count;
}

Monthly Sign‑In Count

public int monthlySignedCount(Long userId, LocalDate date) {
    String key = SignKeyBuilder.monthlyKey(userId, date);
    Long count = stringRedisTemplate.execute(conn -> conn.stringCommands().bitCount(key.getBytes()));
    return count == null ? 0 : count.intValue();
}

Production‑Grade Architecture

The system is split into four layers:

Access layer – authentication, rate‑limiting, tracing.

Business layer – sign‑in idempotency, rule validation, continuous‑day logic.

Cache layer – Redis BitMap stores real‑time sign‑in state.

Async event layer – MQ/Stream publishes a sign_success event for reward distribution, notification, and accounting.

Separating state (Redis) from detail (MySQL) enables fast reads while keeping an audit trail and supporting compensation.

Reward Decoupling

After a successful sign‑in the service publishes a domain event. A separate consumer (e.g., RocketMQ) handles point grants, coupons, or other incentives. This avoids inflating the sign‑in latency and isolates downstream failures.

Handling Failures

If Redis succeeds but MySQL fails, the sign‑in is considered successful; a compensation task later reconciles missing event rows.

If MQ delivery fails, the event row stays with reward_status = 0 and a scheduled job retries.

When Redis is unavailable, the service can either fail fast, fall back to DB writes, or circuit‑break based on business priority.

Scaling and Hotspot Mitigation

Gateway‑level rate limiting and per‑user throttling protect against burst traffic (e.g., 0‑hour sign‑in rush).

Redis Cluster sharding spreads keys across nodes; each user’s monthly key is independent, avoiding single‑key hotspots.

Lua scripts are lighter than distributed locks and guarantee atomicity without extra round‑trips.

Monitoring & Alerting

Key metrics include sign‑in QPS, TP99 latency, success rate, duplicate‑sign ratio, Redis command latency, connection pool usage, and MQ backlog. Alerts trigger on error‑rate > 1 % or Redis latency > 10 ms.

Testing Strategy

Unit tests verify offset logic, leap‑year handling, and cross‑month continuous calculation.

Integration tests with Testcontainers spin up a real Redis instance and run concurrent sign‑in threads to ensure only one succeeds.

Load tests simulate peak QPS, downstream MQ latency, and Redis failures to validate fallback paths.

Key Takeaways

Redis BitMap provides ultra‑compact storage and O(1) operations ideal for daily boolean states.

Lua scripts guarantee atomic check‑and‑set, eliminating race conditions without heavy distributed locks.

Separating real‑time state (Redis) from durable events (MySQL) and async reward processing (MQ) yields a scalable, fault‑tolerant sign‑in service.

Proper key design, monitoring, and compensation mechanisms are essential for production reliability.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

redisspring-bootBitmaphigh performancesign-in
Cloud Architecture
Written by

Cloud Architecture

Focuses on cloud‑native and distributed architecture engineering, sharing practical solutions and lessons learned. Covers microservice governance, Kubernetes, observability, and stability engineering to help your systems run stable, fast, and cost‑effectively.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.