Two servers sharing one waiting line can use idle capacity more effectively than two randomly assigned lines. Pooling reduces occasions when one server is idle while another has a queue. It does not turn two 10 ms servers into one 5 ms server.
State-dependent capacity in M/M/c
Assume Poisson arrivals, identical servers with independent exponential service at rate μ, one FCFS queue, no abandonment and unlimited waiting space. With n jobs inside, the total completion rate is min(n,c)μ.
The waiting tail is P(Wq>t)=C(c,a)e−(cμ−λ)t. Total time also includes the arriving job's own service, so it is generally wrong to assign total time an exponential rate cμ−λ.
Pooling versus splitting with equal resources
For two servers at μ=100/s and total λ=160/s, a=1.6, ρ=0.8, π0=1/9 and C=32/45≈0.7111. Pooled mean waiting is 17.78 ms and mean system time 27.78 ms.
If each arrival independently selects a server with equal probability, Poisson splitting produces two M/M/1 queues at 80/s each. Each has mean wait 40 ms and mean system time 50 ms. Hardware is unchanged; coordinating idle capacity changes waiting.
Preparing the visual
Multiple servers and resource pooling · Experiment
Each server has exponential service at μ=100/s, total λ=cμρ. Compare one FCFS queue with independent uniform random splitting. Changing c also changes total arrival rate; each comparison holds per-server utilization and total capacity equal. No lock contention.
{"id":"queueing-06","title":"Multiple servers and resource pooling · Experiment","summary":"Each server has exponential service at μ=100/s, total λ=cμρ. Compare one FCFS queue with independent uniform random splitting. Changing c also changes total arrival rate; each comparison holds per-server utilization and total capacity equal. No lock contention.","height":1000,"html":"<p class=\"intro\" data-i18n=\"scope\"></p><div class=\"controls\"><div class=\"control\"><label for=\"parameter-0\" data-i18n=\"servers\"></label><output id=\"value-0\" for=\"parameter-0\"></output><input type=\"range\" id=\"parameter-0\" data-parameter=\"0\" min=\"1\" max=\"8\" step=\"1\" value=\"2\"/></div><div class=\"control\"><label for=\"parameter-1\" data-i18n=\"rho\"></label><output id=\"value-1\" for=\"parameter-1\"></output><input type=\"range\" id=\"parameter-1\" data-parameter=\"1\" min=\"0.1\" max=\"0.95\" step=\"0.05\" value=\"0.8\"/></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=6, DEFAULTS=[2, 0.8];\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":{"arrivalRate":"Total arrival rate / s⁻¹","meanTime":"Mean system time / s","no":"No","pooled":"One shared queue","pooledWait":"Pooled mean wait / s","reset":"Reset experiment","rho":"Utilization ρ","rhoAxis":"Utilization ρ","scope":"Each server has exponential service at μ=100/s, total λ=cμρ. Compare one FCFS queue with independent uniform random splitting. Changing c also changes total arrival rate; each comparison holds per-server utilization and total capacity equal. No lock contention.","servers":"Server count c","split":"Random split into separate queues","splitWait":"Split mean wait / s","updated":"Recalculated; — means not applicable under these conditions.","vertical":"Vertical range","waitProbability":"Probability of waiting C","waitingSeconds":"Mean waiting time / s","yes":"Yes"}}
Start with two servers and vary ρ, then vary server count. Changing c also changes offered traffic through λ=cμρ. This studies scale at fixed per-server utilization, not scaling under fixed external traffic. At c=1, the curves coincide, providing a boundary check.
Pooling has implementation conditions
The shared queue must dispatch promptly, and servers must be interchangeable for the jobs. Locality, accelerator type or access restrictions can prevent arbitrary assignment. Shared-queue locks, communication and cache effects are absent from M/M/c. Random splitting is only a baseline: shortest-queue routing, least connections and work stealing differ from it.
Erlang B describes loss systems without waiting space; Erlang C holds arrivals until service becomes available. Abandonment motivates Erlang A. General service times require M/G/c approximations or simulation. These names do not label interchangeable formulas.
Check your understanding
Why not replace two 100/s servers with a single M/M/1 server at 200/s?
Reasoning
A job on the two-server system still needs mean service 10 ms, and only one server works when a single job is present. A 200/s single server gives that job mean service 5 ms. Completion rates already differ in state one, changing the entire response distribution.
Eight workers share a database allowing only two concurrent operations. Is M/M/8 appropriate?
Reasoning
Only if workers independently provide the actual bottleneck service. If time is mainly spent contending for two database slots, model that resource separately or build a network containing workers and database, then measure their interaction.
Further reading
NPTEL's multiple-server lecture derives Erlang C from state-dependent completion rates. Its offered-load notation differs: this series consistently uses a=λ/μ and ρ=a/c.