Uh oh!
There was an error while loading. Please reload this page.
[SPARK-24194] [SQL]HadoopFsRelation cannot overwrite a path that is also being read from. - #21257
[SPARK-24194] [SQL]HadoopFsRelation cannot overwrite a path that is also being read from.#21257zheh12 wants to merge 1 commit into
Conversation
cc @cloud-fan@jiangxb1987 |
There was a problem hiding this comment.
this deletes leaf files one by one, have you evaluated the performance difference?
There was a problem hiding this comment.
First of all, if it is the root directory of the table, I must record all the files in the directory, and wait until the job is commited to delete. Because the _temporary of the entire job is also in the directory, I cannot directly delete the entire directory.
Second, when we record the files that need to be deleted, we just list the files in the root directory non-recursively. Under normal circumstances, the number of files in the first-level directory of the partition table will not be too much.
In the end, this will certainly be slower than directly deleting the entire directory, but under the current implementation, we cannot directly delete the entire table directory.
There was a problem hiding this comment.
is it possible to only do it with overwrite? we should not introduce perf regression when not necessary.
There was a problem hiding this comment.
have you considered the approach taken by dynamicPartitionOverwrite? i.e. using staging directory.
There was a problem hiding this comment.
- From the code point of view, the current implementation is
deleteMatchingPartitionshappend only ifoverwriteis specified. - Using
dynamicPartitionOverwritewill not solve this problem,because it will also generate a.stagedirectory under the table root directory. We still need to record all the files we want to delete, but we cannot directly delete the root directories.
The dynamic partition overwrite is actually recording all the partitions that need to be deleted and then deleted one by one. And the entire tableoverwritedeletes all the data of the entire directory, it needs to record all deleted partition directory files,so in fact the implementation of the code is similar withdynamicPartitionOverwrite.
There was a problem hiding this comment.
If I do this, when the job is committed, it will delete the entire output directory. And there will be no data.
There was a problem hiding this comment.
we will delete files just before committing job, do we?
There was a problem hiding this comment.
The key is that the data is already in the output directory before committing job, and we can't delete the output directory anymore.
We overloaded FileCommitProtocol in the HadoopMapReduceCommitProtocol with the deleteWithJob method. Now it will not delete the file immediately, but it will wait until the entire job is committed.
We did delete the files with committed the job, but the temporary output files were generated when the task was started. These temporary output files are in the output directory. And the data will be move out to the output directory.
After the job starts, there is no safe time to delete the entire output directory.
There was a problem hiding this comment.
ok, then how about adding a new parameter canDeleteNow: Boolean to FileCommitProtocol.deleteWithJob?
There was a problem hiding this comment.
That's a good idea. I change my code.
There was a problem hiding this comment.
cloud-fan
commented
May 8, 2018
cc @ericl |
19e6692 to
a51620bComparecloud-fan
commented
May 9, 2018
ok to test |
SparkQA
commented
May 9, 2018
Test build #90400 has finished for PR 21257 at commit
|
zheh12
commented
May 9, 2018
Jenkins, retest this please. |
zheh12
commented
May 9, 2018
cc @cloud-fan, Jenkins has some error, please help me retest, thanks |
cloud-fan
commented
May 9, 2018
retest this please |
SparkQA
commented
May 9, 2018
Test build #90413 has finished for PR 21257 at commit
|
There was a problem hiding this comment.
seems the recursive is always passed as true? can we remove it?
There was a problem hiding this comment.
In the current situation we can delete it, but I feel it better to use a default value true.
There was a problem hiding this comment.
Is there any (potential) cases we need a recursive parameter?
There was a problem hiding this comment.
I will remove the recursive parameter.
There was a problem hiding this comment.
will this be different from stagingDir.getFileSystem(jobContext.getConfiguration)?
There was a problem hiding this comment.
StagingDir is not always be valid hadoop path, but the JobContext work dir always be.
There was a problem hiding this comment.
can we change other places in this method to use the fs created here?
There was a problem hiding this comment.
I'm not sure you can guarantee that the working dir is always the dest FS. At least with @rdblue's committers, task attempts work dirs are in file:// & task commit (somehow) gets them to the destFS in a form where job commit will make them visible.
There was a problem hiding this comment.
I change my code.
I now record every FileSystem will delete the path with a map structure. And Don't assume that they will use the same FileSystem.
There was a problem hiding this comment.
isInReadPath or inReadPath or isReadPath better?
There was a problem hiding this comment.
I'd personally ignore a failure on delete(), as the conditions for the API call are "if this doesn't raise an exception then the dest is gone". You can skip the exists check as it will be superfluous
SparkQA
commented
May 14, 2018
Test build #90593 has finished for PR 21257 at commit
|
SparkQA
commented
May 15, 2018
Test build #90619 has finished for PR 21257 at commit
|
HyukjinKwon
commented
May 15, 2018
retest this please |
SparkQA
commented
May 15, 2018
Test build #90632 has finished for PR 21257 at commit
|
There was a problem hiding this comment.
- you don't need to do the exists check, it's just overhead. delete() will return false if there was nothing to delete.
- But...what if that delete throws an exception? Should the commit fail (as it does now?), or downgraded. As an example, the hadoop
FileOutputCommtteruses the option"mapreduce.fileoutputcommitter.cleanup-failures.ignoredto choose what to do there - ...and: what about cleanup in an abort job?
I think you'd be best off isolating this cleanup into its own method and call from both job commit & job abort, in job commit discuss with others what to do, and in job abort just log & continue
There was a problem hiding this comment.
I think we should not delete the data when the task is aborted. The semantics ofdescriptionWithJob should be to delete the data when the Job is commited.
I change code for handling exceptions.
SparkQA
commented
May 15, 2018
Test build #90636 has finished for PR 21257 at commit
|
SparkQA
commented
May 19, 2018
Test build #90826 has finished for PR 21257 at commit
|
gatorsmile
commented
May 21, 2018
Update the PR title? |
There was a problem hiding this comment.
Nit: style issue. Can we follow the indents in the method declaration? https://github.com/databricks/scala-style-guide#spacing-and-indentation
SparkQA
commented
May 22, 2018
Test build #90957 has finished for PR 21257 at commit
|
SparkQA
commented
Jun 21, 2018
Test build #92165 has finished for PR 21257 at commit
|
| for (path <- pathsToDelete(fs)) { | ||
| try { | ||
| if (!fs.delete(path, true)) { | ||
| logWarning(s"Delete path ${path} fail at job commit time") |
There was a problem hiding this comment.
delete -> false just means there was nothing there, I wouldn't warn at that point. Unless delete() throws an exception you assume that when the call returns, fs.exists(path) does not hold -regardless of the return value. (Special exception, the dest is "/")
| } catch { | ||
| case ex: IOException => | ||
| throw new IOException(s"Unable to clear output " + | ||
| s"file ${path} at job commit time", ex) |
There was a problem hiding this comment.
recommend including ex.toString() in the new exception raised, as child exception text can often get lost
| while (files.hasNext) { | ||
| val file = files.next() | ||
| if (!committer.deleteWithJob(fs, file.getPath, false)) { | ||
| throw new IOException(s"Unable to clear output " + |
There was a problem hiding this comment.
as committer.deleteWithJob() returns true in base class, that check won't do much, at least not with the default impl. Probably better just to have deleteWithJob() return Unit, require callers to raise an exception on a delete failure. Given that delete() is required to say "dest doesn't exist if you return", I don't think they need to do any checks at all
| if (fs.exists(staticPrefixPath)) { | ||
| if (staticPartitionPrefix.isEmpty && outputCheck) { | ||
| // input contain output, only delete output sub files when job commit | ||
| val files = fs.listFiles(staticPrefixPath, false) |
There was a problem hiding this comment.
if there are a lot of files here, you've gone from a dir delete which was O(1) on a fileystem, probably O(descendant) on an object store to at O(children) on an FS, O(children * descendants (chlld)) op here. Not significant for a small number of files, but could potentially be expensive. Why do the iteration at all?
| } | ||
| } else { | ||
| if (!committer.deleteWithJob(fs, staticPrefixPath, true)) { | ||
| throw new IOException(s"Unable to clear output " + |
There was a problem hiding this comment.
again, hard to see how this exception path would be reached.
| /** | ||
| * now just record the file to be delete | ||
| */ | ||
| override def deleteWithJob(fs: FileSystem, path: Path, |
There was a problem hiding this comment.
No need to worry about concurrent access here, correct?
steveloughran
commented
Jun 21, 2018
some overall thought
I'll have to look a bit closer at what happens in committer cleanups right now, though as they are focused on rm -f $dest/__temporary/$jobAttempt, they are less worried about failures here as it shoudn't be changing any public datasets |
AmplabJenkins
commented
Sep 27, 2018
Can one of the admins verify this patch? |
HyukjinKwon
commented
Nov 11, 2018
ping @zheh12 to address comments. I am going to suggest to close this for now while I am identifying PRs to close now. |
Closesapache#21766Closesapache#21679Closesapache#21161Closesapache#20846Closesapache#19434Closesapache#18080Closesapache#17648Closesapache#17169 Add: Closesapache#22813Closesapache#21994Closesapache#22005Closesapache#22463 Add: Closesapache#15899 Add: Closesapache#22539Closesapache#21868Closesapache#21514Closesapache#21402Closesapache#21322Closesapache#21257Closesapache#20163Closesapache#19691Closesapache#18697Closesapache#18636Closesapache#17176Closesapache#23001 from wangyum/CloseStalePRs. Authored-by: Yuming Wang <yumwang@ebay.com> Signed-off-by: hyukjinkwon <gurwls223@apache.org>
What changes were proposed in this pull request?
When insert overwrite a parquet table. There is a check.
The check throws exception if output path tries to overwrite the same input path. This check(limitation) only exists in datasource table but not hive table. Shall we remove this check?
We cannot read and overwrite a
HadoopFsRelationwith the same path -- input and output should be different. The reason is that spark deletes the output partition path before reading.This pr proposes to mark/cache the paths(to delete) before reading. And postpone deletion when commit job.
How was this patch tested?
I just udpated
InsertSuiteandMetastoreDataSourceSuite.