Change:
I reviewed the current code to see what had been done to generalize request width. I found a few inconsistencies in . A really simple one is at line , where is subtracted from the product but should have been subtracted from just (and their difference multiplied by ). With that fixed, a queue's tracks the expected "virtual time" ( in the original paper) when the oldest waiting request will start. However, computes the expected finish "virtual time" of the J'th request by multiplying by but not . There should be a consistent notion of "virtual time" (AKA ), either always including seats or excluding them. Kubernetes version (use ): latest k8s/apiserver sources Cloud provider or hardware configuration: OS (e.g: ): Kernel (e.g. ): Install tools: Network plugin and version (if this is a network-related bug): Others:
BTW, I suspect it would be better if "virtualTime" is not multiplied by request width. I am working on writing.
/triage accepted
I am running into a dilemma. The whole point of "virtual time" --- R(t) in the original paper --- is that it is shared among all the queues. They all get work done at the same rate, dR/dt. But when we generalize so that requests have different widths (and thus work is the sum over requests of ), even restricting the virtual world so that a queue runs only one request at a time does not save us from the fact that different queues get work done at different rates. Thus, even discarding fairness, and executing one request at a time per queue in the virtual world, it looks like what can be shared among queues is not a matter of work done.
But the current implementation approach makes the assumption that all the non-empty queues get work done at the same rate. As long as we are willing to tolerate the lossages due to that fiction, the queues can share one R(t) meter.
I see both sides of this; I still think it is double counting to use to advance virtual time AND to occupy W seats. OTOH, I also think it is right that we should be fair over the work () -- not just duration. One crazy idea is to add the request to multiple queues, and not execute it until it gets to the head of all the queues it is in. The number of queues it needs to be to would be (# queues) * W / seats, rounded up I guess.
I think "virtual time" is a fuzzy concept that does not come equipped with guidelines about how to think about it correctly. That is why I followed the style of reasoning in the original paper, it defines a specific function and shows why and how that particular function is useful. In the KEP revision I am preparing, I think the problem statement ()the equations defining intended behavior ()the implementation discussion () make sense. It is in the last of those that is introduced, and I think it is clear that this should be a measure that includes request width.
I don't think it's double counting, because those are different things. And yes - we should be fair over WD - this is exactly why I wanted to push virtual time by WD. - I've seen your KEP PR, just I'm under water. My Fridays are real working time, so I will take a look tomorrow.

Discussion:
qs.robinIndex = (qs.robinIndex + 1) % nq queue := qs.queues[qs.robinIndex] if queue.requests.Length() != 0 { <del> currentVirtualFinish := queue.GetVirtualFinish(0, qs.estimatedServiceTime) </del> <ins> currentVirtualFinish := queue.GetNextFinish(qs.estimatedServiceTime) </ins> if currentVirtualFinish < minVirtualFinish { minVirtualFinish = currentVirtualFinish minQueue = queue

S := now.Sub(r.startTime).Seconds() // When a request finishes being served, and the actual service time was S, <del> // the queue’s virtual start time is decremented by G - S. r.queue.virtualStart -= (qs.estimatedServiceTime * float64(r.Seats())) - S </del> <ins> // the queue’s virtual start time is decremented by (G - S)*width. r.queue.virtualStart -= (qs.estimatedServiceTime - S) * float64(r.Seats()) </ins> // request has finished, remove from requests executing r.queue.requestsExecuting--

return request, ok } <del> // GetVirtualFinish returns the expected virtual finish time of the request at // index J in the queue with estimated finish time G func (q *queue) GetVirtualFinish(J int, G float64) float64 { // The virtual finish time of request number J in the queue // (counting from J=1 for the head) is J * G + (virtual start time). // counting from J=1 for the head (eg: queue.requests[0] -> J=1) - J+1 jg := float64(J+1) * float64(G) return jg + q.virtualStart </del> <ins> // GetNextFinish returns the expected virtual finish time of the // oldest request in the queue with estimated duration G func (q *queue) GetNextFinish(G float64) float64 { // TODO: if we decide to generalize this function to return virtual finish time //  for the Nth oldest request waiting in the queue, we need to carefully //  evaluate and potentially improve the performance here. var oldestReq *request q.requests.Walk(func(r *request) bool { oldestReq = r return false }) if oldestReq == nil { // we should never be here, since the caller should ensure // that this queue has request(s) waiting to be served before // calling this function. return q.virtualStart } // the estimated service time of the oldest request is (G * request width) estimatedServiceTime := float64(G) * float64(oldestReq.Seats()) return q.virtualStart + estimatedServiceTime </ins> } func (q *queue) dump(includeDetails bool) debug.QueueDump {

<ins> /* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package queueset import ( "testing" ) func TestGetNextFinish(t *testing.T) { var virtualStart float64 = 100 var G float64 = 60 tests := []struct { name                  string requests              []*request virtualFinishExpected float64 }{ { name: "for the oldest request", requests: []*request{ {width: 5}, {width: 6}, {width: 7}, }, virtualFinishExpected: virtualStart + (5 * G), }, { name:                  "queue does not have any request waiting", requests:              []*request{}, virtualFinishExpected: virtualStart, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { requests := newRequestFIFO() for i := range test.requests { requests.Enqueue(test.requests[i]) } q := &queue{requests: requests} q.virtualStart = virtualStart virtualFinishGot := q.GetNextFinish(G) if test.virtualFinishExpected != virtualFinishGot { t.Errorf("Expected virtual finish time: %.9fs, but got: %.9fs", test.virtualFinishExpected, virtualFinishGot) } }) } } </ins>