Distributors, ingesters, queriers (briefly)

Both ELK and Loki are sharded write-paths plus sharded read-paths plus a backing store; they differ mainly in HOW they choose the partition key — ELK shards by hash(doc_id), Loki streams by hash(label-set).

Previously

We've shaped the data going in, through, and out. Now zoom out to the moving parts of the backend — but only briefly, because the choices we already made determine most of what matters here.

Scene 10

Distributors, ingesters, queriers (briefly)

  1. Watch
  2. Try it
  3. Predict
  4. Capture
ELK · Elasticsearchagents × 6ship docscoordinating noderoutes by hashshard routerhash(doc_id) mod Nelk-d0PRelk-d1PRelk-d2PRelk-d3PRread resultspartition key:hash(doc_id) mod NLokiagents × 6ship streamsdistributors× 2ring · 4loki-i0streams · 2loki-i1streams · 2loki-i2streams · 2loki-i3streams · 2object store1.4 GBread: query-frontend → scheduler → queriers × 3 → ingesters + storepartition key:hash(label-set)VOCABULARY COLLISION"shard" (ELK) vs "stream" (Loki) — same achievement (horizontal scale via consistent hashing), different partition-key choice.Both stacks at steady state. ELK: agents → coordinating node → data nodes (shards routed by hash(doc_id)). Lo…BASELINE
ELK partitions by hash(doc_id) — shards land on data nodes
What to watch for

Two mini-stacks. LEFT (ELK): agents → coordinating node → data nodes; the partition key is hash(doc_id), and each shard has a replica shadow on a neighboring data node. RIGHT (Loki): agents → distributors → consistent-hash ring → ingesters; each stream has 3 replicas (RF=3), and ingesters periodically flush to the object store. Reads on Loki fan out from queriers to BOTH ingesters and the object store. The bottom strip names the vocabulary collision: shard (ELK) vs stream (Loki) — same achievement, different partition-key choice.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

ELK.coordinator_route
coordinating node hashes doc_id, forwards to data node + replicas
def on_index(doc):
shard_id = hash(doc.id) mod num_shards
primary = routing_table.primary(shard_id)
replicas = routing_table.replicas(shard_id) # RF-1
primary.write(doc)
for r in replicas:
r.write(doc) # in-sync replication
Loki.distributor_route
distributor hashes label-set, sends to RF=3 ingesters on the ring
def on_push(line):
key = hash(canonicalize(line.label_set))
owners = ring.successors(key, n=replication_factor) # RF=3
acks = 0
for ingester in owners:
if ingester.send(line):
acks += 1
return acks >= quorum # 2 of 3
Querier.fan_out
queriers ask ingesters for recent data AND object store for old
def execute(query):
plan = frontend.split(query) # frontend → scheduler
ingester_chunks = [
i.query(plan) for i in ring.owners(plan.label_set)
]
store_chunks = object_store.fetch(plan.time_range)
return merge(ingester_chunks, store_chunks)

Where this sits in Build a distributed logging stack (ELK / Loki)

Scene 10 of 12. Both ELK and Loki are sharded write-paths plus sharded read-paths plus a backing store; they differ in the partition key — hash(doc_id) vs hash(label-set).

Up next. Every choice from scenes 02 through 10 is now a knob. Pick a workload and ship a configuration the verifier won't reject.

All 12 scenes in Build a distributed logging stack (ELK / Loki) · Every curriculum

Built with Arqly
Every scene in Build a distributed logging stack (ELK / Loki) builds on the one before it.All 12 Build a distributed logging stack (ELK / Loki) scenes