Retour aux Guides

    SDK, Tuning de Performance & Intégration

    Optimisez le SDK StreamFlow pour votre workload et intégrez-le avec n'importe quel langage ou framework.

    1. Architecture du SDK

    Le SDK StreamFlow expose une seule API avec deux backends de transport :

    ┌──────────────────────────────────────────────┐
    │           StreamFlowClient (API)              │
    │  producer.send()  consumer.poll()  admin.*    │
    ├──────────────────┬───────────────────────────┤
    │  EMBEDDED MODE   │     REMOTE MODE            │
    │  (same JVM)      │     (TCP/network)          │
    │                  │                             │
    │  EmbeddedProducer│     RemoteProducer          │
    │  → ShardRouter   │     → Kafka wire protocol   │
    │  → EventLog      │     → KafkaProtocolServer   │
    │                  │                             │
    │  Latency: ~µs    │     Latency: ~ms            │
    │  Throughput: 3.8M│     Throughput: 358K rec/s   │
    └──────────────────┴───────────────────────────┘

    Différence clé : Le mode embedded n'a pas de tuning réseau, ce sont des appels directs. Le mode remote encapsule un client Kafka et expose le tuning via Properties.

    2. Tuning du Mode Embedded

    Le mode embedded appelle ShardRouter.publish() directement, zéro sérialisation, zéro réseau, zéro copie. Le tuning se fait au niveau du moteur, pas du SDK.

    Configuration du Producer

    Java
    1// No tuning needed, direct engine access
    2StreamFlowClient client = StreamFlowClient.embedded(router, router, eventLog);
    3StreamFlowProducer producer = client.producer();
    4producer.send("orders", "key-1", payload);  // ~µs latency

    Ce que vous POUVEZ tuner (côté moteur)

    PropertyDefaultImpactDescription
    num.event.partitions16ParallelismMore partitions = more parallel writes
    log.segment.bytes1GBSegment rolloverSmaller = faster recovery, larger = fewer files
    index.interval.bytes4096Index densitySmaller = denser index = faster seeks

    Ce que vous NE POUVEZ PAS tuner (fixé par conception)

    • Batch size: contrôlé par l'appelant
    • Compression: non applicable (en mémoire)
    • Acks: toujours synchrone
    • Buffer memory: pas de buffering nécessaire

    Configuration du Consumer

    Java
    1StreamFlowConsumer consumer = client.consumer();  // no group needed in embedded
    2consumer.subscribe("orders");
    3List<StreamFlowRecord> records = consumer.poll(Duration.ofMillis(100));

    Conseils de Performance Embedded

    Java
    1// Tip 1: Use sendBatch for bulk operations (amortizes shard dispatch)
    2List<StreamEvent> batch = buildEventBatch(1000);
    3producer.sendBatch("orders", batch).join();  // 1 dispatch for 1000 events
    4
    5// Tip 2: Use FlatEventBatch directly for maximum throughput
    6// (bypass SDK, call engine directly, 3.8M evt/s)
    7FlatEventBatch flatBatch = new FlatEventBatch(1024, 4 * 1024 * 1024);
    8for (Order order : orders) {
    9    flatBatch.add(order.timestamp(), order.keyHash(), partition,
    10                  order.payload(), 0, order.payload().length);
    11}
    12engine.ingestFlatBatch("orders", partition, flatBatch);
    13// → 3.8M evt/s (bypasses StreamEvent object creation)

    3. Tuning du Mode Remote

    Le mode remote utilise le protocole filaire Kafka (TCP). Le tuning se fait via Properties passées à client.producer(props) ou client.consumer(groupId, props).

    Configuration du Producer

    Java
    1// Default producer, sensible defaults built-in
    2StreamFlowClient client = StreamFlowClient.remote("streamflow:9092");
    3StreamFlowProducer producer = client.producer();

    Valeurs par défaut du SDK (optimisées pour StreamFlow)

    PropertySDK DefaultKafka DefaultPourquoi
    acks1allStreamFlow deferred writes = Kafka acks=1 semantics
    batch.size256KB16KBStreamFlow handles large batches efficiently
    linger.ms10Small wait to accumulate, low latency impact
    buffer.memory64MB32MBLarger buffer for burst handling
    max.in.flight85StreamFlow handles pipelining well
    compression.typesnappynoneReduces network I/O
    retries32147483647SDK handles retries with circuit breaker

    Configuration Haut Débit

    Java
    1Properties props = new Properties();
    2props.put("batch.size", 524288);           // 512KB, fill bigger batches
    3props.put("linger.ms", 10);               // wait 10ms to fill batch
    4props.put("compression.type", "lz4");     // fastest compression
    5props.put("max.in.flight.requests.per.connection", 10);  // more pipelining
    6props.put("buffer.memory", 268435456);     // 256MB buffer
    7
    8StreamFlowProducer producer = client.producer(props);

    Attendu : 350-400K records/sec sur 8-vCPU.

    Configuration Faible Latence

    Java
    1Properties props = new Properties();
    2props.put("batch.size", 16384);            // 16KB, send quickly
    3props.put("linger.ms", 0);                // no wait
    4props.put("compression.type", "none");     // no CPU overhead
    5
    6StreamFlowProducer producer = client.producer(props);

    Attendu : <5ms p99, ~150K records/sec.

    Configuration Durabilité (acks=all)

    Java
    1Properties props = new Properties();
    2props.put("acks", "all");                  // wait for all replicas
    3props.put("retries", 10);
    4props.put("max.in.flight.requests.per.connection", 5);  // required for ordering
    5
    6StreamFlowProducer producer = client.producer(props);

    Attendu : ~100K records/sec, zéro perte de données en cas de défaillance du leader.

    Configuration du Consumer

    Java
    1// Default consumer, auto-commit enabled
    2StreamFlowConsumer consumer = client.consumer("my-group");

    Consumer haut débit

    Java
    1Properties props = new Properties();
    2props.put("fetch.min.bytes", 65536);       // 64KB minimum before returning
    3props.put("fetch.max.wait.ms", 100);       // 100ms long-poll
    4props.put("max.poll.records", 10000);      // large batches per poll
    5props.put("max.partition.fetch.bytes", 4194304);  // 4MB per partition
    6
    7StreamFlowConsumer consumer = client.consumer("fast-group", props);

    Attendu : 500-700K records/sec.

    At-Least-Once Processing

    Java
    1Properties props = new Properties();
    2props.put("enable.auto.commit", "false");  // manual commit
    3props.put("auto.offset.reset", "earliest");
    4
    5StreamFlowConsumer consumer = client.consumer("processor-group", props);
    6consumer.subscribe("orders");
    7
    8while (running) {
    9    List<StreamFlowRecord> records = consumer.poll(Duration.ofMillis(100));
    10    for (StreamFlowRecord r : records) {
    11        processOrder(r);
    12    }
    13    consumer.commitSync();  // commit AFTER processing
    14}

    4. Configuration de la Résilience

    Circuit Breaker (Mode remote uniquement)

    Le SDK inclut un circuit breaker intégré qui protège contre les défaillances en cascade :

    Java
    1import com.streamflow.sdk.resilience.*;
    2
    3// Custom resilience
    4CircuitBreaker cb = new CircuitBreaker("my-producer",
    5    5,          // open after 5 consecutive failures
    6    10_000L     // stay open for 10 seconds
    7);
    8
    9RetryPolicy retry = RetryPolicy.builder()
    10    .maxRetries(5)
    11    .initialDelayMs(100)
    12    .maxDelayMs(5000)
    13    .circuitBreaker(cb)
    14    .build();
    15
    16StreamFlowProducer producer = new RemoteProducer(
    17    "streamflow:9092", new Properties(), retry, cb);

    Machine à états

    CLOSED (normal) → 5 failures → OPEN (fail-fast for 10s)
    OPEN → 10s elapsed → HALF-OPEN (1 test call allowed)
    HALF-OPEN → success → CLOSED
    HALF-OPEN → failure → OPEN

    Embedded mode: Pas de circuit breaker nécessaire, les appels directs n'ont jamais de défaillance réseau.

    5. Intégration Web

    Spring Boot (Java, SDK native)

    XML
    1<dependency>
    2    <groupId>com.streamflow</groupId>
    3    <artifactId>streamflow-sdk</artifactId>
    4    <version>0.1.0-SNAPSHOT</version>
    5</dependency>
    Java
    1@Configuration
    2public class StreamFlowConfig {
    3
    4    @Bean
    5    public StreamFlowClient streamFlowClient(
    6            @Value("${streamflow.servers}") String servers) {
    7        return StreamFlowClient.remote(servers);
    8    }
    9
    10    @Bean
    11    public StreamFlowProducer producer(StreamFlowClient client) {
    12        return client.producer();  // SDK defaults
    13    }
    14}
    15
    16@RestController
    17@RequestMapping("/api/orders")
    18public class OrderController {
    19
    20    private final StreamFlowProducer producer;
    21
    22    @PostMapping
    23    public CompletableFuture<RecordMetadata> create(@RequestBody Order order) {
    24        return producer.send("orders", order.getId(),
    25            objectMapper.writeValueAsBytes(order));
    26    }
    27}

    Quarkus (Java, SDK native)

    Java
    1@ApplicationScoped
    2public class StreamFlowService {
    3    private final StreamFlowProducer producer;
    4
    5    @Inject
    6    public StreamFlowService(
    7            @ConfigProperty(name = "streamflow.servers") String servers) {
    8        StreamFlowClient client = StreamFlowClient.remote(servers);
    9        this.producer = client.producer();
    10    }
    11
    12    public Uni<RecordMetadata> publish(String topic, String key, byte[] value) {
    13        return Uni.createFrom().completionStage(
    14            producer.send(topic, key, value));
    15    }
    16}

    Node.js / TypeScript (via KafkaJS)

    StreamFlow est 100% compatible avec le protocole filaire Kafka. N'importe quel client Kafka fonctionne :

    javascript
    1import { Kafka } from 'kafkajs';
    2
    3const kafka = new Kafka({
    4  clientId: 'my-web-app',
    5  brokers: ['streamflow:9092'],
    6});
    7
    8const producer = kafka.producer();
    9await producer.connect();
    10await producer.send({
    11  topic: 'orders',
    12  messages: [{ key: 'order-123', value: JSON.stringify(order) }],
    13});
    14
    15const consumer = kafka.consumer({ groupId: 'web-app' });
    16await consumer.subscribe({ topic: 'orders', fromBeginning: true });
    17await consumer.run({
    18  eachMessage: async ({ message }) => {
    19    console.log(`${message.key}: ${message.value}`);
    20  },
    21});

    Python (via confluent-kafka)

    python
    1from confluent_kafka import Producer, Consumer
    2import json
    3
    4p = Producer({'bootstrap.servers': 'streamflow:9092'})
    5p.produce('orders', key='order-123', value=json.dumps(order))
    6p.flush()
    7
    8c = Consumer({
    9    'bootstrap.servers': 'streamflow:9092',
    10    'group.id': 'python-app',
    11    'auto.offset.reset': 'earliest'
    12})
    13c.subscribe(['orders'])
    14while True:
    15    msg = c.poll(1.0)
    16    if msg:
    17        print(f'{msg.key()}: {msg.value()}')

    Go (via kafka-go)

    go
    1writer := &kafka.Writer{
    2    Addr:  kafka.TCP("streamflow:9092"),
    3    Topic: "orders",
    4}
    5writer.WriteMessages(ctx, kafka.Message{
    6    Key: []byte("order-123"), Value: orderJSON,
    7})
    8
    9reader := kafka.NewReader(kafka.ReaderConfig{
    10    Brokers: []string{"streamflow:9092"},
    11    Topic:   "orders",
    12    GroupID: "go-app",
    13})
    14msg, _ := reader.ReadMessage(ctx)

    6. Intégration Mobile

    Les apps mobiles NE DOIVENT PAS se connecter directement à StreamFlow. Utilisez une API gateway :

    ┌──────────┐     HTTPS      ┌──────────────┐   StreamFlow SDK   ┌───────────┐
    │  Mobile   │───────────────→│  API Gateway  │──────────────────→│ StreamFlow│
    │  App      │←───────────────│  (Spring/Go)  │←──────────────────│  Cluster  │
    └──────────┘   JSON / SSE    └──────────────┘  producer.send()   └───────────┘

    Gateway (Java + SDK)

    Java
    1@RestController
    2public class MobileGateway {
    3    private final StreamFlowProducer producer;
    4
    5    @PostMapping("/api/v1/events/{topic}")
    6    public CompletableFuture<Map<String, Object>> publish(
    7            @PathVariable String topic,
    8            @RequestBody byte[] payload,
    9            @RequestHeader("X-Device-Id") String deviceId) {
    10        return producer.send(topic, deviceId, payload)
    11            .thenApply(m -> Map.of(
    12                "partition", m.partition(),
    13                "offset", m.offset()));
    14    }
    15
    16    @GetMapping(value = "/api/v1/events/{topic}/stream",
    17                produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    18    public Flux<StreamFlowRecord> stream(@PathVariable String topic) {
    19        StreamFlowConsumer c = client.consumer("mobile-" + topic);
    20        c.subscribe(topic);
    21        return Flux.interval(Duration.ofMillis(100))
    22            .flatMapIterable(tick -> c.poll(Duration.ofMillis(50)));
    23    }
    24}

    iOS Swift

    swift
    1class StreamFlowClient {
    2    let baseURL: URL
    3
    4    func publish(topic: String, key: String, value: Data) async throws -> EventMetadata {
    5        var req = URLRequest(url: baseURL.appending(
    6            path: "api/v1/events/\(topic)"))
    7        req.httpMethod = "POST"
    8        req.httpBody = value
    9        req.setValue(
    10            UIDevice.current.identifierForVendor?.uuidString,
    11            forHTTPHeaderField: "X-Device-Id")
    12        let (data, _) = try await URLSession.shared.data(for: req)
    13        return try JSONDecoder().decode(EventMetadata.self, from: data)
    14    }
    15
    16    func subscribe(topic: String) -> AsyncStream<Data> {
    17        AsyncStream { cont in
    18            let src = EventSource(url: baseURL.appending(
    19                path: "api/v1/events/\(topic)/stream"))
    20            src.onMessage = { cont.yield($0.data) }
    21            cont.onTermination = { _ in src.disconnect() }
    22        }
    23    }
    24}

    Android Kotlin

    kotlin
    1class StreamFlowClient(private val baseUrl: String) {
    2    private val client = OkHttpClient()
    3
    4    suspend fun publish(
    5        topic: String, key: String, value: ByteArray
    6    ): EventMetadata = withContext(Dispatchers.IO) {
    7        val req = Request.Builder()
    8            .url("$baseUrl/api/v1/events/$topic")
    9            .post(value.toRequestBody(
    10                "application/octet-stream".toMediaType()))
    11            .addHeader("X-Device-Id", Settings.Secure.ANDROID_ID)
    12            .build()
    13        client.newCall(req).execute().use { resp ->
    14            Json.decodeFromString(resp.body!!.string())
    15        }
    16    }
    17
    18    fun subscribe(topic: String): Flow<String> = callbackFlow {
    19        val src = EventSources.createFactory(client)
    20            .newEventSource(
    21                Request.Builder()
    22                    .url("$baseUrl/api/v1/events/$topic/stream")
    23                    .build(),
    24                object : EventSourceListener() {
    25                    override fun onEvent(
    26                        es: EventSource, id: String?,
    27                        type: String?, data: String
    28                    ) { trySend(data) }
    29                })
    30        awaitClose { src.cancel() }
    31    }
    32}

    7. Profils de Tuning

    Référence Rapide des Profils SDK

    ProfileModebatch.sizelinger.msacksThroughputLatence
    DefaultRemote256KB11~250K/s~10ms p99
    Max ThroughputRemote512KB101~400K/s~50ms p99
    Low LatencyRemote16KB01~150K/s<5ms p99
    DurabilityRemote64KB5all~100K/s~20ms p99
    EmbeddedEmbeddedN/AN/AN/A3.8M/s~µs

    Quand Utiliser Chaque Mode

    ScénarioModePourquoi
    Unit testsEmbeddedPas de réseau, instantané, déterministe
    BenchmarksEmbeddedMesurer le débit pur du moteur
    Monolith (same JVM)EmbeddedPerformance maximale
    MicroservicesRemoteIsolation réseau
    Multi-languageRemoteCompatible protocole filaire Kafka
    Mobile appsRemote (gateway)HTTPS + SSE/WebSocket
    IoT edgeRemotebatch.size=32KB, linger.ms=100