Distributed Logging (eg: Sumologc, Datadog, Splunk cloud)
This is a multi-tenant distributed version of Sumo Logic, Splunk
Cloud, Datadog Logs, or Grafana Loki, where applications running
across thousands of machines and multiple regions continuously stream
logs to a central platform for indexing, searching, alerting, and
analytics.
Example: Netskope sending(Event logs, application logs, transaction
logs, service logs, alerts), CompanyX(general logs), Domain
Controller(sends syslogs)
Purpose: Distributed logging system will store the logs and
provide dashboard for monitoring.
Requirements
Functional
1. Log Collection: Tenants/Customers/Applications can
send(Application logs, System logs, Audit logs, Security logs,
Kubernetes logs, Container logs, Network device logs. Support multiple
protocols(HTTP HTTPS gRPC TCP UDP (Syslog) Kafka)
2. Highly scalable: Millions of logs/sec
3. Near Real-Time Search(within 2-5 seconds) Formats:
Text Search: (status=500, user=amit, host=server12,
requestId=abc123, service=auth)
Time Range Query: (Last 5 minutes, Last 1 hour, Yesterday, Last
30 days)
Aggregation: (count(), avg(), sum(), percentile(), top(),
histogram(), group by())
4. Send Alerts: If ERROR > 100/sec, Send Slack, PagerDuty,
Email, Webhook
5. Multitenant: 1000s of customers
6. Different Rentention polcies: 7 days, 30 days, 180 days, 1
year
Non-functional Requirements
High availability (99.99%+), Horizontal scalability, Fault tolerance, Low ingestion latency, High write throughput, Eventual consistency for search, Secure transport (TLS), Encryption at rest, RBAC, Cost-efficient storage, Multi-region deployment
HLD
There are 2 independent systems:
1. Storage Pipeline (Write Path): optimized for ingesting millions of
events/sec. Where tenants write the logs and those are stored
2. Query Pipeline (Read Path): optimized for searching petabytes of
historical data. Dashboards where data is searched looked upon for
investigation/forensics
Storage Pipeline (Write Path)
flowchart LR
subgraph TENANT_NS1["Tenant Netskope"]
APP1[POP1
nsproxy]
subgraph Agent Client1
AGENT1[Fluent Bit
Collector
Agent]
COMPRESS1[gzip / zstd]
end
end
subgraph TENANT_NS1["Tenant Netskope"]
APP2[POP2
nsproxy]
subgraph Agent Client2
AGENT2[Collector
Agent]
COMPRESS2[gzip / zstd]
end
end
subgraph Tenant XYZ
APP3[Server]
subgraph AWS
AGENT3[Open Telemetry
Collector
Agent]
COMPRESS3[gzip / zstd]
end
end
APP1 -->|transaction logs
Alerts
Event logs
Log Format1| AGENT1
AGENT1 --> COMPRESS1
APP2 --> AGENT2
AGENT2 --> COMPRESS2
APP3 -->|General logs
Log Format2| AGENT3
AGENT3 --> COMPRESS3
COMPRESS1 -->|"1000 log bundle
HTTP
Header:metadata(tenantid,Timestamp..)
Body:500 log batch logs.zip"| GLB[Global
Load
Balancer]
COMPRESS2 -->|"500 log bundle
HTTP
Header:metadata
Body:logs.zip"| GLB[Global
Load
Balancer]
COMPRESS3 -->|"100 log bundle
HTTP
Header:metadata
Body:logs.zip"| GLB[Global
Load
Balancer]
GLB --> RLB[Regional
Load
Balancer]
RLB --> APIGW[API
Gateway]
subgraph Sumologic Datacenter
APIGW --> AUTH[Tenant Validation Cache]
APIGW --> INGRESS[Kubernetes Ingress
Load Balancer]
INGRESS --> INGEST1[Ingestion
Service Pod1]
INGRESS --> INGEST2[Ingestion
Service Pod2]
INGEST1 -.- Note["1.Add Meta Data to logs
2.Create Kafka Topic"]
style Note fill:#fff9c4,stroke:#fbc02d,stroke-width:1px
INGEST1 -->|validate request| CACHE[(Authentication
CACHE)]
CACHE -->|Validated| INGEST1
INGEST1 -->|Topic=Log
Msg=logs.zip+MetaData| RETRY[Retry Queue]
RETRY --> KAFKA[(Kafka)]
INGEST1 -->|Topic=Log
Msg=logs.zip+MetaData| DLQ[(Dead Letter Queue)]
KAFKA --> Processing_Worker[Workers]
end
Processing_Worker -->|logs.zip| Hot[(Hot Storage
7-30 days
SSSD)]
Processing_Worker -->|logs.zip| Cold[(Cold Storage
1,5,10 years
Amazon S3)]
Processing_Worker -->|Metadata| Meta[(Metadata)]
INGEST1 --> METRICS[Prometheus Metrics]
Collector Agent (Installed on Log Server). Eg: Opentelemetry Agent, Fluent Bit Agent
Suppose one server generates: 100,000 logs/sec. It does not send:
100,000 HTTP requests/sec. Instead it buffers logs. like
500 logs ->One HTTP request
Now authentication will happen on this 1 HTTP request
API Gateway
TLS termination, DDoS, Rate limiting, API key/JWT validation, Routing
Ingestion Service
Validate tenant exists, Validate source type, Attach metadata, send to kafka
Authentication
Imagine Netskope signs up for your logging platform. During onboarding, they receive:
Tenant ID: tenant-3421
API Key: abcXYZ...
Secret: xxxxxxxx
Region: us-east
Retention: 90 days
Their log agent (for example, Fluent Bit or OpenTelemetry Collector) is configured with these credentials. Every request includes headers such as:
POST /v1/logs
Authorization: Bearer eyJhb...
X-Tenant-ID: tenant-3421
X-Source: kubernetes
Content-Encoding: gzip
Content-Type: application/json
Authentication will happen on every batched request. See
Collector agent sends logs in batch of 500,100 logs
At every HTTP request following things can be checked from local cache
tenant, rentention days, quota, region, allowed sources
Pods Scaling
if traffic doubles. Scales itself(HPA. On CPU, memory, request rate,
or queue depth).
6 Pods -> 40 Pods -> 100 Pods
Proccessing at Ingestion Service
Ingestion service will not unzip every log.zip, if Ingestion service
keep unzipping, it will become bottleneck.
With every log message customer also sends Meta data(whose schema is
same for all customers)
Different customers will have different log formats, but same schema
Log format
Company A {"user":"amit","error":"timeout"}
Company B {"employee":"123","status":"failed"}
Meta data schema
Tenant ID, Timestamp, Host, Region, Source, Log Type, Service, Namespace, Cluster, Severity, Trace ID, Span ID, Request ID, Compression Type, Retention Class, Encryption Key ID, Schema Version
Sequence Diagram
sequenceDiagram
autonumber
box Customer Premises POP
participant App as Server Generating Log
participant Collector
end
participant API as API Gateway
box Datacenter
participant Ingest as Ingestion Service
participant Cache as Tenant Cache
participant MetaDB as Metadata DB Batch Level
participant Kafka
end
App->>Collector: Generate log events
Collector->>Collector: Buffer 500 logs / 1 MB / 1 sec
Note over Collector: Compress batch gzip/zstd
500 logs per HTTP POST
Collector->>API: HTTPS POST JWT + Tenant ID
Body logs.zip
API->>API: TLS Rate Limit JWT Validation
API->>Ingest: Forward authenticated request
Ingest->>Cache: Get tenant data
Cache-->>Ingest: Retention Quota Allowed Sources
Note over Ingest: Validate tenant and sources
Note over Ingest: Add metadata
Tenant ID,Region
Source,Log Type
Service,Host
Namespace,Cluster
Retention,Encryption Key ID
Schema Version,Compression Type
Ingestion Timestamp
Ingest->>MetaDB: Store Batch Metadata
(BatchID, TenantID, Time Range, Region,
Source, Log Count, Compression,
Retention, Object ID*)
MetaDB-->>Ingest: Metadata stored
Ingest->>Kafka: Publish internal event batch
Kafka-->>Ingest: ACK
Ingest-->>Collector: HTTP 200 OK
Query Pipeline (Read Path)
Downstream workers: Messages are consumed by downstream
workers, transformed, indexed, and stored.
Dashboard: queries the Search Index (OpenSearch, ClickHouse,
Loki, etc.) and retrieves raw logs from Object Storage only when
necessary
flowchart LR
K1[(Kafka
raw-logs)]
P[Parser Service
multipod]
K2[(Kafka
normalized-logs)]
E[Enrichment Service
multipod]
K3[(Kafka
enriched-logs)]
IX[Indexer Service
multipod
consumer-group=indexer-group]
CW[Chunk Writer Service
multipod
consumer-group=chunker-group]
MW[Metadata Writer Service
multipod
consumer-group=metadata-group]
SI[(Search Index
OpenSearch / ClickHouse)]
OS[(Object Storage
S3 / Blob)]
MDB[(Metadata DB)]
K1 --> P
P --> K2
K2 --> E
E --> K3
K3 --> IX
K3 --> CW
K3 --> MW
IX --> SI
CW --> OS
MW --> MDB
| Service | Work | ||
|---|---|---|---|
| Parser Servic |
Parser Service converts customer-specific formats into a
normalized representation
|
||
| Enrichment Service |
Adds additional information(recieved from Collector Agent) to
logs
|
||
| Indexer Service |
Creates the
inverted indexes
|
||
| Chunk Writer |
Create batch of logs.
logs -> Create batch(100MB or 5 sec) -> compress -> encrypt ->
S3
|
||
| Metadata Writer |
Maintains the catalog telling us where the actual data lives
|
Logs on Dashboard
flowchart LR
USER[User]
UI[Logging Dashboard]
API[Query API Service]
AUTH[Authentication &
Authorization]
QC[Query Coordinator]
QS[ClickHouse Query
Service]
CH[(ClickHouse Shard1
Distributed Tables)]
CH1[(ClickHouse Shard2
Distributed Tables)]
CH2[(ClickHouse Shard3
Distributed Tables)]
CACHE[Query Cache
Optional]
FORMAT[Result Formatter
Pagination / Sorting / Aggregation]
USER --> UI
UI --> API
API --> AUTH
AUTH --> QC
QC --> CACHE
CACHE -->|Cache Miss| QS
QS <--> CH
QS <--> CH1
QS <--> CH2
QS --> QC
QC --> FORMAT
FORMAT --> API
API --> UI
UI --> USER
Sequence Diagram
Optimization is that (tenant_id, timestamp) should be part of the
physical data layout/partitioning strategy, so ClickHouse doesn't scan
the entire log dataset.
Search from S3/Object Store:When the requested time range has
moved to cold storage or when you deliberately keep only a subset of
fields in ClickHouse.
sequenceDiagram
participant User
participant UI as Dashboard/Browser
participant API as Query API
participant Auth as Auth/IDP
participant QC as Query Coordinator
participant Cache as Query Cache
participant QSvc as ClickHouse Query Service
participant CH as ClickHouse Cluster
participant S1 as Shard 1
participant S2 as Shard 2
participant S3 as Shard 3
User->>UI: Selects
Tenant: abc
Service: Policy Engine
Severity: ERROR
Time: Last 1 hour
UI->>API: GET /api/v1/logs?
service=policy-engine
&severity=ERROR
&from=10:00
&to=11:00
API->>Auth: Authenticate + authorize user
Auth-->>API: Authorized for tenant=abc
API->>QC: Execute log query
QC->>QC: Validate query
Apply tenant scope
Determine time range
Apply query limits
QC->>Cache: Check query cache
alt Cache Hit
Cache-->>QC: Cached results
else Cache Miss
QC->>QSvc: Execute ClickHouse query
Note over QSvc:SELECT timestamp, service,
host,severity,
message,trace_id
FROM logs
WHERE tenant_id = 'abc'
AND service = 'policy-engine'
AND severity = 'ERROR'
AND timestamp >= now() - INTERVAL 1 HOUR
ORDER BY timestamp DESC
LIMIT 100;
QSvc->>CH: Distributed query
CH->>S1: Query relevant partitions
CH->>S2: Query relevant partitions
CH->>S3: Query relevant partitions
S1-->>CH: Matching rows
S2-->>CH: Matching rows
S3-->>CH: Matching rows
CH->>CH: Merge shard results
CH->>CH: Sort by timestamp
CH->>CH: Apply LIMIT / aggregation
CH-->>QSvc: Query results
QSvc-->>QC: Query results
QC->>Cache: Store result (optional)
end
QC->>QC: Merge / sort / paginate
QC-->>API: Final result set
API-->>UI: JSON response
UI->>UI: Render log table
UI-->>User: Display logs