| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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. |
| |
| #include "exec/tuple-file-writer.h" |
| |
| #include <boost/filesystem.hpp> |
| |
| #include "common/logging.h" |
| #include "gutil/strings/substitute.h" |
| #include "kudu/util/array_view.h" |
| #include "kudu/util/env.h" |
| #include "runtime/mem-tracker.h" |
| #include "runtime/outbound-row-batch.h" |
| #include "runtime/row-batch.h" |
| #include "runtime/runtime-state.h" |
| #include "util/debug-util.h" |
| #include "util/kudu-status-util.h" |
| |
| #include "common/names.h" |
| |
| namespace filesystem = boost::filesystem; |
| |
| namespace impala { |
| |
| static const char* UNIQUE_PATH_SUFFIX = ".%%%%"; |
| |
| TupleFileWriter::TupleFileWriter( |
| std::string path, MemTracker* parent, RuntimeProfile* profile, |
| RequestWriteSizeCb request_write_size_cb) |
| : path_(move(path)), |
| temp_suffix_(filesystem::unique_path(UNIQUE_PATH_SUFFIX).string()), |
| tracker_(new MemTracker(-1, "TupleFileWriter", parent)), |
| allocator_(new CharMemTrackerAllocator(tracker_)), |
| out_batch_(new OutboundRowBatch(*allocator_)), |
| write_timer_(profile ? ADD_TIMER(profile, "TupleCacheWriteTime") : nullptr), |
| serialize_timer_(profile ? ADD_TIMER(profile, "TupleCacheSerializeTime") : nullptr), |
| bytes_written_(profile ? |
| ADD_COUNTER(profile, "TupleCacheBytesWritten", TUnit::BYTES) : nullptr), |
| request_write_size_cb_(move(request_write_size_cb)) {} |
| |
| TupleFileWriter::~TupleFileWriter() { |
| if (state_ != State::Uninitialized) { |
| if (tmp_file_ != nullptr) { |
| DCHECK_EQ(state_, State::InProgress); |
| Abort(); |
| } |
| |
| // The final state is either committed or aborted. In either case, the temporary file |
| // should be gone. |
| DCHECK(state_ == State::Committed || state_ == State::Aborted); |
| DCHECK(!kudu::Env::Default()->FileExists(TempPath())); |
| } |
| // MemTracker expects an explicit close. Consumers need to be released first. |
| // Since the TupleFileWriter can be freed before query execution ends, this must |
| // unregister the MemTracker from its parent before it is freed. |
| out_batch_.reset(); |
| allocator_.reset(); |
| if (tracker_) tracker_->CloseAndUnregisterFromParent(); |
| } |
| |
| Status TupleFileWriter::Open(RuntimeState* state) { |
| DCHECK_EQ(state_, State::Uninitialized); |
| VLOG_FILE << "Tuple Cache: Opening " << TempPath() << " for writing"; |
| KUDU_RETURN_IF_ERROR(kudu::Env::Default()->NewWritableFile(TempPath(), &tmp_file_), |
| "Failed to create tuple cache file"); |
| RETURN_IF_ERROR(DebugAction(state->query_options(), "TUPLE_FILE_WRITER_OPEN")); |
| state_ = State::InProgress; |
| return Status::OK(); |
| } |
| |
| Status TupleFileWriter::Write(RuntimeState* state, RowBatch* row_batch) { |
| DCHECK_EQ(state_, State::InProgress); |
| SCOPED_TIMER(write_timer_); |
| // serialize and write row batch |
| { |
| SCOPED_TIMER(serialize_timer_); |
| // Passing in nullptr for 'compression_scratch' disables compression. |
| RETURN_IF_ERROR( |
| row_batch->Serialize(out_batch_.get(), /* compression_scratch */ nullptr)); |
| } |
| |
| if (out_batch_->header()->num_rows() == 0) { |
| DCHECK_EQ(out_batch_->header()->uncompressed_size(), 0); |
| return Status::OK(); |
| } |
| |
| // Collect all of the pieces that we would want to write, then determine if writing |
| // them would exceed the max file size. |
| std::string header_buf; |
| if (!out_batch_->header()->SerializeToString(&header_buf)) { |
| return Status(TErrorCode::INTERNAL_ERROR, |
| "Could not serialize RowBatchHeaderPB to string"); |
| } |
| size_t header_len = header_buf.size(); |
| DCHECK_GT(header_len, 0); |
| kudu::Slice tuple_data = out_batch_->TupleDataAsSlice(); |
| kudu::Slice tuple_offsets = out_batch_->TupleOffsetsAsSlice(); |
| // tuple_data_len is possible to be 0, see IMPALA-13411. |
| size_t tuple_data_len = tuple_data.size(); |
| size_t tuple_offsets_len = tuple_offsets.size(); |
| DCHECK_GT(tuple_offsets_len, 0); |
| |
| // We write things in this order (sizes first, then the variable-sized data): |
| // 1. The size of the header |
| // 2. The size of the tuple data |
| // 3. The size of the tuple offsets |
| // 4. The serialized header |
| // 5. The tuple data |
| // 6. The tuple offsets |
| std::vector<kudu::Slice> slices = { |
| kudu::Slice(reinterpret_cast<const char*>(&header_len), sizeof(header_len)), |
| kudu::Slice(reinterpret_cast<const char*>(&tuple_data_len), |
| sizeof(tuple_data_len)), |
| kudu::Slice(reinterpret_cast<const char*>(&tuple_offsets_len), |
| sizeof(tuple_offsets_len)), |
| kudu::Slice(header_buf), |
| kudu::Slice(tuple_data), |
| kudu::Slice(tuple_offsets)}; |
| |
| // Enforce the max file size |
| size_t num_bytes_to_write = 0; |
| for (auto slice : slices) { |
| num_bytes_to_write += slice.size(); |
| } |
| |
| if (request_write_size_cb_ != nullptr) { |
| // If the request_write_size_cb is set, call it before writing to get permission. |
| // It will return a not-OK status if this writer should stop. |
| RETURN_IF_ERROR(request_write_size_cb_(BytesWritten() + num_bytes_to_write)); |
| } |
| |
| RETURN_IF_ERROR(DebugAction(state->query_options(), "TUPLE_FILE_WRITER_WRITE")); |
| |
| KUDU_RETURN_IF_ERROR( |
| tmp_file_->AppendV(kudu::ArrayView<const kudu::Slice>(slices)), |
| "Failed to write to cache file"); |
| COUNTER_ADD(bytes_written_, num_bytes_to_write); |
| |
| return Status::OK(); |
| } |
| |
| size_t TupleFileWriter::BytesWritten() const { |
| DCHECK(tmp_file_ != nullptr); |
| return tmp_file_->Size(); |
| } |
| |
| std::string TupleFileWriter::TempPath() const { |
| return path_ + temp_suffix_; |
| } |
| |
| void TupleFileWriter::Abort() { |
| DCHECK_EQ(state_, State::InProgress); |
| DCHECK(tmp_file_ != nullptr); |
| |
| kudu::Status status = tmp_file_->Close(); |
| if (!status.ok()) { |
| LOG(WARNING) << |
| Substitute("Failed to close file $0: $1", TempPath(), status.ToString()); |
| } |
| |
| // Delete the file. |
| status = kudu::Env::Default()->DeleteFile(TempPath()); |
| if (!status.ok()) { |
| LOG(WARNING) << |
| Substitute("Failed to unlink $0: $1", TempPath(), status.ToString()); |
| } |
| tmp_file_.reset(); |
| state_ = State::Aborted; |
| } |
| |
| Status TupleFileWriter::Commit(RuntimeState* state) { |
| DCHECK_EQ(state_, State::InProgress); |
| DCHECK(tmp_file_ != nullptr); |
| |
| RETURN_IF_ERROR(DebugAction(state->query_options(), "TUPLE_FILE_WRITER_COMMIT")); |
| |
| // Sync() is called asychnronously by TupleCacheMgr's sync thread pool. |
| KUDU_RETURN_IF_ERROR(tmp_file_->Close(), "Failed to close cache file"); |
| |
| std::string src = TempPath(); |
| KUDU_RETURN_IF_ERROR(kudu::Env::Default()->RenameFile(src, path_), |
| "Failed to rename cache file"); |
| tmp_file_.reset(); |
| state_ = State::Committed; |
| return Status::OK(); |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const TupleFileWriter::State& state) { |
| switch (state) { |
| case TupleFileWriter::State::Uninitialized: out << "Uninitialized"; break; |
| case TupleFileWriter::State::InProgress: out << "InProgress"; break; |
| case TupleFileWriter::State::Committed: out << "Committed"; break; |
| case TupleFileWriter::State::Aborted: out << "Aborted"; break; |
| } |
| return out; |
| } |
| |
| } // namespace impala |