wave13: 514/239 — flink/nifi/artemis + K8s/cilium/linkerd2 + ES/OpenSearch/Solr + hadoop/hbase/spark

This commit is contained in:
russell@unturf.com 2026-03-27 17:50:40 -04:00
parent 424a2a7787
commit 8f0bc73afa
32 changed files with 3977 additions and 5 deletions

View file

@ -0,0 +1,91 @@
# artemis-0001: BindingsImpl routeFromCluster O(R×A) → O(R+A)
## Location
`artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/BindingsImpl.java`
Lines 630–664 (`routeFromCluster`)
## Severity
HIGH — called on every clustered message that requires selective ACK routing
## Description
`routeFromCluster` decodes two byte arrays: `ids` (all binding IDs to route to) and
`idsToAck` (subset that require ACK). It builds `idsToAckList` as an `ArrayList<Long>`,
then iterates over `ids` calling `idsToAckList.contains(bindingID)` for each entry.
For R route targets and A ACK targets, this is O(R×A). Since this method is called
on the hot message routing path in clustered deployments, it compounds per message.
## Root Cause
```java
private void routeFromCluster(final Message message, final RoutingContext context,
final byte[] ids) throws Exception {
byte[] idsToAck = (byte[]) message.removeProperty(Message.HDR_ROUTE_TO_ACK_IDS);
List<Long> idsToAckList = new ArrayList<>(); // ← ArrayList
if (idsToAck != null) {
ByteBuffer buff = ByteBuffer.wrap(idsToAck);
while (buff.hasRemaining()) {
idsToAckList.add(buff.getLong()); // populate O(A)
}
}
ByteBuffer buff = ByteBuffer.wrap(ids);
while (buff.hasRemaining()) { // O(R) loop
long bindingID = buff.getLong();
Binding binding = bindingsIdMap.get(bindingID);
if (binding != null) {
if (idsToAckList.contains(bindingID)) { // O(A) scan → O(R×A) total
binding.routeWithAck(message, context);
} else {
binding.route(message, context);
}
}
}
}
```
## Fix
Use a `HashSet<Long>` instead of `ArrayList<Long>` for `idsToAckSet`:
```java
private void routeFromCluster(final Message message, final RoutingContext context,
final byte[] ids) throws Exception {
byte[] idsToAck = (byte[]) message.removeProperty(Message.HDR_ROUTE_TO_ACK_IDS);
Set<Long> idsToAckSet = new HashSet<>(); // ← HashSet
if (idsToAck != null) {
ByteBuffer buff = ByteBuffer.wrap(idsToAck);
while (buff.hasRemaining()) {
idsToAckSet.add(buff.getLong()); // populate O(A)
}
}
ByteBuffer buff = ByteBuffer.wrap(ids);
while (buff.hasRemaining()) { // O(R) loop
long bindingID = buff.getLong();
Binding binding = bindingsIdMap.get(bindingID);
if (binding != null) {
if (idsToAckSet.contains(bindingID)) { // O(1) → O(R+A) total
binding.routeWithAck(message, context);
} else {
binding.route(message, context);
}
}
}
}
```
## Complexity
| | Before | After |
|---|---|---|
| routeFromCluster | O(R×A) | O(R+A) |
Where R=routing targets, A=ACK targets.
At R=100 bindings, A=50 ACKs: 5,000 ops per message → 150 ops (33x improvement).
Under sustained load of 10,000 msg/s this saves ~49.8M operations/second.
## Context
This is in the clustered message routing path — called when the broker receives a
message with `HDR_ROUTE_TO_ACK_IDS` set, routing to multiple queues where some
require acknowledgment. In large clustered deployments with many queues per address,
R and A can be large.

View file

@ -0,0 +1,203 @@
package unit;
import java.util.*;
/**
* artemis-0001: BindingsImpl routeFromCluster O(R×A) → O(R+A)
*
* Simulates the routing logic in:
* artemis-server/.../core/postoffice/impl/BindingsImpl.java
* Method: routeFromCluster (lines 630–664)
*
* Standalone — no JUnit, no Artemis deps.
*/
public class BindingsRouteFromClusterAlgorithm {
// -----------------------------------------------------------------------
// Result types
// -----------------------------------------------------------------------
static class RouteResult {
final long ops;
final List<Long> routedWithAck;
final List<Long> routedWithoutAck;
RouteResult(long ops, List<Long> routedWithAck, List<Long> routedWithoutAck) {
this.ops = ops;
this.routedWithAck = routedWithAck;
this.routedWithoutAck = routedWithoutAck;
}
}
// -----------------------------------------------------------------------
// Slow: ArrayList.contains inside loop (production code)
// -----------------------------------------------------------------------
static RouteResult routeFromClusterSlow(long[] routeIds, long[] ackIds) {
long ops = 0;
// Build idsToAckList as ArrayList (defect)
List<Long> idsToAckList = new ArrayList<>();
for (long id : ackIds) {
idsToAckList.add(id);
}
List<Long> routedWithAck = new ArrayList<>();
List<Long> routedWithoutAck = new ArrayList<>();
for (long bindingID : routeIds) { // O(R) loop
// List.contains: O(A) scan
boolean needsAck = false;
for (Long ackId : idsToAckList) {
ops++;
if (ackId.equals(bindingID)) {
needsAck = true;
break;
}
}
if (needsAck) {
routedWithAck.add(bindingID);
} else {
routedWithoutAck.add(bindingID);
}
}
return new RouteResult(ops, routedWithAck, routedWithoutAck);
}
// -----------------------------------------------------------------------
// Fast: HashSet for O(1) lookup
// -----------------------------------------------------------------------
static RouteResult routeFromClusterFast(long[] routeIds, long[] ackIds) {
long ops = 0;
// Build idsToAckSet as HashSet (fix)
Set<Long> idsToAckSet = new HashSet<>();
for (long id : ackIds) {
ops++;
idsToAckSet.add(id);
}
List<Long> routedWithAck = new ArrayList<>();
List<Long> routedWithoutAck = new ArrayList<>();
for (long bindingID : routeIds) { // O(R) loop
ops++;
if (idsToAckSet.contains(bindingID)) { // O(1)
routedWithAck.add(bindingID);
} else {
routedWithoutAck.add(bindingID);
}
}
return new RouteResult(ops, routedWithAck, routedWithoutAck);
}
// -----------------------------------------------------------------------
// Test harness
// -----------------------------------------------------------------------
public static void main(String[] args) {
int passed = 0;
int total = 0;
// ---- Test 1: basic routing correctness ----
total++;
long[] routes = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L};
long[] acks = {2L, 4L, 6L, 8L, 10L};
RouteResult slow1 = routeFromClusterSlow(routes, acks);
RouteResult fast1 = routeFromClusterFast(routes, acks);
boolean match1 = new HashSet<>(slow1.routedWithAck).equals(new HashSet<>(fast1.routedWithAck)) &&
new HashSet<>(slow1.routedWithoutAck).equals(new HashSet<>(fast1.routedWithoutAck));
if (match1) {
System.out.println("PASS test1: basic routing — ack=" + slow1.routedWithAck.size() +
" noack=" + slow1.routedWithoutAck.size());
passed++;
} else {
System.out.println("FAIL test1: routing mismatch");
}
// ---- Test 2: no ACKs required ----
total++;
long[] noAcks = {};
RouteResult slow2 = routeFromClusterSlow(routes, noAcks);
RouteResult fast2 = routeFromClusterFast(routes, noAcks);
boolean match2 = slow2.routedWithAck.isEmpty() && fast2.routedWithAck.isEmpty() &&
slow2.routedWithoutAck.size() == routes.length &&
fast2.routedWithoutAck.size() == routes.length;
if (match2) {
System.out.println("PASS test2: no-ACK case — all routes without ack");
passed++;
} else {
System.out.println("FAIL test2: no-ACK case mismatch");
}
// ---- Test 3: all ACKs required ----
total++;
RouteResult slow3 = routeFromClusterSlow(routes, routes);
RouteResult fast3 = routeFromClusterFast(routes, routes);
boolean match3 = slow3.routedWithAck.size() == routes.length &&
fast3.routedWithAck.size() == routes.length &&
slow3.routedWithoutAck.isEmpty() && fast3.routedWithoutAck.isEmpty();
if (match3) {
System.out.println("PASS test3: all-ACK case — all routes with ack");
passed++;
} else {
System.out.println("FAIL test3: all-ACK case mismatch");
}
// ---- Test 4: large scale ops ratio ----
total++;
int R = 100; // route targets
int A = 50; // ACK targets
long[] bigRoutes = new long[R];
for (int i = 0; i < R; i++) bigRoutes[i] = i;
long[] bigAcks = new long[A];
for (int i = 0; i < A; i++) bigAcks[i] = i * 2; // every other route needs ACK
RouteResult slowBig = routeFromClusterSlow(bigRoutes, bigAcks);
RouteResult fastBig = routeFromClusterFast(bigRoutes, bigAcks);
boolean matchBig = new HashSet<>(slowBig.routedWithAck).equals(new HashSet<>(fastBig.routedWithAck));
if (!matchBig) {
System.out.println("FAIL test4: large scale result mismatch");
} else {
double ratio = (double) slowBig.ops / fastBig.ops;
if (ratio >= 10.0) {
System.out.printf("PASS test4: large scale slow=%d ops, fast=%d ops, ratio=%.1fx%n",
slowBig.ops, fastBig.ops, ratio);
passed++;
} else {
System.out.printf("FAIL test4: ratio=%.1fx (need >=10x) slow=%d fast=%d%n",
ratio, slowBig.ops, fastBig.ops);
}
}
// ---- Test 5: route IDs not in ACK list ----
total++;
long[] disjointAcks = {200L, 201L, 202L}; // none match routes 0..9
RouteResult slow5 = routeFromClusterSlow(routes, disjointAcks);
RouteResult fast5 = routeFromClusterFast(routes, disjointAcks);
boolean match5 = slow5.routedWithAck.isEmpty() && fast5.routedWithAck.isEmpty();
if (match5) {
System.out.println("PASS test5: disjoint ACK IDs — no routes with ack");
passed++;
} else {
System.out.println("FAIL test5: disjoint ACK IDs mismatch");
}
System.out.println("\n" + passed + "/" + total + " PASS");
if (passed != total) {
System.exit(1);
}
}
}