A request may visit caches, application workers and a database, loop back, or fan out to several subrequests. Queueing networks translate external requests into the work each resource must perform.
Solve traffic before waiting
Let γj be external arrival rate to node j, and pij the probability of routing a completion at i to j. Stationary flow conservation gives:
λj=γj+i∑λipij.
These equations are routing accounting; they do not make each node M/M/1. An open Jackson network additionally assumes independent external Poisson arrivals, exponential service, suitable independent memoryless routing, unlimited waiting space and stable node loads. Internal processes must not be casually treated as independent request samples.
The key result is Jackson's stationary product form. For this chapter's single-server nodes:
It makes node occupancies independent at a common stationary time, not successive delays along a path. With feedback, internal node arrivals need not even be Poisson. These marginals still give mean occupancy and hence network mean residence. See the MIT Jackson-network lecture for the distinction and assumptions.
The experiment visits nodes 1 and 2, then returns to node 1 with probability p or exits. Each business request visits each node V=1/(1−p) times on average. Both internal rates are λ0V. Server rates are 100/s and 250/s; stability requires λ0V<100.
E[Tnetwork]=V(100−λ0V1+250−λ0V1).
This follows by summing mean node occupancies and using network Little's law; it does not require independent delays along a request's path. At λ0=40/s and p=0.2, V=1.25, each internal rate is 50/s and mean end-to-end time is 31.25 ms.
Preparing the visual
Queueing networks and bottlenecks · Experiment
External Poisson arrivals enter node 1 then node 2. Node 2 returns to 1 with probability p, otherwise exits. Single servers use independent exponential service, μ₁=100/s and μ₂=250/s. Stationary delay is withheld at or beyond the bottleneck.
{"id":"queueing-09","title":"Queueing networks and bottlenecks · Experiment","summary":"External Poisson arrivals enter node 1 then node 2. Node 2 returns to 1 with probability p, otherwise exits. Single servers use independent exponential service, μ₁=100/s and μ₂=250/s. Stationary delay is withheld at or beyond the bottleneck.","height":1000,"html":"<p class=\"intro\" data-i18n=\"scope\"></p><div class=\"controls\"><div class=\"control\"><label for=\"parameter-0\" data-i18n=\"rate\"></label><output id=\"value-0\" for=\"parameter-0\"></output><input type=\"range\" id=\"parameter-0\" data-parameter=\"0\" min=\"10\" max=\"90\" step=\"5\" value=\"40\"/></div><div class=\"control\"><label for=\"parameter-1\" data-i18n=\"feedback\"></label><output id=\"value-1\" for=\"parameter-1\"></output><input type=\"range\" id=\"parameter-1\" data-parameter=\"1\" min=\"0\" max=\"0.6\" step=\"0.05\" value=\"0.2\"/></div></div><div class=\"tools\"><button id=\"reset\" data-i18n=\"reset\"></button></div><div id=\"timeline\"></div><div id=\"plots\"></div><div id=\"metrics\"></div><p id=\"status\" role=\"status\" aria-live=\"polite\"></p>","css":".plotarea{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:8px}.yticks{display:flex;flex-direction:column;justify-content:space-between;padding:3px 0;font-size:13px;font-variant-numeric:tabular-nums;color:var(--muted)}*{box-sizing:border-box}.intro{margin:0 0 16px;color:var(--muted);line-height:1.65}.controls{display:grid;gap:14px}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center}.control label{font-size:16px;line-height:1.5}.control input{grid-column:1/-1;width:100%;min-height:28px}.control output{font-variant-numeric:tabular-nums}.control select{grid-column:1/-1;width:100%;max-width:100%;padding:8px;font:inherit;background:var(--surface);color:var(--ink);border:1px solid var(--rule);border-radius:6px}.tools{margin:14px 0}.tools button{font:inherit;padding:8px 14px;min-height:42px}.chart{margin:22px 0}.chart h3{font-size:16px;margin:0 0 6px;line-height:1.5}.chart svg{display:block;width:100%;height:160px;overflow:hidden}.scale,.axes{font-size:14px;color:var(--muted);font-variant-numeric:tabular-nums}.axes{display:flex;justify-content:space-between;align-items:start;gap:8px;margin-top:6px}.axes span:nth-child(2){text-align:center;flex:1;min-width:0}.legend{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:8px;font-size:15px}.legend span{display:inline-flex;align-items:center;gap:7px}.legend i{display:inline-block;width:22px;flex-shrink:0}#metrics{border-top:1px solid var(--rule);padding-top:12px;display:grid;gap:8px}#metrics>div{display:flex;justify-content:space-between;gap:16px;font-size:15px;line-height:1.5}#metrics strong{font-weight:600;font-variant-numeric:tabular-nums;flex-shrink:0}#status{font-size:14px;color:var(--muted);min-height:3em;margin:14px 0 0}button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:3px}@media(max-width:420px){.chart svg{height:145px}#metrics>div{flex-wrap:wrap;gap:3px 12px}}\n.job{margin:16px 0}.job-title{font-size:16px;font-weight:600}.job-times{display:flex;flex-wrap:wrap;gap:4px 16px;font-size:14px;color:var(--muted);line-height:1.6}.job svg{display:block;width:100%;height:26px;margin:6px 0}.timeline-key{display:flex;flex-wrap:wrap;gap:12px;font-size:15px;line-height:1.5}.timeline-key span{border-left:12px solid var(--accent);padding-left:6px}.timeline-key span:first-child{border-color:var(--muted)}\n.presets{display:flex;flex-wrap:wrap;grid-column:1/-1;gap:8px}.presets button{font:inherit;line-height:1.5;min-height:44px;max-width:100%;padding:8px 12px;border:1px solid var(--rule);border-radius:6px;color:var(--ink);background:var(--surface);text-align:left;white-space:normal}.presets button[aria-pressed=\"true\"]{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}\n","js":"const MODEL_ID=9, DEFAULTS=[40, 0.2];\n/* Independent teaching models. Seconds, jobs, and jobs/second unless stated.\n Pure functions: no DOM, clock, timers or external packages. */\nconst mean=a=>a.reduce((s,x)=>s+x,0)/a.length;\nconst curve=(name,points)=>({name,points});\nconst chart=(title,xlabel,curves,extra={})=>({title,xlabel,curves,...extra});\nfunction fifo(arrivals,services){let end=0;return arrivals.map((a,i)=>{const b=Math.max(a,end);end=b+services[i];return {id:i+1,a,b,d:end,s:services[i],w:b-a,t:end-a};});}\nfunction stepCounts(jobs,end,key){const events=jobs.map(j=>j[key]).filter(t=>t<=end).sort((a,b)=>a-b),p=[[0,0]];let n=0;for(const t of events)p.push([t,n],[t,++n]);p.push([end,n]);return p;}\nfunction occupancy(jobs,end){const ev=jobs.flatMap(j=>[[j.a,1],[j.d,-1]]).filter(e=>e[0]<=end).sort((a,b)=>a[0]-b[0]||a[1]-b[1]);let n=0;const p=[[0,0]];for(const [t,v]of ev){p.push([t,n],[t,n+v]);n+=v;}p.push([end,n]);return p;}\nfunction erlangC(c,rho){const a=c*rho;let term=1,sum=1;for(let n=1;n<c;n++){term*=a/n;sum+=term;}const tail=term*a/c/(1-rho);return tail/(sum+tail);}\nfunction finiteQueue(lambda,mu,K){const rho=lambda/mu;const w=Array.from({length:K+1},(_,n)=>rho**n),z=w.reduce((s,x)=>s+x,0),prob=w.map(x=>x/z),block=prob[K],through=lambda*(1-block),L=prob.reduce((s,x,n)=>s+n*x,0);return {prob,block,through,L,W:L/through,busy:1-prob[0]};}\nfunction seeded(seed){let s=seed>>>0;return()=>{s=(Math.imul(1664525,s)+1013904223)>>>0;return (s+.5)/4294967296;};}\nfunction simulateMM1(rho,count,seed){const random=seeded(seed),arr=[],srv=[];let t=0;for(let i=0;i<count;i++){t+=-Math.log(random())/(100*rho);arr.push(t);srv.push(-Math.log(random())/100);}return fifo(arr,srv);}\nfunction queueModel(id,p){let plots=[],metrics={},rows;\n if(id===1){const [gap,long]=p,jobs=fifo([0,gap,2*gap,3*gap],[long,1,1,1]),end=jobs.at(-1).d;\n rows=jobs;plots=[chart('cumulative','timeAxis',[curve('arrivals',stepCounts(jobs,end,'a')),curve('starts',stepCounts(jobs,end,'b')),curve('departures',stepCounts(jobs,end,'d'))])];metrics={meanWait:mean(jobs.map(j=>j.w)),meanTime:mean(jobs.map(j=>j.t)),lastDeparture:end};\n }else if(id===2){const [end]=p,jobs=fifo([0,1,2,6],[3,1,2,1]),overlap=j=>Math.max(0,Math.min(j.d,end)-j.a),area=jobs.reduce((s,j)=>s+overlap(j),0),done=jobs.filter(j=>j.d<=end),completedArea=done.reduce((s,j)=>s+j.t,0);\n plots=[chart('occupancy','timeAxis',[curve('inSystem',occupancy(jobs,end))],{yrange:[0,4]})];metrics={area,meanN:area/end,completedProduct:completedArea/end,boundaryArea:area-completedArea,completed:done.length};\n }else if(id===3){const [rate,window]=p,m=rate*window,prob=[];let q=Math.exp(-m);for(let n=0;n<=Math.ceil(m+7*Math.sqrt(m)+5);n++){if(n)q*=m/n;prob.push([n,q]);}\n plots=[chart('countProbability','countAxis',[curve('poisson',prob)],{bars:true,yrange:[0,Math.max(...prob.map(x=>x[1]))*1.12]}),chart('gapSurvival','timeAxis',[curve('exponential',Array.from({length:101},(_,i)=>{const t=5/rate*i/100;return [t,Math.exp(-rate*t)];}))],{yrange:[0,1]})];metrics={countMean:m,countVariance:m,emptyProbability:Math.exp(-m),meanGap:1/rate};\n }else if(id===4){const [rho]=p,delta=100*(1-rho),q99=Math.log(100)/delta;\n plots=[chart('tailProbability','timeAxis',[curve('systemTail',Array.from({length:101},(_,i)=>{const t=q99*1.2*i/100;return [t,Math.exp(-delta*t)];})),curve('waitingTail',Array.from({length:101},(_,i)=>{const t=q99*1.2*i/100;return [t,rho*Math.exp(-delta*t)];}))],{yrange:[0,1]}),chart('latency','rhoAxis',[curve('meanTime',Array.from({length:95},(_,i)=>{const r=(i+1)/100;return [r,1/(100*(1-r))];})),curve('p99',Array.from({length:95},(_,i)=>{const r=(i+1)/100;return [r,Math.log(100)/(100*(1-r))];}))],{event:rho})];metrics={utilization:rho,meanTime:1/delta,meanWait:rho/delta,p99:q99,zeroWait:1-rho,meanN:rho/(1-rho)};\n }else if(id===5){const [ca2,cs2]=p,points=f=>Array.from({length:95},(_,i)=>{const r=(i+1)/100;return[r,f(r)];}),pk=r=>r/(1-r)*(1+cs2)/2*.01,king=r=>r/(1-r)*(ca2+cs2)/2*.01;\n plots=[chart('waitingSeconds','rhoAxis',[curve('poissonReference',points(pk)),curve('kingman',points(king))],{event:.8})];metrics={pkWait:pk(.8),kingmanWait:king(.8),secondMoment:(1+cs2)*.0001,residualBusy:(1+cs2)*.005};\n }else if(id===6){const [c,rho]=p,C=erlangC(c,rho),wait=C/(c*100*(1-rho));\n const pts=f=>Array.from({length:95},(_,i)=>{const r=(i+1)/100;return[r,f(r)];});\n plots=[chart('waitingSeconds','rhoAxis',[curve('pooled',pts(r=>erlangC(c,r)/(c*100*(1-r)))),curve('split',pts(r=>r/(100*(1-r))))],{event:rho})];metrics={arrivalRate:c*100*rho,waitProbability:C,pooledWait:wait,splitWait:rho/(100*(1-rho)),meanTime:wait+.01};\n }else if(id===7){const [long,policy]=p,sizes=[long,1,1,1,1],order=policy?[1,2,3,4,0]:[0,1,2,3,4];let end=0;rows=order.map(i=>{const b=end;end+=sizes[i];return{id:i+1,a:0,b,d:end,s:sizes[i],w:b,t:end};});\n plots=[];metrics={meanWait:mean(rows.map(j=>j.w)),meanTime:mean(rows.map(j=>j.t)),longWait:rows.find(j=>j.id===1).w,maxSlowdown:Math.max(...rows.map(j=>j.t/j.s)),makespan:end};\n }else if(id===8){const [lambda,K]=p,q=finiteQueue(lambda,100,K);\n plots=[chart('stateProbability','countAxis',[curve('stationary',q.prob.map((v,n)=>[n,v]))],{bars:true})];metrics={offeredRatio:lambda/100,blocking:q.block,acceptedRate:q.through,meanN:q.L,meanTime:q.W,utilization:q.busy};\n }else if(id===9){const [lambda,feedback]=p,visit=1/(1-feedback),local=lambda*visit,stable=local<100,demands=[visit*.01,visit*.004];\n plots=[chart('utilizationChart','nodeAxis',[curve('nodeLoad',[[1,lambda*demands[0]],[2,lambda*demands[1]]])],{bars:true,yrange:[0,Math.max(1.2,lambda*demands[0]*1.1)]})];metrics={visits:visit,internalRate:local,bottleneckBound:100/visit,stable,networkTime:stable?visit*(1/(100-local)+1/(250-local)):null};\n }else if(id===10){const [rho,count,seed]=p,jobs=simulateMM1(rho,count,seed),warm=Math.floor(count/5),cohort=jobs.slice(warm),end=jobs.at(-1).a,done=cohort.filter(j=>j.d<=end),times=cohort.map(j=>j.t).sort((a,b)=>a-b),q99=times[Math.ceil(.99*times.length)-1],theory=1/(100*(1-rho)),max=Math.max(q99*1.2,theory*Math.log(100)*1.2);\n let ix=0;const ecdf=Array.from({length:151},(_,i)=>{const t=max*i/150;while(ix<times.length&×[ix]<=t)ix++;return[t,1-ix/times.length];});\n plots=[chart('tailProbability','timeAxis',[curve('empirical',ecdf),curve('stationaryTheory',Array.from({length:151},(_,i)=>{const t=max*i/150;return[t,Math.exp(-t/theory)];}))],{yrange:[0,1]})];metrics={sampleCount:times.length,censored:cohort.length-done.length,cohortMean:mean(times),completedMean:done.length?mean(done.map(j=>j.t)):null,theoryMean:theory,sampleP99:q99,theoryP99:theory*Math.log(100)};\n }else if(id===11){const [capacity,prob]=p,base=60,factor=1+prob+prob*prob,normal=base*factor,burst=120*factor,Qpeak=Math.max(0,burst-capacity)*30,drain=capacity>normal?Qpeak/(capacity-normal):Qpeak===0?0:null;\n const pts=Array.from({length:241},(_,i)=>{const t=i,rate=t<20||t>=50?normal:burst;let Q;if(t<20)Q=Math.max(0,normal-capacity)*t;else if(t<50)Q=Math.max(0,normal-capacity)*20+Math.max(0,burst-capacity)*(t-20);else Q=Math.max(0,Math.max(0,normal-capacity)*20+Qpeak+(normal-capacity)*(t-50));return [t,Q];});\n plots=[chart('backlog','timeAxis',[curve('queuedWork',pts)]),chart('rateChart','timeAxis',[curve('attemptRate',[[0,normal],[20,normal],[20,burst],[50,burst],[50,normal],[240,normal]]),curve('capacity',[[0,capacity],[240,capacity]])])];metrics={attemptFactor:factor,normalAttempts:normal,burstBacklog:pts[50][1],canDrain:capacity>normal,drainSeconds:capacity>normal?pts[50][1]/(capacity-normal):null,endBacklog:pts.at(-1)[1]};\n }\n return {plots,metrics,...(rows?{rows}:{})};\n}\nif(typeof module!=='undefined')module.exports={queueModel,fifo,erlangC,finiteQueue,simulateMM1};\n\nconst $=s=>document.querySelector(s), colors=['var(--accent)','#b77818','#269282'],dash=['','7 4','2 4'];\nconst number=x=>Number.isFinite(x)?viz.number(x,Number.isInteger(x)?0:Math.abs(x)<.01&&x!==0?5:3):'—';\nfunction plot(parent,spec){\n const all=spec.curves.flatMap(s=>s.points),xx=all.map(p=>p[0]),yy=all.map(p=>p[1]);\n const zero=true;\n let lo=zero?Math.min(0,...yy):Math.min(...yy),hi=zero?Math.max(0,...yy):Math.max(...yy),xmin=Math.min(...xx),xmax=Math.max(...xx);\n if(hi===lo)hi=lo+1;if(xmin===xmax)xmax=xmin+1;\n const pad=(hi-lo)*.08;lo=Math.max(0,lo-pad);hi+=pad;\n if(spec.bars){xmin-=.5;xmax+=.5;}if(spec.xrange)[xmin,xmax]=spec.xrange;if(spec.yrange)[lo,hi]=spec.yrange;\n const sx=x=>8+(x-xmin)/(xmax-xmin)*584,sy=y=>8+(hi-y)/(hi-lo)*144;\n const section=document.createElement('section');section.className='chart';\n const title=document.createElement('h3');title.textContent=viz.t(spec.title);section.append(title);\n const scale=document.createElement('div');scale.className='scale';scale.textContent=viz.t('vertical')+' '+number(lo)+' … '+number(hi);section.append(scale);\n const ns='http://www.w3.org/2000/svg',svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 600 160');svg.setAttribute('preserveAspectRatio','none');svg.setAttribute('role','img');svg.setAttribute('aria-label',viz.t(spec.title));\n function node(name,attrs){const n=document.createElementNS(ns,name);for(const [k,v]of Object.entries(attrs))n.setAttribute(k,v);svg.append(n);return n;}\n for(let j=0;j<=4;j++)node('line',{x1:8,x2:592,y1:8+j*36,y2:8+j*36,stroke:'var(--rule)','stroke-width':1});\n if(lo<=0&&hi>=0)node('line',{x1:8,x2:592,y1:sy(0),y2:sy(0),stroke:'var(--muted)','stroke-width':1});\n if(spec.scatter)node('line',{x1:sx(0),x2:sx(0),y1:8,y2:152,stroke:'var(--muted)','stroke-width':1});\n if(spec.event!==undefined&&spec.event>=xmin&&spec.event<=xmax)node('line',{x1:sx(spec.event),x2:sx(spec.event),y1:8,y2:152,stroke:'var(--muted)','stroke-dasharray':'3 3'});\n spec.curves.forEach((s,i)=>{if(spec.bars){for(const [x,y]of s.points)node('line',{x1:sx(x),x2:sx(x),y1:sy(0),y2:sy(y),stroke:colors[i%3],'stroke-width':Math.min(16,400/s.points.length)});}else if(spec.scatter||spec.dots===i){for(const [x,y] of s.points)node('circle',{cx:sx(x),cy:sy(y),r:spec.scatter?5:2.8,fill:colors[i%3]});}else{let pts=s.points;if(spec.title==='replicas')pts=pts.flatMap((p,j)=>j?[[p[0],pts[j-1][1]],p]:[p]);node('path',{d:pts.map(([x,y],j)=>(j?'L':'M')+sx(x).toFixed(2)+' '+sy(y).toFixed(2)).join(' '),fill:'none',stroke:colors[i%3],'stroke-width':2,'stroke-dasharray':dash[i%3],'vector-effect':'non-scaling-stroke'});}});\n const area=document.createElement('div');area.className='plotarea';const ticks=document.createElement('div');ticks.className='yticks';for(const v of [hi,(hi+lo)/2,lo]){const label=document.createElement('span');label.textContent=number(v);ticks.append(label);}area.append(ticks,svg);section.append(area);\n const axes=document.createElement('div');axes.className='axes';const fmt=x=>spec.logx?number(10**x):number(x);\n for(const text of [fmt(spec.bars?xmin+.5:xmin),viz.t(spec.xlabel),fmt(spec.bars?xmax-.5:xmax)]){const a=document.createElement('span');a.textContent=text;axes.append(a);}section.append(axes);\n const legend=document.createElement('div');legend.className='legend';spec.curves.forEach((s,i)=>{const el=document.createElement('span'),sw=document.createElement('i');sw.style.borderTop=`3px ${i===0?'solid':i===1?'dashed':'dotted'} ${colors[i%3]}`;el.append(sw,document.createTextNode(viz.t(s.name)));legend.append(el);});section.append(legend);parent.append(section);\n}\nfunction timeline(rows){\n const host=$('#timeline');host.replaceChildren();if(!rows.length)return;\n const key=document.createElement('div');key.className='timeline-key';for(const k of ['waiting','service']){const e=document.createElement('span');e.textContent=viz.t(k);key.append(e);}host.append(key);\n const max=Math.max(...rows.map(r=>r.d)),ns='http://www.w3.org/2000/svg';\n for(const r of rows){const box=document.createElement('div');box.className='job';const title=document.createElement('div');title.className='job-title';title.textContent=viz.t('job')+' '+r.id;box.append(title);\n const svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 600 26');svg.setAttribute('preserveAspectRatio','none');svg.setAttribute('role','img');svg.setAttribute('aria-label',viz.t('waiting')+' '+number(r.w)+' s; '+viz.t('service')+' '+number(r.s)+' s');\n for(const [a,b,color]of [[r.a,r.b,'var(--muted)'],[r.b,r.d,'var(--accent)']]){const rect=document.createElementNS(ns,'rect');rect.setAttribute('x',a/max*600);rect.setAttribute('y','3');rect.setAttribute('width',(b-a)/max*600);rect.setAttribute('height','20');rect.setAttribute('fill',color);svg.append(rect);}box.append(svg);\n const text=document.createElement('div');text.className='job-times';for(const [k,val]of [['arrivalTime',r.a],['startTime',r.b],['endTime',r.d]]){const e=document.createElement('span');e.textContent=viz.t(k)+' '+number(val)+' s';text.append(e);}box.append(text);host.append(box);\n }const axis=document.createElement('div');axis.className='axes';for(const s of ['0',viz.t('timeAxis'),number(max)]){const e=document.createElement('span');e.textContent=s;axis.append(e);}host.append(axis);\n}\n\nfunction update(){\n const values=[...document.querySelectorAll('[data-parameter]')].map(e=>+(e.matches('.presets')?e.dataset.value:e.value));\n document.querySelectorAll('input[data-parameter]').forEach(e=>$('#value-'+e.dataset.parameter).textContent=number(+e.value));\n document.querySelectorAll('[data-choice]').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.choice===b.parentElement.dataset.value)));\n const r=queueModel(MODEL_ID,values);document.body.dataset.result=JSON.stringify(r.metrics);document.body.dataset.parameters=JSON.stringify(values);\n timeline(r.rows||[]);$('#plots').replaceChildren();r.plots.forEach(s=>plot($('#plots'),s));$('#metrics').replaceChildren();\n for(const [k,v]of Object.entries(r.metrics)){const line=document.createElement('div'),label=document.createElement('span'),value=document.createElement('strong');label.textContent=viz.t(k);value.textContent=typeof v==='boolean'?viz.t(v?'yes':'no'):number(v);line.append(label,value);$('#metrics').append(line);}\n $('#status').textContent=viz.t('updated');\n}\ndocument.querySelectorAll('input[data-parameter]').forEach(e=>e.addEventListener('input',update));\ndocument.querySelectorAll('[data-choice]').forEach(b=>b.addEventListener('click',()=>{b.parentElement.dataset.value=b.dataset.choice;update();}));\n$('#reset').addEventListener('click',()=>{document.querySelectorAll('[data-parameter]').forEach((e,i)=>{if(e.matches('.presets'))e.dataset.value=DEFAULTS[i];else e.value=DEFAULTS[i];});update();});update();\n","audio":false,"strings":{"bottleneckBound":"External-rate stability boundary / s⁻¹","feedback":"Probability p of returning to node 1","internalRate":"Internal arrival rate per node / s⁻¹","networkTime":"Mean end-to-end time / s","no":"No","nodeAxis":"Node number","nodeLoad":"Node 1: 100/s; node 2: 250/s","rate":"Arrival rate λ / s⁻¹","reset":"Reset experiment","scope":"External Poisson arrivals enter node 1 then node 2. Node 2 returns to 1 with probability p, otherwise exits. Single servers use independent exponential service, μ₁=100/s and μ₂=250/s. Stationary delay is withheld at or beyond the bottleneck.","stable":"Network stationary condition satisfied","updated":"Recalculated; — means not applicable under these conditions.","utilizationChart":"Offered load / node capacity","vertical":"Vertical range","visits":"Mean visits to each node","yes":"Yes"}}
Increase return probability while keeping external traffic fixed. Internal demand increases. At or beyond the bottleneck, the stationary-condition indicator becomes false and mean delay shows —. A finite plotting window cannot supply a nonexistent stationary solution.
Service demand and bottleneck bounds
Mean service demand at resource j per completed business request is Dj=VjE[Sj]. A single-capacity resource has utilization Uj=XDj, hence the necessary throughput bound:
X≤maxjDj1.
Dj includes resource service occupancy, not waiting. Substituting response time mistakes congestion for intrinsic work. For cj interchangeable parallel resources, the corresponding bound becomes cj/Dj, assuming work can be assigned and no shared bottleneck intervenes. A bottleneck bound is not an achievable-throughput guarantee under an arbitrary latency target.
A closed system with N circulating users and mean think time Z satisfies N=X(E[T]+Z). Users who submit again only after completion reduce new traffic as responses slow. An open system with externally scheduled arrivals does not self-throttle this way.
Fan-out amplifies tails
If a request waits for all m children, its duration includes max(T1,…,Tm). Only for IID child times with CDF F is the maximum's CDF F(t)m. If one child finishes before a threshold with probability 0.99, 100 independent children all finish before it with probability only 0.99100≈0.366.
Shared databases, networks and failures create dependence, invalidating unconditional multiplication. Serial-stage means add, but adding stage p99 values generally does not produce end-to-end p99. Use joint measurements, appropriate probability bounds or an explicit dependence model.
Check your understanding
A business request visits a database three times, occupying it for 5 ms each. What is the single-resource throughput bound?
Reasoning
D=3×0.005=0.015 s/request, so X≤66.67/s. Waiting targets, other resources and variability may reduce usable throughput further.
Ten users with Z=0 see mean response grow from 0.1 s to 1 s. What happens in a closed load test?
Reasoning
The stationary relation changes throughput from 100/s to 10/s. The generator slows with the system; this does not show that the system sustained an open demand of 100/s.