blob: 13b5a39bfc02c841ba92129d09cba4aa8f623fd9 [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.
#pragma once
#include <butil/macros.h>
#include <gen_cpp/cloud.pb.h>
#include <glog/logging.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include "common/status.h"
#include "io/fs/file_system.h"
#include "io/fs/file_writer.h"
#include "io/fs/path.h"
#include "util/slice.h"
namespace doris::io {
struct PackedSliceLocation {
std::string packed_file_path;
int64_t offset;
int64_t size;
int64_t create_time = 0;
int64_t tablet_id = 0;
std::string rowset_id;
std::string resource_id;
int64_t txn_id = 0;
int64_t packed_file_size = -1; // Total size of the packed file, -1 means not set
};
struct PackedAppendContext {
std::string resource_id;
int64_t tablet_id = 0;
std::string rowset_id;
int64_t txn_id = 0;
uint64_t expiration_time = 0; // TTL expiration time in seconds since epoch, 0 means no TTL
bool write_file_cache = true; // Whether to write data to file cache
};
// Global object that manages packing small files into larger files for S3 optimization
class PackedFileManager {
struct PackedFileContext;
public:
static PackedFileManager* instance();
// Initialize manager state; file system will be resolved lazily
Status init();
// Write a small file to the current packed file
Status append_small_file(const std::string& path, const Slice& data,
const PackedAppendContext& info);
// Block until the small file's packed file is uploaded to S3
Status wait_upload_done(const std::string& path);
// Get packed file index information for a small file
Status get_packed_slice_location(const std::string& path, PackedSliceLocation* location);
// Start the background management thread
void start_background_manager();
// Stop the background management thread
void stop_background_manager();
// Mark current packed file for upload and create new one
Status mark_current_packed_file_for_upload(const std::string& resource_id);
// Internal helper; expects caller holds _current_packed_file_mutex
Status mark_current_packed_file_for_upload_locked(const std::string& resource_id);
void record_packed_file_metrics(const PackedFileContext& packed_file);
private:
PackedFileManager() = default;
~PackedFileManager();
DISALLOW_COPY_AND_ASSIGN(PackedFileManager);
// Background thread function for managing packed file lifecycle
void background_manager();
// Upload packed file to S3 and update meta service
Status finalize_packed_file_upload(const std::string& packed_file_path, FileWriter* writer);
// Update meta service with packed file information
// table_id is used for rate limiting; -1 means no specific table (cross-table operation)
Status update_meta_service(const std::string& packed_file_path,
const cloud::PackedFileInfoPB& packed_file_info,
int64_t table_id = -1);
// Process uploading files
void process_uploading_packed_files();
// Clean up expired data
void cleanup_expired_data();
// Internal structure to track packed file state
enum class PackedFileState {
INIT, // Initial state, no files written yet
ACTIVE, // Has files but doesn't meet upload conditions
READY_TO_UPLOAD, // Ready for upload, metadata still being prepared
UPLOADING, // Upload triggered, waiting for writer close to finish
UPLOADED, // Upload completed
FAILED // Upload failed
};
struct PackedFileContext {
std::string packed_file_path;
std::unique_ptr<FileWriter> writer;
std::unordered_map<std::string, PackedSliceLocation> slice_locations;
int64_t current_offset = 0;
int64_t total_size = 0;
int64_t create_time;
int64_t upload_time = 0;
std::chrono::steady_clock::time_point create_timestamp;
std::optional<std::chrono::steady_clock::time_point> first_append_timestamp;
std::optional<std::chrono::steady_clock::time_point> ready_to_upload_timestamp;
std::optional<std::chrono::steady_clock::time_point> uploading_timestamp;
std::atomic<PackedFileState> state {PackedFileState::INIT};
std::condition_variable upload_cv;
std::mutex upload_mutex;
std::string last_error;
std::string resource_id;
FileSystemSPtr file_system;
};
// Create a new packed file state with file writer
Status create_new_packed_file_context(const std::string& resource_id,
std::unique_ptr<PackedFileContext>& packed_file_ctx);
Status ensure_file_system(const std::string& resource_id, FileSystemSPtr* file_system);
// Helper function to wait for packed file upload completion
Status wait_for_packed_file_upload(PackedFileContext* packed_file_ptr);
// Thread management
std::atomic<bool> _stop_background_thread {false};
std::unique_ptr<std::thread> _background_thread;
// File system
FileSystemSPtr _default_file_system;
std::unordered_map<std::string, FileSystemSPtr> _file_systems;
std::mutex _file_system_mutex;
// Current active packed file
std::unordered_map<std::string, std::unique_ptr<PackedFileContext>> _current_packed_files;
std::timed_mutex _current_packed_file_mutex;
// Merge files ready for upload or being processed
std::unordered_map<std::string, std::shared_ptr<PackedFileContext>> _uploading_packed_files;
// Uploaded packed files (kept for some time for wait_write_done)
std::unordered_map<std::string, std::shared_ptr<PackedFileContext>> _uploaded_packed_files;
std::mutex _packed_files_mutex;
// Global index mapping small file path to packed file index
std::unordered_map<std::string, PackedSliceLocation> _global_slice_locations;
std::mutex _global_index_mutex;
#ifdef BE_TEST
public:
void reset_packed_file_bvars_for_test() const;
int64_t packed_file_total_count_for_test() const;
int64_t packed_file_total_small_file_num_for_test() const;
int64_t packed_file_total_size_bytes_for_test() const;
double packed_file_avg_small_file_num_for_test() const;
double packed_file_avg_file_size_for_test() const;
void record_packed_file_metrics_for_test(const PackedFileContext* packed_file);
// Test-only helpers to introspect/clear internal state
void clear_state_for_test();
auto& current_packed_files_for_test() { return _current_packed_files; }
auto& uploading_packed_files_for_test() { return _uploading_packed_files; }
auto& uploaded_packed_files_for_test() { return _uploaded_packed_files; }
auto& global_slice_locations_for_test() { return _global_slice_locations; }
auto& file_systems_for_test() { return _file_systems; }
FileSystemSPtr& default_file_system_for_test() { return _default_file_system; }
Status create_new_packed_file_state_for_test(const std::string& resource_id,
std::unique_ptr<PackedFileContext>& ctx) {
return create_new_packed_file_context(resource_id, ctx);
}
#endif
};
} // namespace doris::io