2019-12-06 14:37:21 +00:00
|
|
|
#include <Common/config.h>
|
|
|
|
|
|
|
|
#if USE_AWS_S3
|
2019-06-01 21:18:20 +00:00
|
|
|
|
2020-01-28 13:05:37 +00:00
|
|
|
# include <IO/WriteBufferFromS3.h>
|
|
|
|
# include <IO/WriteHelpers.h>
|
Do not catch exceptions during final flush in writers destructors
Since this hides real problems, since destructor does final flush and if
it fails, then data will be lost.
One of such examples if MEMORY_LIMIT_EXCEEDED exception, so lock
exceptions from destructors, by using
MemoryTracker::LockExceptionInThread to block these exception, and allow
others (so std::terminate will be called, since this is c++11 with
noexcept for destructors by default).
Here is an example, that leads to empty block in the distributed batch:
2021.01.21 12:43:18.619739 [ 46468 ] {7bd60d75-ebcb-45d2-874d-260df9a4ddac} <Error> virtual DB::CompressedWriteBuffer::~CompressedWriteBuffer(): Code: 241, e.displayText() = DB::Exception: Memory limit (for user) exceeded: would use 332.07 GiB (attempt to allocate chunk of 4355342 bytes), maximum: 256.00 GiB, Stack trace (when copying this message, always include the lines below):
0. DB::Exception::Exception<>() @ 0x86f7b88 in /usr/bin/clickhouse
...
4. void DB::PODArrayBase<>::resize<>(unsigned long) @ 0xe9e878d in /usr/bin/clickhouse
5. DB::CompressedWriteBuffer::nextImpl() @ 0xe9f0296 in /usr/bin/clickhouse
6. DB::CompressedWriteBuffer::~CompressedWriteBuffer() @ 0xe9f0415 in /usr/bin/clickhouse
7. DB::DistributedBlockOutputStream::writeToShard() @ 0xf6bed4a in /usr/bin/clickhouse
2021-01-22 18:56:50 +00:00
|
|
|
# include <Common/MemoryTracker.h>
|
2019-06-17 07:16:43 +00:00
|
|
|
|
2020-01-28 13:05:37 +00:00
|
|
|
# include <aws/s3/S3Client.h>
|
|
|
|
# include <aws/s3/model/CreateMultipartUploadRequest.h>
|
2020-12-09 14:09:04 +00:00
|
|
|
# include <aws/s3/model/CompleteMultipartUploadRequest.h>
|
2020-07-09 14:09:17 +00:00
|
|
|
# include <aws/s3/model/PutObjectRequest.h>
|
2020-01-28 13:05:37 +00:00
|
|
|
# include <aws/s3/model/UploadPartRequest.h>
|
|
|
|
# include <common/logger_useful.h>
|
2019-12-03 16:23:24 +00:00
|
|
|
|
2020-01-28 13:05:37 +00:00
|
|
|
# include <utility>
|
2019-06-01 21:18:20 +00:00
|
|
|
|
|
|
|
|
2020-07-10 09:32:34 +00:00
|
|
|
namespace ProfileEvents
|
|
|
|
{
|
|
|
|
extern const Event S3WriteBytes;
|
|
|
|
}
|
|
|
|
|
2021-03-22 19:12:42 +00:00
|
|
|
|
2019-06-01 21:18:20 +00:00
|
|
|
namespace DB
|
|
|
|
{
|
2019-09-23 07:42:02 +00:00
|
|
|
// S3 protocol does not allow to have multipart upload with more than 10000 parts.
|
|
|
|
// In case server does not return an error on exceeding that number, we print a warning
|
|
|
|
// because custom S3 implementation may allow relaxed requirements on that.
|
2019-09-22 10:42:47 +00:00
|
|
|
const int S3_WARN_MAX_PARTS = 10000;
|
|
|
|
|
|
|
|
|
2019-06-17 07:16:43 +00:00
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
2019-12-03 16:23:24 +00:00
|
|
|
extern const int S3_ERROR;
|
2019-06-17 07:16:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-01 21:18:20 +00:00
|
|
|
WriteBufferFromS3::WriteBufferFromS3(
|
2019-12-03 16:23:24 +00:00
|
|
|
std::shared_ptr<Aws::S3::S3Client> client_ptr_,
|
|
|
|
const String & bucket_,
|
|
|
|
const String & key_,
|
2019-06-21 05:24:01 +00:00
|
|
|
size_t minimum_upload_part_size_,
|
2020-12-09 14:09:04 +00:00
|
|
|
size_t max_single_part_upload_size_,
|
2020-12-08 18:31:57 +00:00
|
|
|
std::optional<std::map<String, String>> object_metadata_,
|
2020-01-28 13:05:37 +00:00
|
|
|
size_t buffer_size_)
|
2019-12-03 16:23:24 +00:00
|
|
|
: BufferWithOwnMemory<WriteBuffer>(buffer_size_, nullptr, 0)
|
|
|
|
, bucket(bucket_)
|
|
|
|
, key(key_)
|
2020-12-08 18:31:57 +00:00
|
|
|
, object_metadata(std::move(object_metadata_))
|
2019-12-03 16:23:24 +00:00
|
|
|
, client_ptr(std::move(client_ptr_))
|
2020-12-09 14:09:04 +00:00
|
|
|
, minimum_upload_part_size(minimum_upload_part_size_)
|
|
|
|
, max_single_part_upload_size(max_single_part_upload_size_)
|
2021-03-17 14:20:55 +00:00
|
|
|
{
|
|
|
|
allocateBuffer();
|
|
|
|
}
|
2019-06-01 21:18:20 +00:00
|
|
|
|
2019-06-17 00:06:14 +00:00
|
|
|
void WriteBufferFromS3::nextImpl()
|
|
|
|
{
|
|
|
|
if (!offset())
|
|
|
|
return;
|
|
|
|
|
2019-06-17 00:42:47 +00:00
|
|
|
temporary_buffer->write(working_buffer.begin(), offset());
|
|
|
|
|
2020-07-10 09:32:34 +00:00
|
|
|
ProfileEvents::increment(ProfileEvents::S3WriteBytes, offset());
|
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
last_part_size += offset();
|
|
|
|
|
|
|
|
/// Data size exceeds singlepart upload threshold, need to use multipart upload.
|
|
|
|
if (multipart_upload_id.empty() && last_part_size > max_single_part_upload_size)
|
|
|
|
createMultipartUpload();
|
|
|
|
|
|
|
|
if (!multipart_upload_id.empty() && last_part_size > minimum_upload_part_size)
|
2019-06-17 00:42:47 +00:00
|
|
|
{
|
2020-12-09 14:09:04 +00:00
|
|
|
writePart();
|
2021-03-17 14:20:55 +00:00
|
|
|
allocateBuffer();
|
2019-06-17 00:42:47 +00:00
|
|
|
}
|
2019-06-17 00:06:14 +00:00
|
|
|
}
|
|
|
|
|
2021-03-17 14:20:55 +00:00
|
|
|
void WriteBufferFromS3::allocateBuffer()
|
|
|
|
{
|
|
|
|
temporary_buffer = Aws::MakeShared<Aws::StringStream>("temporary buffer");
|
|
|
|
temporary_buffer->exceptions(std::ios::badbit);
|
|
|
|
last_part_size = 0;
|
|
|
|
}
|
|
|
|
|
2019-06-01 21:18:20 +00:00
|
|
|
void WriteBufferFromS3::finalize()
|
|
|
|
{
|
Do not catch exceptions during final flush in writers destructors
Since this hides real problems, since destructor does final flush and if
it fails, then data will be lost.
One of such examples if MEMORY_LIMIT_EXCEEDED exception, so lock
exceptions from destructors, by using
MemoryTracker::LockExceptionInThread to block these exception, and allow
others (so std::terminate will be called, since this is c++11 with
noexcept for destructors by default).
Here is an example, that leads to empty block in the distributed batch:
2021.01.21 12:43:18.619739 [ 46468 ] {7bd60d75-ebcb-45d2-874d-260df9a4ddac} <Error> virtual DB::CompressedWriteBuffer::~CompressedWriteBuffer(): Code: 241, e.displayText() = DB::Exception: Memory limit (for user) exceeded: would use 332.07 GiB (attempt to allocate chunk of 4355342 bytes), maximum: 256.00 GiB, Stack trace (when copying this message, always include the lines below):
0. DB::Exception::Exception<>() @ 0x86f7b88 in /usr/bin/clickhouse
...
4. void DB::PODArrayBase<>::resize<>(unsigned long) @ 0xe9e878d in /usr/bin/clickhouse
5. DB::CompressedWriteBuffer::nextImpl() @ 0xe9f0296 in /usr/bin/clickhouse
6. DB::CompressedWriteBuffer::~CompressedWriteBuffer() @ 0xe9f0415 in /usr/bin/clickhouse
7. DB::DistributedBlockOutputStream::writeToShard() @ 0xf6bed4a in /usr/bin/clickhouse
2021-01-22 18:56:50 +00:00
|
|
|
/// FIXME move final flush into the caller
|
2021-04-14 20:37:08 +00:00
|
|
|
MemoryTracker::LockExceptionInThread lock(VariableContext::Global);
|
2020-09-30 13:24:36 +00:00
|
|
|
finalizeImpl();
|
|
|
|
}
|
2020-01-27 18:44:30 +00:00
|
|
|
|
2020-09-30 13:24:36 +00:00
|
|
|
void WriteBufferFromS3::finalizeImpl()
|
|
|
|
{
|
2020-12-09 14:09:04 +00:00
|
|
|
if (finalized)
|
|
|
|
return;
|
2019-06-17 00:06:14 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
next();
|
2020-09-30 13:24:36 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
if (multipart_upload_id.empty())
|
|
|
|
{
|
|
|
|
makeSinglepartUpload();
|
2020-09-30 13:24:36 +00:00
|
|
|
}
|
2020-12-09 14:09:04 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
/// Write rest of the data as last part.
|
|
|
|
writePart();
|
|
|
|
completeMultipartUpload();
|
|
|
|
}
|
|
|
|
|
|
|
|
finalized = true;
|
2020-09-30 13:24:36 +00:00
|
|
|
}
|
2019-06-10 02:35:33 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
void WriteBufferFromS3::createMultipartUpload()
|
2019-06-17 00:42:47 +00:00
|
|
|
{
|
2019-12-03 16:23:24 +00:00
|
|
|
Aws::S3::Model::CreateMultipartUploadRequest req;
|
|
|
|
req.SetBucket(bucket);
|
|
|
|
req.SetKey(key);
|
2020-12-08 18:31:57 +00:00
|
|
|
if (object_metadata.has_value())
|
|
|
|
req.SetMetadata(object_metadata.value());
|
2019-06-17 07:16:43 +00:00
|
|
|
|
2019-12-03 16:23:24 +00:00
|
|
|
auto outcome = client_ptr->CreateMultipartUpload(req);
|
2019-06-17 07:16:43 +00:00
|
|
|
|
2019-12-06 14:48:56 +00:00
|
|
|
if (outcome.IsSuccess())
|
|
|
|
{
|
2020-12-09 14:09:04 +00:00
|
|
|
multipart_upload_id = outcome.GetResult().GetUploadId();
|
2021-03-17 14:20:55 +00:00
|
|
|
LOG_DEBUG(log, "Multipart upload has created. Bucket: {}, Key: {}, Upload id: {}", bucket, key, multipart_upload_id);
|
2019-06-17 07:16:43 +00:00
|
|
|
}
|
2019-12-06 14:48:56 +00:00
|
|
|
else
|
|
|
|
throw Exception(outcome.GetError().GetMessage(), ErrorCodes::S3_ERROR);
|
2019-06-17 00:42:47 +00:00
|
|
|
}
|
2019-06-17 18:06:28 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
void WriteBufferFromS3::writePart()
|
2019-06-17 00:42:47 +00:00
|
|
|
{
|
2021-03-17 14:20:55 +00:00
|
|
|
auto size = temporary_buffer->tellp();
|
|
|
|
|
|
|
|
LOG_DEBUG(log, "Writing part. Bucket: {}, Key: {}, Upload_id: {}, Size: {}", bucket, key, multipart_upload_id, size);
|
|
|
|
|
|
|
|
if (size < 0)
|
|
|
|
throw Exception("Failed to write part. Buffer in invalid state.", ErrorCodes::S3_ERROR);
|
|
|
|
|
|
|
|
if (size == 0)
|
|
|
|
{
|
|
|
|
LOG_DEBUG(log, "Skipping writing part. Buffer is empty.");
|
2020-01-27 18:44:30 +00:00
|
|
|
return;
|
2021-03-17 14:20:55 +00:00
|
|
|
}
|
2020-01-27 18:44:30 +00:00
|
|
|
|
2019-09-22 10:42:47 +00:00
|
|
|
if (part_tags.size() == S3_WARN_MAX_PARTS)
|
2019-06-22 05:58:05 +00:00
|
|
|
{
|
2019-09-22 10:42:47 +00:00
|
|
|
// Don't throw exception here by ourselves but leave the decision to take by S3 server.
|
2020-05-23 22:24:01 +00:00
|
|
|
LOG_WARNING(log, "Maximum part number in S3 protocol has reached (too many parts). Server may not accept this whole upload.");
|
2019-06-22 05:58:05 +00:00
|
|
|
}
|
|
|
|
|
2019-12-03 16:23:24 +00:00
|
|
|
Aws::S3::Model::UploadPartRequest req;
|
2019-06-10 02:35:33 +00:00
|
|
|
|
2019-12-03 16:23:24 +00:00
|
|
|
req.SetBucket(bucket);
|
|
|
|
req.SetKey(key);
|
|
|
|
req.SetPartNumber(part_tags.size() + 1);
|
2020-12-09 14:09:04 +00:00
|
|
|
req.SetUploadId(multipart_upload_id);
|
2021-03-17 14:20:55 +00:00
|
|
|
req.SetContentLength(size);
|
2020-12-09 14:09:04 +00:00
|
|
|
req.SetBody(temporary_buffer);
|
2019-06-10 02:35:33 +00:00
|
|
|
|
2019-12-03 16:23:24 +00:00
|
|
|
auto outcome = client_ptr->UploadPart(req);
|
2019-06-10 02:35:33 +00:00
|
|
|
|
2019-12-06 14:48:56 +00:00
|
|
|
if (outcome.IsSuccess())
|
|
|
|
{
|
2019-12-03 16:23:24 +00:00
|
|
|
auto etag = outcome.GetResult().GetETag();
|
|
|
|
part_tags.push_back(etag);
|
2021-03-17 14:20:55 +00:00
|
|
|
LOG_DEBUG(log, "Writing part finished. Bucket: {}, Key: {}, Upload_id: {}, Etag: {}, Parts: {}", bucket, key, multipart_upload_id, etag, part_tags.size());
|
2019-06-17 07:16:43 +00:00
|
|
|
}
|
2019-12-06 14:48:56 +00:00
|
|
|
else
|
|
|
|
throw Exception(outcome.GetError().GetMessage(), ErrorCodes::S3_ERROR);
|
2019-06-01 21:18:20 +00:00
|
|
|
}
|
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
void WriteBufferFromS3::completeMultipartUpload()
|
2019-06-17 00:06:14 +00:00
|
|
|
{
|
2021-03-17 14:20:55 +00:00
|
|
|
LOG_DEBUG(log, "Completing multipart upload. Bucket: {}, Key: {}, Upload_id: {}, Parts: {}", bucket, key, multipart_upload_id, part_tags.size());
|
|
|
|
|
|
|
|
if (part_tags.empty())
|
|
|
|
throw Exception("Failed to complete multipart upload. No parts have uploaded", ErrorCodes::S3_ERROR);
|
2020-11-11 12:15:16 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
Aws::S3::Model::CompleteMultipartUploadRequest req;
|
|
|
|
req.SetBucket(bucket);
|
|
|
|
req.SetKey(key);
|
|
|
|
req.SetUploadId(multipart_upload_id);
|
2020-11-11 12:15:16 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
Aws::S3::Model::CompletedMultipartUpload multipart_upload;
|
|
|
|
for (size_t i = 0; i < part_tags.size(); ++i)
|
|
|
|
{
|
|
|
|
Aws::S3::Model::CompletedPart part;
|
|
|
|
multipart_upload.AddParts(part.WithETag(part_tags[i]).WithPartNumber(i + 1));
|
|
|
|
}
|
2020-11-11 12:15:16 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
req.SetMultipartUpload(multipart_upload);
|
2020-11-11 12:15:16 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
auto outcome = client_ptr->CompleteMultipartUpload(req);
|
2020-11-11 12:15:16 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
if (outcome.IsSuccess())
|
2021-03-17 14:20:55 +00:00
|
|
|
LOG_DEBUG(log, "Multipart upload has completed. Bucket: {}, Key: {}, Upload_id: {}, Parts: {}", bucket, key, multipart_upload_id, part_tags.size());
|
2020-12-09 14:09:04 +00:00
|
|
|
else
|
|
|
|
throw Exception(outcome.GetError().GetMessage(), ErrorCodes::S3_ERROR);
|
|
|
|
}
|
2020-01-27 18:44:30 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
void WriteBufferFromS3::makeSinglepartUpload()
|
|
|
|
{
|
2021-03-17 14:20:55 +00:00
|
|
|
auto size = temporary_buffer->tellp();
|
|
|
|
|
|
|
|
LOG_DEBUG(log, "Making single part upload. Bucket: {}, Key: {}, Size: {}", bucket, key, size);
|
2019-12-03 16:23:24 +00:00
|
|
|
|
2021-03-17 14:20:55 +00:00
|
|
|
if (size < 0)
|
|
|
|
throw Exception("Failed to make single part upload. Buffer in invalid state", ErrorCodes::S3_ERROR);
|
|
|
|
|
|
|
|
if (size == 0)
|
|
|
|
{
|
|
|
|
LOG_DEBUG(log, "Skipping single part upload. Buffer is empty.");
|
|
|
|
return;
|
|
|
|
}
|
2020-01-27 18:44:30 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
Aws::S3::Model::PutObjectRequest req;
|
|
|
|
req.SetBucket(bucket);
|
|
|
|
req.SetKey(key);
|
2021-03-17 14:20:55 +00:00
|
|
|
req.SetContentLength(size);
|
2020-12-09 14:09:04 +00:00
|
|
|
req.SetBody(temporary_buffer);
|
2020-12-11 15:28:41 +00:00
|
|
|
if (object_metadata.has_value())
|
|
|
|
req.SetMetadata(object_metadata.value());
|
2020-11-11 12:15:16 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
auto outcome = client_ptr->PutObject(req);
|
2020-07-09 14:09:17 +00:00
|
|
|
|
2020-12-09 14:09:04 +00:00
|
|
|
if (outcome.IsSuccess())
|
2021-03-22 05:49:41 +00:00
|
|
|
LOG_DEBUG(log, "Single part upload has completed. Bucket: {}, Key: {}, Object size: {}", bucket, key, req.GetContentLength());
|
2020-07-09 14:09:17 +00:00
|
|
|
else
|
2020-12-09 14:09:04 +00:00
|
|
|
throw Exception(outcome.GetError().GetMessage(), ErrorCodes::S3_ERROR);
|
2019-06-17 00:06:14 +00:00
|
|
|
}
|
|
|
|
|
2019-06-01 21:18:20 +00:00
|
|
|
}
|
2019-12-06 14:37:21 +00:00
|
|
|
|
2019-12-09 12:36:06 +00:00
|
|
|
#endif
|