github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/importccl/read_import_base.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package importccl 10 11 import ( 12 "bytes" 13 "compress/bzip2" 14 "compress/gzip" 15 "context" 16 "fmt" 17 "io" 18 "io/ioutil" 19 "math" 20 "net/url" 21 "runtime" 22 "strings" 23 "sync/atomic" 24 "time" 25 26 "github.com/cockroachdb/cockroach/pkg/roachpb" 27 "github.com/cockroachdb/cockroach/pkg/sql/execinfra" 28 "github.com/cockroachdb/cockroach/pkg/sql/execinfrapb" 29 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 30 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 31 "github.com/cockroachdb/cockroach/pkg/sql/row" 32 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 33 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 34 "github.com/cockroachdb/cockroach/pkg/storage/cloud" 35 "github.com/cockroachdb/cockroach/pkg/util/ctxgroup" 36 "github.com/cockroachdb/cockroach/pkg/util/log" 37 "github.com/cockroachdb/cockroach/pkg/util/tracing" 38 "github.com/cockroachdb/errors" 39 ) 40 41 func runImport( 42 ctx context.Context, 43 flowCtx *execinfra.FlowCtx, 44 spec *execinfrapb.ReadImportDataSpec, 45 progCh chan execinfrapb.RemoteProducerMetadata_BulkProcessorProgress, 46 ) (*roachpb.BulkOpSummary, error) { 47 // Used to send ingested import rows to the KV layer. 48 kvCh := make(chan row.KVBatch, 10) 49 conv, err := makeInputConverter(ctx, spec, flowCtx.NewEvalCtx(), kvCh) 50 if err != nil { 51 return nil, err 52 } 53 54 // This group holds the go routines that are responsible for producing KV batches. 55 // After this group is done, we need to close kvCh. 56 // Depending on the import implementation both conv.start and conv.readFiles can 57 // produce KVs so we should close the channel only after *both* are finished. 58 producerGroup := ctxgroup.WithContext(ctx) 59 conv.start(producerGroup) 60 // Read input files into kvs 61 producerGroup.GoCtx(func(ctx context.Context) error { 62 ctx, span := tracing.ChildSpan(ctx, "readImportFiles") 63 defer tracing.FinishSpan(span) 64 var inputs map[int32]string 65 if spec.ResumePos != nil { 66 // Filter out files that were completely processed. 67 inputs = make(map[int32]string) 68 for id, name := range spec.Uri { 69 if seek, ok := spec.ResumePos[id]; !ok || seek < math.MaxInt64 { 70 inputs[id] = name 71 } 72 } 73 } else { 74 inputs = spec.Uri 75 } 76 77 return conv.readFiles(ctx, inputs, spec.ResumePos, spec.Format, flowCtx.Cfg.ExternalStorage) 78 }) 79 80 // This group links together the producers (via producerGroup) and the KV ingester. 81 group := ctxgroup.WithContext(ctx) 82 group.Go(func() error { 83 defer close(kvCh) 84 return producerGroup.Wait() 85 }) 86 87 // Ingest the KVs that the producer group emitted to the chan and the row result 88 // at the end is one row containing an encoded BulkOpSummary. 89 var summary *roachpb.BulkOpSummary 90 group.GoCtx(func(ctx context.Context) error { 91 summary, err = ingestKvs(ctx, flowCtx, spec, progCh, kvCh) 92 if err != nil { 93 return err 94 } 95 var prog execinfrapb.RemoteProducerMetadata_BulkProcessorProgress 96 prog.ResumePos = make(map[int32]int64) 97 prog.CompletedFraction = make(map[int32]float32) 98 for i := range spec.Uri { 99 prog.CompletedFraction[i] = 1.0 100 prog.ResumePos[i] = math.MaxInt64 101 } 102 progCh <- prog 103 return nil 104 }) 105 106 if err := group.Wait(); err != nil { 107 return nil, err 108 } 109 110 return summary, nil 111 } 112 113 type readFileFunc func(context.Context, *fileReader, int32, int64, chan string) error 114 115 // readInputFile reads each of the passed dataFiles using the passed func. The 116 // key part of dataFiles is the unique index of the data file among all files in 117 // the IMPORT. progressFn, if not nil, is periodically invoked with a percentage 118 // of the total progress of reading through all of the files. This percentage 119 // attempts to use the Size() method of ExternalStorage to determine how many 120 // bytes must be read of the input files, and reports the percent of bytes read 121 // among all dataFiles. If any Size() fails for any file, then progress is 122 // reported only after each file has been read. 123 func readInputFiles( 124 ctx context.Context, 125 dataFiles map[int32]string, 126 resumePos map[int32]int64, 127 format roachpb.IOFileFormat, 128 fileFunc readFileFunc, 129 makeExternalStorage cloud.ExternalStorageFactory, 130 ) error { 131 done := ctx.Done() 132 133 fileSizes := make(map[int32]int64, len(dataFiles)) 134 135 // Attempt to fetch total number of bytes for all files. 136 for id, dataFile := range dataFiles { 137 conf, err := cloud.ExternalStorageConfFromURI(dataFile) 138 if err != nil { 139 return err 140 } 141 es, err := makeExternalStorage(ctx, conf) 142 if err != nil { 143 return err 144 } 145 sz, err := es.Size(ctx, "") 146 es.Close() 147 if sz <= 0 { 148 // Don't log dataFile here because it could leak auth information. 149 log.Infof(ctx, "could not fetch file size; falling back to per-file progress: %v", err) 150 break 151 } 152 fileSizes[id] = sz 153 } 154 155 for dataFileIndex, dataFile := range dataFiles { 156 select { 157 case <-done: 158 return ctx.Err() 159 default: 160 } 161 if err := func() error { 162 conf, err := cloud.ExternalStorageConfFromURI(dataFile) 163 if err != nil { 164 return err 165 } 166 es, err := makeExternalStorage(ctx, conf) 167 if err != nil { 168 return err 169 } 170 defer es.Close() 171 raw, err := es.ReadFile(ctx, "") 172 if err != nil { 173 return err 174 } 175 defer raw.Close() 176 177 src := &fileReader{total: fileSizes[dataFileIndex], counter: byteCounter{r: raw}} 178 decompressed, err := decompressingReader(&src.counter, dataFile, format.Compression) 179 if err != nil { 180 return err 181 } 182 defer decompressed.Close() 183 src.Reader = decompressed 184 185 var rejected chan string 186 if (format.Format == roachpb.IOFileFormat_CSV && format.SaveRejected) || 187 (format.Format == roachpb.IOFileFormat_MysqlOutfile && format.SaveRejected) { 188 rejected = make(chan string) 189 } 190 if rejected != nil { 191 grp := ctxgroup.WithContext(ctx) 192 grp.GoCtx(func(ctx context.Context) error { 193 var buf []byte 194 var countRejected int64 195 for s := range rejected { 196 countRejected++ 197 if countRejected > 1000 { // TODO(spaskob): turn the magic constant into an option 198 return pgerror.Newf( 199 pgcode.DataCorrupted, 200 "too many parsing errors (%d) encountered for file %s", 201 countRejected, 202 dataFile, 203 ) 204 } 205 buf = append(buf, s...) 206 } 207 if countRejected == 0 { 208 // no rejected rows 209 return nil 210 } 211 rejFn, err := rejectedFilename(dataFile) 212 if err != nil { 213 return err 214 } 215 conf, err := cloud.ExternalStorageConfFromURI(rejFn) 216 if err != nil { 217 return err 218 } 219 rejectedStorage, err := makeExternalStorage(ctx, conf) 220 if err != nil { 221 return err 222 } 223 defer rejectedStorage.Close() 224 if err := rejectedStorage.WriteFile(ctx, "", bytes.NewReader(buf)); err != nil { 225 return err 226 } 227 return nil 228 }) 229 230 grp.GoCtx(func(ctx context.Context) error { 231 defer close(rejected) 232 if err := fileFunc(ctx, src, dataFileIndex, resumePos[dataFileIndex], rejected); err != nil { 233 return err 234 } 235 return nil 236 }) 237 238 if err := grp.Wait(); err != nil { 239 return errors.Wrapf(err, "%s", dataFile) 240 } 241 } else { 242 if err := fileFunc(ctx, src, dataFileIndex, resumePos[dataFileIndex], nil /* rejected */); err != nil { 243 return errors.Wrapf(err, "%s", dataFile) 244 } 245 } 246 return nil 247 }(); err != nil { 248 return err 249 } 250 } 251 return nil 252 } 253 254 func rejectedFilename(datafile string) (string, error) { 255 parsedURI, err := url.Parse(datafile) 256 if err != nil { 257 return "", err 258 } 259 parsedURI.Path = parsedURI.Path + ".rejected" 260 return parsedURI.String(), nil 261 } 262 263 func decompressingReader( 264 in io.Reader, name string, hint roachpb.IOFileFormat_Compression, 265 ) (io.ReadCloser, error) { 266 switch guessCompressionFromName(name, hint) { 267 case roachpb.IOFileFormat_Gzip: 268 return gzip.NewReader(in) 269 case roachpb.IOFileFormat_Bzip: 270 return ioutil.NopCloser(bzip2.NewReader(in)), nil 271 default: 272 return ioutil.NopCloser(in), nil 273 } 274 } 275 276 func guessCompressionFromName( 277 name string, hint roachpb.IOFileFormat_Compression, 278 ) roachpb.IOFileFormat_Compression { 279 if hint != roachpb.IOFileFormat_Auto { 280 return hint 281 } 282 switch { 283 case strings.HasSuffix(name, ".gz"): 284 return roachpb.IOFileFormat_Gzip 285 case strings.HasSuffix(name, ".bz2") || strings.HasSuffix(name, ".bz"): 286 return roachpb.IOFileFormat_Bzip 287 default: 288 if parsed, err := url.Parse(name); err == nil && parsed.Path != name { 289 return guessCompressionFromName(parsed.Path, hint) 290 } 291 return roachpb.IOFileFormat_None 292 } 293 } 294 295 type byteCounter struct { 296 r io.Reader 297 n int64 298 } 299 300 func (b *byteCounter) Read(p []byte) (int, error) { 301 n, err := b.r.Read(p) 302 b.n += int64(n) 303 return n, err 304 } 305 306 type fileReader struct { 307 io.Reader 308 total int64 309 counter byteCounter 310 } 311 312 func (f fileReader) ReadFraction() float32 { 313 if f.total == 0 { 314 return 0.0 315 } 316 return float32(f.counter.n) / float32(f.total) 317 } 318 319 type inputConverter interface { 320 start(group ctxgroup.Group) 321 readFiles( 322 ctx context.Context, 323 dataFiles map[int32]string, 324 resumePos map[int32]int64, 325 format roachpb.IOFileFormat, 326 makeExternalStorage cloud.ExternalStorageFactory, 327 ) error 328 } 329 330 func isMultiTableFormat(format roachpb.IOFileFormat_FileFormat) bool { 331 switch format { 332 case roachpb.IOFileFormat_Mysqldump, 333 roachpb.IOFileFormat_PgDump: 334 return true 335 } 336 return false 337 } 338 339 func makeRowErr(_ string, row int64, code, format string, args ...interface{}) error { 340 err := pgerror.NewWithDepthf(1, code, format, args...) 341 err = errors.WrapWithDepthf(1, err, "row %d", row) 342 return err 343 } 344 345 func wrapRowErr(err error, _ string, row int64, code, format string, args ...interface{}) error { 346 if format != "" || len(args) > 0 { 347 err = errors.WrapWithDepthf(1, err, format, args...) 348 } 349 err = errors.WrapWithDepthf(1, err, "row %d", row) 350 if code != pgcode.Uncategorized { 351 err = pgerror.WithCandidateCode(err, code) 352 } 353 return err 354 } 355 356 // importRowError is an error type describing malformed import data. 357 type importRowError struct { 358 err error 359 row string 360 rowNum int64 361 } 362 363 func (e *importRowError) Error() string { 364 return fmt.Sprintf("error parsing row %d: %v (row: %q)", e.rowNum, e.err, e.row) 365 } 366 367 func newImportRowError(err error, row string, num int64) error { 368 return &importRowError{ 369 err: err, 370 row: row, 371 rowNum: num, 372 } 373 } 374 375 // parallelImportContext describes state associated with the import. 376 type parallelImportContext struct { 377 walltime int64 // Import time stamp. 378 numWorkers int // Parallelism 379 batchSize int // Number of records to batch 380 evalCtx *tree.EvalContext // Evaluation context. 381 tableDesc *sqlbase.TableDescriptor // Table descriptor we're importing into. 382 targetCols tree.NameList // List of columns to import. nil if importing all columns. 383 kvCh chan row.KVBatch // Channel for sending KV batches. 384 } 385 386 // importFileContext describes state specific to a file being imported. 387 type importFileContext struct { 388 source int32 // Source is where the row data in the batch came from. 389 skip int64 // Number of records to skip 390 rejected chan string // Channel for reporting corrupt "rows" 391 } 392 393 // handleCorruptRow reports an error encountered while processing a row 394 // in an input file. 395 func handleCorruptRow(ctx context.Context, fileCtx *importFileContext, err error) error { 396 log.Errorf(ctx, "%v", err) 397 398 if rowErr := (*importRowError)(nil); errors.As(err, &rowErr) && fileCtx.rejected != nil { 399 fileCtx.rejected <- rowErr.row + "\n" 400 return nil 401 } 402 403 return err 404 } 405 406 func makeDatumConverter( 407 ctx context.Context, importCtx *parallelImportContext, fileCtx *importFileContext, 408 ) (*row.DatumRowConverter, error) { 409 conv, err := row.NewDatumRowConverter( 410 ctx, importCtx.tableDesc, importCtx.targetCols, importCtx.evalCtx.Copy(), importCtx.kvCh) 411 if err == nil { 412 conv.KvBatch.Source = fileCtx.source 413 } 414 return conv, err 415 } 416 417 // importRowProducer is producer of "rows" that must be imported. 418 // Row is an opaque interface{} object which will be passed onto 419 // the consumer implementation. 420 // The implementations of this interface need not need to be thread safe. 421 // However, since the data returned by the Row() method may be 422 // handled by a different go-routine, the data returned must not access, 423 // or reference internal state in a thread-unsafe way. 424 type importRowProducer interface { 425 // Scan returns true if there is more data available. 426 // After Scan() returns false, the caller should verify 427 // that the scanner has not encountered an error (Err() == nil). 428 Scan() bool 429 430 // Err returns an error (if any) encountered while processing rows. 431 Err() error 432 433 // Skip, as the name implies, skips the current record in this stream. 434 Skip() error 435 436 // Row returns current row (record). 437 Row() (interface{}, error) 438 439 // Progress returns a fraction of the input that has been consumed so far. 440 Progress() float32 441 } 442 443 // importRowConsumer consumes the data produced by the importRowProducer. 444 // Implementations of this interface do not need to be thread safe. 445 type importRowConsumer interface { 446 // FillDatums sends row data to the provide datum converter. 447 FillDatums(row interface{}, rowNum int64, conv *row.DatumRowConverter) error 448 } 449 450 // batch represents batch of data to convert. 451 type batch struct { 452 data []interface{} 453 startPos int64 454 progress float32 455 } 456 457 // parallelImporter is a helper to facilitate running input 458 // conversion using parallel workers. 459 type parallelImporter struct { 460 b batch 461 batchSize int 462 recordCh chan batch 463 } 464 465 var parallelImporterReaderBatchSize = 500 466 467 // TestingSetParallelImporterReaderBatchSize is a testing knob to modify 468 // csv input reader batch size. 469 // Returns a function that resets the value back to the default. 470 func TestingSetParallelImporterReaderBatchSize(s int) func() { 471 parallelImporterReaderBatchSize = s 472 return func() { 473 parallelImporterReaderBatchSize = 500 474 } 475 } 476 477 // runParallelImport reads the data produced by 'producer' and sends 478 // the data to a set of workers responsible for converting this data to the 479 // appropriate key/values. 480 func runParallelImport( 481 ctx context.Context, 482 importCtx *parallelImportContext, 483 fileCtx *importFileContext, 484 producer importRowProducer, 485 consumer importRowConsumer, 486 ) error { 487 batchSize := importCtx.batchSize 488 if batchSize <= 0 { 489 batchSize = parallelImporterReaderBatchSize 490 } 491 importer := ¶llelImporter{ 492 b: batch{ 493 data: make([]interface{}, 0, batchSize), 494 }, 495 batchSize: batchSize, 496 recordCh: make(chan batch), 497 } 498 499 group := ctxgroup.WithContext(ctx) 500 501 // Start consumers. 502 parallelism := importCtx.numWorkers 503 if parallelism <= 0 { 504 parallelism = runtime.NumCPU() 505 } 506 507 minEmited := make([]int64, parallelism) 508 group.GoCtx(func(ctx context.Context) error { 509 ctx, span := tracing.ChildSpan(ctx, "inputconverter") 510 defer tracing.FinishSpan(span) 511 return ctxgroup.GroupWorkers(ctx, parallelism, func(ctx context.Context, id int) error { 512 return importer.importWorker(ctx, id, consumer, importCtx, fileCtx, minEmited) 513 }) 514 }) 515 516 // Read data from producer and send it to consumers. 517 group.GoCtx(func(ctx context.Context) error { 518 defer close(importer.recordCh) 519 520 var count int64 521 for producer.Scan() { 522 // Skip rows if needed. 523 count++ 524 if count <= fileCtx.skip { 525 if err := producer.Skip(); err != nil { 526 return err 527 } 528 continue 529 } 530 531 // Batch parsed data. 532 data, err := producer.Row() 533 if err != nil { 534 if err = handleCorruptRow(ctx, fileCtx, err); err != nil { 535 return err 536 } 537 continue 538 } 539 540 if err := importer.add(ctx, data, count, producer.Progress); err != nil { 541 return err 542 } 543 } 544 545 if producer.Err() == nil { 546 return importer.flush(ctx) 547 } 548 return producer.Err() 549 }) 550 551 return group.Wait() 552 } 553 554 // Adds data to the current batch, flushing batches as needed. 555 func (p *parallelImporter) add( 556 ctx context.Context, data interface{}, pos int64, progress func() float32, 557 ) error { 558 if len(p.b.data) == 0 { 559 p.b.startPos = pos 560 } 561 p.b.data = append(p.b.data, data) 562 563 if len(p.b.data) == p.batchSize { 564 p.b.progress = progress() 565 return p.flush(ctx) 566 } 567 return nil 568 } 569 570 // Flush flushes currently accumulated data. 571 func (p *parallelImporter) flush(ctx context.Context) error { 572 select { 573 case <-ctx.Done(): 574 return ctx.Err() 575 default: 576 } 577 578 // if the batch isn't empty, we need to flush it. 579 if len(p.b.data) > 0 { 580 p.recordCh <- p.b 581 p.b = batch{ 582 data: make([]interface{}, 0, cap(p.b.data)), 583 } 584 } 585 return nil 586 } 587 588 func (p *parallelImporter) importWorker( 589 ctx context.Context, 590 workerID int, 591 consumer importRowConsumer, 592 importCtx *parallelImportContext, 593 fileCtx *importFileContext, 594 minEmitted []int64, 595 ) error { 596 conv, err := makeDatumConverter(ctx, importCtx, fileCtx) 597 if err != nil { 598 return err 599 } 600 if conv.EvalCtx.SessionData == nil { 601 panic("uninitialized session data") 602 } 603 604 var rowNum int64 605 epoch := time.Date(2015, time.January, 1, 0, 0, 0, 0, time.UTC).UnixNano() 606 const precision = uint64(10 * time.Microsecond) 607 timestamp := uint64(importCtx.walltime-epoch) / precision 608 609 conv.CompletedRowFn = func() int64 { 610 m := emittedRowLowWatermark(workerID, rowNum, minEmitted) 611 return m 612 } 613 614 for batch := range p.recordCh { 615 conv.KvBatch.Progress = batch.progress 616 for batchIdx, record := range batch.data { 617 rowNum = batch.startPos + int64(batchIdx) 618 if err := consumer.FillDatums(record, rowNum, conv); err != nil { 619 if err = handleCorruptRow(ctx, fileCtx, err); err != nil { 620 return err 621 } 622 continue 623 } 624 625 rowIndex := int64(timestamp) + rowNum 626 if err := conv.Row(ctx, conv.KvBatch.Source, rowIndex); err != nil { 627 return newImportRowError(err, fmt.Sprintf("%v", record), rowNum) 628 } 629 } 630 } 631 return conv.SendBatch(ctx) 632 } 633 634 // Updates emitted row for the specified worker and returns 635 // low watermark for the emitted rows across all workers. 636 func emittedRowLowWatermark(workerID int, emittedRow int64, minEmitted []int64) int64 { 637 atomic.StoreInt64(&minEmitted[workerID], emittedRow) 638 639 for i := 0; i < len(minEmitted); i++ { 640 if i != workerID { 641 w := atomic.LoadInt64(&minEmitted[i]) 642 if w < emittedRow { 643 emittedRow = w 644 } 645 } 646 } 647 648 return emittedRow 649 }