blob: ba6e408da5ac818b2bfba0fe1f320d59b42773d1 [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 <bthread/countdown_event.h>
#include <bthread/mutex.h>
#include <gen_cpp/BackendService.h>
#include <array>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <unordered_set>
#include <vector>
#include "cloud/cloud_storage_engine.h"
#include "cloud/cloud_tablet.h"
#include "common/status.h"
#include "util/threadpool.h"
namespace doris {
struct RecycledRowsets;
enum class DownloadType {
BE,
S3,
};
// Filter for event-driven warmup jobs.
// nullopt = cluster-level (no table filter, warm up all tables)
// has_value = table-level filter (only warm up tables in the set)
using EventDrivenJobFilter = std::optional<std::unordered_set<int64_t>>;
struct JobReplicaInfo {
int64_t job_id;
TReplicaInfo replica;
};
struct JobMeta {
JobMeta() = default;
JobMeta(const TJobMeta& meta);
DownloadType download_type;
std::string be_ip;
int32_t brpc_port;
std::vector<int64_t> tablet_ids;
};
// Represents a single peer candidate for cross compute group peer read
struct PeerCandidate {
std::string host;
int32_t brpc_port {0};
std::string compute_group_id;
int64_t last_access_time_ms {0}; // ms since epoch, used for expiry
int32_t consecutive_rpc_failures {0};
};
// Holds all peer candidates for a single tablet
struct TabletPeerCandidates {
// candidates[0] is the highest priority (warmup-inserted candidates go to front)
std::vector<PeerCandidate> candidates;
// last successful compute group used for this tablet
std::string last_successful_compute_group_id;
// singleflight guard: true while an async fetch from FE is in progress
bool fetching_from_fe {false};
// Cooldown: consecutive all-miss count and cooldown deadline.
// When all candidates miss N times in a row, temporarily skip peer for this tablet.
int32_t consecutive_all_miss {0};
int64_t cooldown_until_ms {0}; // ms since epoch; 0 = not in cooldown
};
// manager for
// table warm up
// cluster warm up
// balance peer addr cache
class CloudWarmUpManager {
public:
explicit CloudWarmUpManager(CloudStorageEngine& engine);
~CloudWarmUpManager();
// Set the job id if the id is zero
Status check_and_set_job_id(int64_t job_id);
// Set the batch id to record download progress
Status check_and_set_batch_id(int64_t job_id, int64_t batch_id, bool* retry = nullptr);
// Add the dowload job
void add_job(const std::vector<TJobMeta>& job_metas);
#ifdef BE_TEST
void consumer_job();
#endif
// Get the job state tuple<cur_job_id, cur_batch_id, pending_job_metas_size, _finish_job_size>
std::tuple<int64_t, int64_t, int64_t, int64_t> get_current_job_state();
// Cancel the job
Status clear_job(int64_t job_id);
Status set_event(int64_t job_id, TWarmUpEventType::type event, bool clear = false,
const std::vector<int64_t>* table_ids = nullptr);
// If `sync_wait_timeout_ms` <= 0, the function will send the warm-up RPC
// and return immediately without waiting for the warm-up to complete.
// If `sync_wait_timeout_ms` > 0, the function will wait for the warm-up
// to finish or until the specified timeout (in milliseconds) is reached.
//
// @param rs_meta Metadata of the rowset to be warmed up.
// @param sync_wait_timeout_ms Timeout in milliseconds to wait for the warm-up
// to complete. Non-positive value means no waiting.
void warm_up_rowset(RowsetMeta& rs_meta, int64_t table_id, int64_t sync_wait_timeout_ms = -1);
void recycle_cache(int64_t tablet_id, const std::vector<RecycledRowsets>& rowsets);
// Balance warm up cache management methods
// compute_group_id defaults to "" for backward compatibility
void record_balanced_tablet(int64_t tablet_id, const std::string& host, int32_t brpc_port,
const std::string& compute_group_id = "");
void remove_balanced_tablet(int64_t tablet_id);
void remove_balanced_tablets(const std::vector<int64_t>& tablet_ids);
// Cross compute group peer read candidate management
void fetch_candidates_from_fe(int64_t tablet_id);
std::vector<PeerCandidate> get_peer_candidates(int64_t tablet_id);
void update_peer_candidate_on_success(int64_t tablet_id, const std::string& compute_group_id);
void update_peer_candidate_on_rpc_failure(int64_t tablet_id, const std::string& host,
int32_t brpc_port);
// Rotate a cache-miss candidate to the end of the list so next read tries a different one.
void rotate_peer_candidate_on_cache_miss(int64_t tablet_id, const std::string& host,
int32_t brpc_port);
// Record that all candidates missed for this tablet in a single race.
// After consecutive_all_miss reaches threshold, sets a cooldown period during which
// get_peer_candidates returns empty to avoid wasting peer RPCs.
void record_peer_all_miss(int64_t tablet_id);
// Check if peer read is in cooldown for this tablet (all candidates repeatedly missed).
bool is_peer_cooldown(int64_t tablet_id) const;
// ---- HTTP debug/admin API ----
// Read-only snapshot of TabletPeerCandidates for a single tablet.
// Returns nullopt if the tablet has no peer candidates.
std::optional<TabletPeerCandidates> get_tablet_peer_info(int64_t tablet_id) const;
// Snapshot of all tablets with peer candidates. If limit > 0, returns at most that many.
std::vector<std::pair<int64_t, TabletPeerCandidates>> get_all_peer_info(
int64_t limit = 0) const;
// Force-set the full TabletPeerCandidates for a tablet (admin override).
void set_tablet_peer_candidates(int64_t tablet_id, TabletPeerCandidates candidates);
private:
struct WarmUpRowsetFailure {
int code;
std::string reason;
};
static Status _build_warm_up_rowset_result(const std::vector<WarmUpRowsetFailure>& failures,
size_t replica_count, int64_t tablet_id,
int64_t table_id, const std::string& rowset_id);
void handle_jobs();
void run_cleanup_loop();
Status _do_warm_up_rowset(RowsetMeta& rs_meta, int64_t table_id,
std::vector<JobReplicaInfo>& replicas, int64_t sync_wait_timeout_ms,
bool skip_existence_check);
std::vector<JobReplicaInfo> get_replica_info(int64_t tablet_id, int64_t table_id,
bool bypass_cache, bool& cache_hit);
void _warm_up_rowset(RowsetMeta& rs_meta, int64_t table_id, int64_t sync_wait_timeout_ms);
void _recycle_cache(int64_t tablet_id, const std::vector<RecycledRowsets>& rowsets);
void submit_download_tasks(io::Path path, int64_t file_size, io::FileSystemSPtr file_system,
int64_t expiration_time,
std::shared_ptr<bthread::CountdownEvent> wait, bool is_index = false,
std::function<void(Status)> done_cb = nullptr,
int64_t tablet_id = -1);
std::mutex _mtx;
std::condition_variable _cond;
int64_t _cur_job_id {0};
int64_t _cur_batch_id {-1};
std::deque<std::shared_ptr<JobMeta>> _pending_job_metas;
std::vector<std::shared_ptr<JobMeta>> _finish_job;
std::thread _download_thread;
bool _closed {false};
// the attribute for compile in ut
[[maybe_unused]] CloudStorageEngine& _engine;
// timestamp, info
using CacheEntry = std::pair<std::chrono::steady_clock::time_point, TReplicaInfo>;
// tablet_id -> entry
using Cache = std::unordered_map<int64_t, CacheEntry>;
// job_id -> cache
std::unordered_map<int64_t, Cache> _tablet_replica_cache;
// job_id -> table filter (nullopt = cluster-level, no filter)
std::unordered_map<int64_t, EventDrivenJobFilter> _event_driven_filters;
std::unique_ptr<ThreadPool> _thread_pool;
std::unique_ptr<ThreadPoolToken> _thread_pool_token;
// Sharded lock for better performance
// bthread::Mutex is used because peer read path runs in bthread context
static constexpr size_t SHARD_COUNT = 10240;
struct Shard {
mutable bthread::Mutex mtx;
std::unordered_map<int64_t, TabletPeerCandidates> tablets;
};
std::array<Shard, SHARD_COUNT> _balanced_tablets_shards;
// Helper methods for shard operations
size_t get_shard_index(int64_t tablet_id) const {
return std::hash<int64_t> {}(tablet_id) % SHARD_COUNT;
}
Shard& get_shard(int64_t tablet_id) {
return _balanced_tablets_shards[get_shard_index(tablet_id)];
}
const Shard& get_shard(int64_t tablet_id) const {
return _balanced_tablets_shards[get_shard_index(tablet_id)];
}
// Cleanup thread and its synchronization primitives
std::thread _cleanup_thread;
std::mutex _cleanup_mtx;
std::condition_variable _cleanup_cond;
};
} // namespace doris