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 := &parallelImporter{
   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  }