blob: 4fe1f0cfb38832bc3043837599339f902601217a [file]
// 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