> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diyanet.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Instance Senkron

> EventBusRemoteTagBroadcaster, CacheInvalidationIntegrationEvent ve echo guard ile L1 senkronizasyonu

## Sorun: L1 her node'da ayrı

`HybridCache`'in **L2'si (Redis) paylaşımlıdır** — tüm node'lar aynı Redis'i görür, tag mark'ı herkesçe okunur. Ancak **L1 (in-process memory) her node'da ayrıdır**. Bir node tag'i düşürüp kendi L1'ini temizlese de, diğer node'ların L1'inde aynı veri TTL dolana kadar bayat kalır.

Çözüm: bir node tag düşürdüğünde EventBus üzerinden diğer node'lara haber verip onların da **sadece L1'lerini** düşürmesini sağlamak.

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant N1 as Node #1 (origin)
    participant L2 as Redis (L2 paylaşımlı)
    participant Bus as IEventBus (RabbitMQ)
    participant N2 as Node #2
    participant N3 as Node #3

    Note over N1: RemoveByTagAsync("faqs")
    N1->>L2: tag "faqs" invalidate (L1+L2 local)
    N1->>Bus: Publish CacheInvalidationIntegrationEvent<br/>{ Tags: [faqs], SourceNodeId: N1 }
    Bus-->>N1: (echo — kendi event'i)
    Note over N1: SourceNodeId == NodeId → SKIP
    Bus-->>N2: Consume
    N2->>N2: RemoveByTagLocallyAsync("faqs") → sadece L1
    Bus-->>N3: Consume
    N3->>N3: RemoveByTagLocallyAsync("faqs") → sadece L1
```

## `EventBusRemoteTagBroadcaster`

`IRemoteTagBroadcaster`'ın EventBus (MassTransit + RabbitMQ) backend'li implementasyonu (`src/.../Application/SeedWork/Caching/EventBusRemoteTagBroadcaster.cs`). `IHybridRequestCache.RemoveByTagAsync` içinden, `BroadcastTagInvalidation` açıksa tetiklenir.

```csharp theme={null}
public sealed class EventBusRemoteTagBroadcaster : IRemoteTagBroadcaster
{
    // Process yaşam süresi boyunca sabit, process başına unique node kimliği.
    public static readonly string NodeId = Guid.NewGuid().ToString("N");

    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<EventBusRemoteTagBroadcaster> _logger;

    public async ValueTask BroadcastInvalidationAsync(
        IReadOnlyCollection<string> tags, CancellationToken cancellationToken = default)
    {
        if (tags is null || tags.Count == 0) return;

        // Singleton broadcaster, Scoped IEventBus → per-call scope.
        using var scope = _scopeFactory.CreateScope();
        var eventBus = scope.ServiceProvider.GetRequiredService<IEventBus>();

        var evt = new CacheInvalidationIntegrationEvent(tags, NodeId);
        await eventBus.PublishAsync(evt, cancellationToken);
    }
}
```

<Note>
  **Lifetime deseni:** `IHybridRequestCache` Singleton, dolayısıyla broadcaster da Singleton; ama `IEventBus` Scoped. Bu yüzden ctor injection yerine `IServiceScopeFactory` tutulup her broadcast'te scope açılır — standart "Singleton consumes Scoped" pattern'i. Tag invalidation seyrek olduğu için maliyet düşük.
</Note>

`NodeId` bilerek `static`'tir: aynı process içinde hem broadcaster (publish) hem handler (consume) aynı değeri okur — echo tespiti bunun üzerine kuruludur.

## `CacheInvalidationIntegrationEvent`

Yayınlanan event (`src/BuildingBlocks/.../Contracts.Events/Cache/CacheInvalidationIntegrationEvent.cs`):

```csharp theme={null}
[MessageUrn("urn:event:cache-invalidation:v1")]
public sealed class CacheInvalidationIntegrationEvent : IntegrationEvent
{
    public IReadOnlyCollection<string> Tags { get; init; } = Array.Empty<string>();

    // Hangi node yayınladı — receiver kendi event'ini atlamak için karşılaştırır.
    public string SourceNodeId { get; init; } = string.Empty;

    public CacheInvalidationIntegrationEvent() { }
    public CacheInvalidationIntegrationEvent(IReadOnlyCollection<string> tags, string sourceNodeId)
    {
        Tags = tags;
        SourceNodeId = sourceNodeId;
    }
}
```

## Echo guard + cascade önleme

Receiver `CacheInvalidationIntegrationEventHandler` (`src/.../Application/IntegrationEventHandlers/CacheInvalidation/`) iki önlem taşır:

```csharp theme={null}
public async Task Handle(CacheInvalidationIntegrationEvent @event)
{
    // 1) ECHO GUARD: kendi yayınladığımız event'i de görürüz → kendi NodeId'mizi atla.
    if (@event.SourceNodeId == EventBusRemoteTagBroadcaster.NodeId)
    {
        _logger.LogTrace("[CacheBroadcast] Skip echo: tags=[{Tags}]", string.Join(",", @event.Tags));
        return;
    }

    foreach (var tag in @event.Tags)
    {
        try
        {
            // 2) CASCADE ÖNLEME: RemoveByTagLocallyAsync — sadece L1, tekrar broadcast YOK.
            await _cache.RemoveByTagLocallyAsync(tag);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "[CacheBroadcast] Local tag drop failed: tag={Tag}", tag);
        }
    }
}
```

| Önlem                                     | Neyi engeller                                                                                          |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| **Echo guard** (`SourceNodeId == NodeId`) | Origin node kendi broadcast'ini tüketip L1'ini iki kez düşürmesin.                                     |
| **`RemoveByTagLocallyAsync`**             | Receiver tekrar broadcast yapmasın → sonsuz cascade önlenir (`RemoveByTagAsync` broadcast tetiklerdi). |

## Konfigürasyon

```json theme={null}
{
  "Cache": {
    "BroadcastTagInvalidation": true
  }
}
```

| Ortam                         | `BroadcastTagInvalidation`                                                  |
| ----------------------------- | --------------------------------------------------------------------------- |
| Single-node (dev, küçük prod) | `false` — broadcast gereksiz, RabbitMQ trafiği boşuna.                      |
| Multi-instance / multi-pod    | `true` — yoksa diğer node'ların L1'i invalidate edilemez, bayat veri döner. |

Broadcaster registration: `BuildingBlocks.Caching.AddCache` default olarak `NoopRemoteTagBroadcaster`'ı `TryAdd` ile koyar. Application katmanı EventBus aktifken bunu gerçek impl ile **Replace** eder (`AddSingleton`, TryAdd değil):

```csharp theme={null}
// Application/DependencyInjection.cs — EventBus register edildikten SONRA
builder.Services.AddSingleton<IRemoteTagBroadcaster, EventBusRemoteTagBroadcaster>();
```

## İlgili

<CardGroup cols={2}>
  <Card title="Invalidation" icon="trash" href="/caching/invalidation">
    `RemoveByTagAsync` vs `RemoveByTagLocallyAsync`.
  </Card>

  <Card title="Integration Events" icon="arrows-turn-to-dots" href="/events/integration-events">
    `IntegrationEvent` base ve consumer kaydı.
  </Card>

  <Card title="RabbitMQ Topology" icon="rabbit" href="/events/rabbitmq-topology">
    Exchange, routing key, queue.
  </Card>

  <Card title="Cache Mimarisi" icon="database" href="/caching/overview">
    Genel bakış.
  </Card>
</CardGroup>
