mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
Flush the records staging buffer periodically (#7193)
- Staging buffer is flushed every 500ms. In cases where the result records are slowly generated (e.g. when a where condition matches very few records), this change causes the server to send results even though the staging buffer is not full. - Refactor messageWriter code to use simpler channel based co-ordination instead of atomic variables.
This commit is contained in:
parent
3dfbe0f68c
commit
fd4e15c116
@ -226,12 +226,12 @@ type messageWriter struct {
|
|||||||
|
|
||||||
payloadBuffer []byte
|
payloadBuffer []byte
|
||||||
payloadBufferIndex int
|
payloadBufferIndex int
|
||||||
|
payloadCh chan []byte
|
||||||
|
|
||||||
dataCh chan []byte
|
finBytesScanned, finBytesProcessed int64
|
||||||
doneCh chan struct{}
|
|
||||||
closeCh chan struct{}
|
errCh chan []byte
|
||||||
stopped uint32
|
doneCh chan struct{}
|
||||||
closed uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (writer *messageWriter) write(data []byte) bool {
|
func (writer *messageWriter) write(data []byte) bool {
|
||||||
@ -246,80 +246,89 @@ func (writer *messageWriter) write(data []byte) bool {
|
|||||||
func (writer *messageWriter) start() {
|
func (writer *messageWriter) start() {
|
||||||
keepAliveTicker := time.NewTicker(1 * time.Second)
|
keepAliveTicker := time.NewTicker(1 * time.Second)
|
||||||
var progressTicker *time.Ticker
|
var progressTicker *time.Ticker
|
||||||
|
var progressTickerC <-chan time.Time
|
||||||
if writer.getProgressFunc != nil {
|
if writer.getProgressFunc != nil {
|
||||||
progressTicker = time.NewTicker(1 * time.Minute)
|
progressTicker = time.NewTicker(1 * time.Minute)
|
||||||
|
progressTickerC = progressTicker.C
|
||||||
}
|
}
|
||||||
|
recordStagingTicker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
|
||||||
go func() {
|
// Exit conditions:
|
||||||
quitFlag := 0
|
//
|
||||||
for quitFlag == 0 {
|
// 1. If a writer.write() returns false, select loop below exits and
|
||||||
if progressTicker == nil {
|
// closes `doneCh` to indicate to caller to also exit.
|
||||||
select {
|
//
|
||||||
case data, ok := <-writer.dataCh:
|
// 2. If caller (Evaluate()) has an error, it sends an error
|
||||||
if !ok {
|
// message and waits for this go-routine to quit in
|
||||||
quitFlag = 1
|
// FinishWithError()
|
||||||
break
|
//
|
||||||
}
|
// 3. If caller is done, it waits for this go-routine to exit
|
||||||
if !writer.write(data) {
|
// in Finish()
|
||||||
quitFlag = 1
|
|
||||||
}
|
quitFlag := false
|
||||||
case <-writer.doneCh:
|
for !quitFlag {
|
||||||
quitFlag = 2
|
select {
|
||||||
case <-keepAliveTicker.C:
|
case data := <-writer.errCh:
|
||||||
if !writer.write(continuationMessage) {
|
quitFlag = true
|
||||||
quitFlag = 1
|
// Flush collected records before sending error message
|
||||||
}
|
if !writer.flushRecords() {
|
||||||
}
|
break
|
||||||
} else {
|
|
||||||
select {
|
|
||||||
case data, ok := <-writer.dataCh:
|
|
||||||
if !ok {
|
|
||||||
quitFlag = 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !writer.write(data) {
|
|
||||||
quitFlag = 1
|
|
||||||
}
|
|
||||||
case <-writer.doneCh:
|
|
||||||
quitFlag = 2
|
|
||||||
case <-keepAliveTicker.C:
|
|
||||||
if !writer.write(continuationMessage) {
|
|
||||||
quitFlag = 1
|
|
||||||
}
|
|
||||||
case <-progressTicker.C:
|
|
||||||
bytesScanned, bytesProcessed := writer.getProgressFunc()
|
|
||||||
bytesReturned := atomic.LoadInt64(&writer.bytesReturned)
|
|
||||||
if !writer.write(newProgressMessage(bytesScanned, bytesProcessed, bytesReturned)) {
|
|
||||||
quitFlag = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
writer.write(data)
|
||||||
|
|
||||||
atomic.StoreUint32(&writer.stopped, 1)
|
case payload, ok := <-writer.payloadCh:
|
||||||
close(writer.closeCh)
|
if !ok {
|
||||||
|
// payloadCh is closed by caller to
|
||||||
|
// indicate finish with success
|
||||||
|
quitFlag = true
|
||||||
|
|
||||||
keepAliveTicker.Stop()
|
if !writer.flushRecords() {
|
||||||
if progressTicker != nil {
|
|
||||||
progressTicker.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
if quitFlag == 2 {
|
|
||||||
for data := range writer.dataCh {
|
|
||||||
if _, err := writer.writer.Write(data); err != nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
// Write Stats message, then End message
|
||||||
|
bytesReturned := atomic.LoadInt64(&writer.bytesReturned)
|
||||||
|
if !writer.write(newStatsMessage(writer.finBytesScanned, writer.finBytesProcessed, bytesReturned)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
writer.write(endMessage)
|
||||||
|
} else {
|
||||||
|
// Write record payload to staging buffer
|
||||||
|
freeSpace := bufLength - writer.payloadBufferIndex
|
||||||
|
if len(payload) > freeSpace {
|
||||||
|
if !writer.flushRecords() {
|
||||||
|
quitFlag = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copy(writer.payloadBuffer[writer.payloadBufferIndex:], payload)
|
||||||
|
writer.payloadBufferIndex += len(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-recordStagingTicker.C:
|
||||||
|
if !writer.flushRecords() {
|
||||||
|
quitFlag = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-keepAliveTicker.C:
|
||||||
|
if !writer.write(continuationMessage) {
|
||||||
|
quitFlag = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-progressTickerC:
|
||||||
|
bytesScanned, bytesProcessed := writer.getProgressFunc()
|
||||||
|
bytesReturned := atomic.LoadInt64(&writer.bytesReturned)
|
||||||
|
if !writer.write(newProgressMessage(bytesScanned, bytesProcessed, bytesReturned)) {
|
||||||
|
quitFlag = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
}
|
close(writer.doneCh)
|
||||||
|
|
||||||
func (writer *messageWriter) close() {
|
recordStagingTicker.Stop()
|
||||||
if atomic.SwapUint32(&writer.closed, 1) == 0 {
|
keepAliveTicker.Stop()
|
||||||
close(writer.doneCh)
|
if progressTicker != nil {
|
||||||
for range writer.closeCh {
|
progressTicker.Stop()
|
||||||
close(writer.dataCh)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,88 +336,69 @@ const (
|
|||||||
bufLength = maxRecordSize
|
bufLength = maxRecordSize
|
||||||
)
|
)
|
||||||
|
|
||||||
// collectRecord - collects records into a buffer, and when it is
|
// Sends a single whole record.
|
||||||
// full, sends a message with the collected payload.
|
func (writer *messageWriter) SendRecord(payload []byte) error {
|
||||||
func (writer *messageWriter) collectRecord(data []byte) (err error) {
|
select {
|
||||||
freeSpace := bufLength - writer.payloadBufferIndex
|
case writer.payloadCh <- payload:
|
||||||
if len(data) > freeSpace {
|
|
||||||
err = writer.FlushRecords()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
copy(writer.payloadBuffer[writer.payloadBufferIndex:], data)
|
|
||||||
writer.payloadBufferIndex += len(data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (writer *messageWriter) send(data []byte) error {
|
|
||||||
err := func() error {
|
|
||||||
if atomic.LoadUint32(&writer.stopped) == 1 {
|
|
||||||
return fmt.Errorf("writer already closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case writer.dataCh <- data:
|
|
||||||
case <-writer.doneCh:
|
|
||||||
return fmt.Errorf("closed writer")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}()
|
case <-writer.doneCh:
|
||||||
|
return fmt.Errorf("messageWriter is done")
|
||||||
if err != nil {
|
|
||||||
writer.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (writer *messageWriter) SendRecords(payload []byte) error {
|
func (writer *messageWriter) flushRecords() bool {
|
||||||
return writer.collectRecord(payload)
|
if writer.payloadBufferIndex == 0 {
|
||||||
}
|
return true
|
||||||
|
|
||||||
func (writer *messageWriter) FlushRecords() (err error) {
|
|
||||||
err = writer.send(newRecordsMessage(writer.payloadBuffer[0:writer.payloadBufferIndex]))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
atomic.AddInt64(&writer.bytesReturned, int64(writer.payloadBufferIndex))
|
result := writer.write(newRecordsMessage(writer.payloadBuffer[0:writer.payloadBufferIndex]))
|
||||||
writer.payloadBufferIndex = 0
|
if result {
|
||||||
return nil
|
atomic.AddInt64(&writer.bytesReturned, int64(writer.payloadBufferIndex))
|
||||||
}
|
writer.payloadBufferIndex = 0
|
||||||
|
|
||||||
func (writer *messageWriter) SendStats(bytesScanned, bytesProcessed int64) error {
|
|
||||||
bytesReturned := atomic.LoadInt64(&writer.bytesReturned)
|
|
||||||
err := writer.send(newStatsMessage(bytesScanned, bytesProcessed, bytesReturned))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
err = writer.send(endMessage)
|
|
||||||
writer.close()
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (writer *messageWriter) SendError(errorCode, errorMessage string) error {
|
// Finish is the last call to the message writer - it sends any
|
||||||
err := writer.send(newErrorMessage([]byte(errorCode), []byte(errorMessage)))
|
// remaining record payload, then sends statistics and finally the end
|
||||||
if err == nil {
|
// message.
|
||||||
writer.close()
|
func (writer *messageWriter) Finish(bytesScanned, bytesProcessed int64) error {
|
||||||
|
select {
|
||||||
|
case <-writer.doneCh:
|
||||||
|
return fmt.Errorf("messageWriter is done")
|
||||||
|
default:
|
||||||
|
writer.finBytesScanned = bytesScanned
|
||||||
|
writer.finBytesProcessed = bytesProcessed
|
||||||
|
close(writer.payloadCh)
|
||||||
|
// Wait until the `start` go-routine is done.
|
||||||
|
<-writer.doneCh
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (writer *messageWriter) FinishWithError(errorCode, errorMessage string) error {
|
||||||
|
select {
|
||||||
|
case <-writer.doneCh:
|
||||||
|
return fmt.Errorf("messageWriter is done")
|
||||||
|
case writer.errCh <- newErrorMessage([]byte(errorCode), []byte(errorMessage)):
|
||||||
|
// Wait until the `start` go-routine is done.
|
||||||
|
<-writer.doneCh
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMessageWriter creates a message writer that writes to the HTTP
|
||||||
|
// response writer
|
||||||
func newMessageWriter(w http.ResponseWriter, getProgressFunc func() (bytesScanned, bytesProcessed int64)) *messageWriter {
|
func newMessageWriter(w http.ResponseWriter, getProgressFunc func() (bytesScanned, bytesProcessed int64)) *messageWriter {
|
||||||
writer := &messageWriter{
|
writer := &messageWriter{
|
||||||
writer: w,
|
writer: w,
|
||||||
getProgressFunc: getProgressFunc,
|
getProgressFunc: getProgressFunc,
|
||||||
|
|
||||||
payloadBuffer: make([]byte, bufLength),
|
payloadBuffer: make([]byte, bufLength),
|
||||||
|
payloadCh: make(chan []byte),
|
||||||
|
|
||||||
dataCh: make(chan []byte),
|
errCh: make(chan []byte),
|
||||||
doneCh: make(chan struct{}),
|
doneCh: make(chan struct{}),
|
||||||
closeCh: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
writer.start()
|
go writer.start()
|
||||||
return writer
|
return writer
|
||||||
}
|
}
|
||||||
|
@ -329,11 +329,11 @@ func (s3Select *S3Select) Evaluate(w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(data) > maxRecordSize {
|
if len(data) > maxRecordSize {
|
||||||
writer.SendError("OverMaxRecordSize", "The length of a record in the input or result is greater than maxCharsPerRecord of 1 MB.")
|
writer.FinishWithError("OverMaxRecordSize", "The length of a record in the input or result is greater than maxCharsPerRecord of 1 MB.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = writer.SendRecords(data); err != nil {
|
if err = writer.SendRecord(data); err != nil {
|
||||||
// FIXME: log this error.
|
// FIXME: log this error.
|
||||||
err = nil
|
err = nil
|
||||||
return false
|
return false
|
||||||
@ -344,13 +344,7 @@ func (s3Select *S3Select) Evaluate(w http.ResponseWriter) {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
if s3Select.statement.LimitReached() {
|
if s3Select.statement.LimitReached() {
|
||||||
if err = writer.FlushRecords(); err != nil {
|
if err = writer.Finish(s3Select.getProgress()); err != nil {
|
||||||
// FIXME: log this error
|
|
||||||
err = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = writer.SendStats(s3Select.getProgress()); err != nil {
|
|
||||||
// FIXME: log this error.
|
// FIXME: log this error.
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@ -373,17 +367,10 @@ func (s3Select *S3Select) Evaluate(w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = writer.FlushRecords(); err != nil {
|
if err = writer.Finish(s3Select.getProgress()); err != nil {
|
||||||
// FIXME: log this error
|
|
||||||
err = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = writer.SendStats(s3Select.getProgress()); err != nil {
|
|
||||||
// FIXME: log this error.
|
// FIXME: log this error.
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,8 +391,7 @@ func (s3Select *S3Select) Evaluate(w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("SQL Err: %#v\n", err)
|
if serr := writer.FinishWithError("InternalError", err.Error()); serr != nil {
|
||||||
if serr := writer.SendError("InternalError", err.Error()); serr != nil {
|
|
||||||
// FIXME: log errors.
|
// FIXME: log errors.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user