Backend Development 14 min read

Request Merging Techniques: Hystrix Collapser, Custom BatchCollapser, and ConcurrentHashMultiset

This article compares three request‑merging approaches—Hystrix Collapser, a custom BatchCollapser implementation, and Guava’s ConcurrentHashMultiset—explaining their configurations, code examples, and suitable scenarios for reducing downstream load and improving system throughput while also highlighting performance trade‑offs and practical tips for integration in Spring‑Boot services.

Top Architect
Top Architect
Top Architect
Request Merging Techniques: Hystrix Collapser, Custom BatchCollapser, and ConcurrentHashMultiset

In many services the traditional request‑response model creates a separate thread and memory space for each call, leading to high I/O cost when many identical requests are processed concurrently. Merging similar requests upstream before forwarding them downstream can dramatically lower the load on downstream systems and increase overall throughput.

The article examines three concrete techniques for request merging in Java/Spring environments:

1. Hystrix Collapser

Hystrix, a Netflix library, provides a built‑in collapser that can batch multiple calls into a single request. It works together with the hystrix-javanica annotation module, requiring only two annotations: @HystrixCollapser(batchMethod="batch") on the single request method and @HystrixCommand on the batch method. The collapser stores pending requests in a ConcurrentHashMap , creates an Observable, and returns a Future to the caller. Important configuration items include collapserKey , batchMethod , scope (REQUEST vs GLOBAL), and properties such as maxRequestsInBatch and timerDelayInMilliseconds . A typical configuration looks like:

@HystrixCollapser(
    batchMethod = "batch",
    collapserKey = "single",
    scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
    collapserProperties = {
        @HystrixProperty(name = "maxRequestsInBatch", value = "100"),
        @HystrixProperty(name = "timerDelayInMilliseconds", value = "1000"),
        @HystrixProperty(name = "requestCache.enabled", value = "true")
    })
public Future
single(String input) { return null; }

public List
batch(List
inputs) {
    return inputs.stream().map(i -> Boolean.TRUE).collect(Collectors.toList());
}

2. Custom BatchCollapser

When the result of individual requests is not needed, a lightweight collapser can be built that simply accumulates request parameters in a thread‑safe container and triggers a batch execution either after a time interval or when a request count threshold is reached. The implementation uses a LinkedBlockingDeque as the container and a ScheduledExecutorService to run a periodic clean‑up task. Example skeleton:

public class BatchCollapser
implements InitializingBean {
    private static final ScheduledExecutorService SCHEDULE_EXECUTOR = Executors.newScheduledThreadPool(1);
    private volatile LinkedBlockingDeque
batchContainer = new LinkedBlockingDeque<>();
    private Handler
, Boolean> cleaner;
    private long interval;
    private int threshHold;

    public BatchCollapser(Handler
, Boolean> cleaner, int threshHold, long interval) {
        this.cleaner = cleaner;
        this.threshHold = threshHold;
        this.interval = interval;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        SCHEDULE_EXECUTOR.scheduleAtFixedRate(() -> {
            try { clean(); } catch (Exception e) { logger.error("clean container exception", e); }
        }, 0, interval, TimeUnit.MILLISECONDS);
    }

    public void submit(E event) {
        batchContainer.add(event);
        if (batchContainer.size() >= threshHold) { clean(); }
    }

    private void clean() {
        List
transferList = new ArrayList<>();
        batchContainer.drainTo(transferList, 100);
        if (transferList.isEmpty()) return;
        try { cleaner.handle(transferList); } catch (Exception e) { logger.error("batch execute error", e); }
    }
}

3. ConcurrentHashMultiset (Guava)

For scenarios with extremely high duplicate request rates, Guava’s ConcurrentHashMultiset provides a lock‑free multiset that counts occurrences of each element. It is ideal for in‑memory aggregation such as counting clicks, votes, or other statistics before persisting them in batches. A typical usage pattern:

if (ConcurrentHashMultiset.isEmpty()) return;
List
transferList = new ArrayList<>();
ConcurrentHashMultiset.elementSet().forEach(request -> {
    int count = ConcurrentHashMultiset.count(request);
    if (count <= 0) return;
    transferList.add(count == 1 ? request : new Request(request.getIncrement() * count));
    ConcurrentHashMultiset.remove(request, count);
});
// process transferList in a single batch operation

Summary of suitable scenarios :

Hystrix Collapser – when each individual response is required and the extra latency introduced by the collapser timer is acceptable.

Custom BatchCollapser – when the result of individual calls is irrelevant and merging can be triggered by time or count thresholds.

ConcurrentHashMultiset – when requests have a very high duplication rate and the goal is to aggregate counts efficiently in memory.

The article also suggests that a hybrid approach could combine the container logic of ConcurrentHashMultiset with the timing/threshold mechanism of BatchCollapser to gain the benefits of both.

backendbatch processingSpring BootGuavaHystrixRequest Collapsing
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login 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.