blob: 711360dacff5269e0db1e34630a6e9ed8dabad44 [file] [log] [blame]
// 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 <cstddef>
#include <cstdint>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <gtest/gtest_prod.h>
#include "kudu/consensus/log.h"
#include "kudu/consensus/metadata.pb.h"
#include "kudu/consensus/raft_consensus.h"
#include "kudu/fs/fs_manager.h"
#include "kudu/gutil/macros.h"
#include "kudu/gutil/ref_counted.h"
#include "kudu/tablet/metadata.pb.h"
#include "kudu/tablet/op_order_verifier.h"
#include "kudu/tablet/ops/op.h"
#include "kudu/tablet/ops/op_tracker.h"
#include "kudu/tablet/ops/write_op.h"
#include "kudu/tablet/tablet.h"
#include "kudu/tablet/tablet_metadata.h"
#include "kudu/util/locks.h"
#include "kudu/util/metrics.h"
#include "kudu/util/status.h"
#include "kudu/util/status_callback.h"
namespace kudu {
class AlterTableTest;
class DnsResolver;
class MaintenanceManager;
class MaintenanceOp;
class MonoDelta;
class ThreadPool;
class ThreadPoolToken;
class TxnOpDispatcherITest;
class TxnOpDispatcherITest_DispatcherLifecycleMultipleReplicas_Test;
class TxnOpDispatcherITest_DuplicateTxnParticipantRegistration_Test;
class TxnOpDispatcherITest_ErrorInProcessingWriteOp_Test;
class TxnOpDispatcherITest_LifecycleBasic_Test;
class TxnOpDispatcherITest_NoPendingWriteOps_Test;
class TxnOpDispatcherITest_PreliminaryTasksTimeout_Test;
namespace consensus {
class ConsensusMetadataManager;
class OpStatusPB;
class TimeManager;
}
namespace clock {
class Clock;
}
namespace log {
class LogAnchorRegistry;
} // namespace log
namespace rpc {
class Messenger;
class ResultTracker;
} // namespace rpc
namespace tablet {
class AlterSchemaOpState;
class OpDriver;
class ParticipantOpState;
class TabletStatusPB;
class TxnCoordinator;
class TxnCoordinatorFactory;
// A replica in a tablet consensus configuration, which coordinates writes to tablets.
// Each time Write() is called this class appends a new entry to a replicated
// state machine through a consensus algorithm, which makes sure that other
// peers see the same updates in the same order. In addition to this, this
// class also splits the work and coordinates multi-threaded execution.
class TabletReplica : public RefCountedThreadSafe<TabletReplica>,
public consensus::ConsensusRoundHandler {
public:
TabletReplica(scoped_refptr<TabletMetadata> meta,
scoped_refptr<consensus::ConsensusMetadataManager> cmeta_manager,
consensus::RaftPeerPB local_peer_pb,
ThreadPool* apply_pool,
ThreadPool* reload_txn_status_tablet_pool,
TxnCoordinatorFactory* txn_coordinator_factory,
consensus::MarkDirtyCallback cb);
// Initializes RaftConsensus.
// This must be called before publishing the instance to other threads.
// If this fails, the TabletReplica instance remains in a NOT_INITIALIZED
// state.
Status Init(consensus::ServerContext server_ctx);
// Starts the TabletReplica, making it available for Write()s. If this
// TabletReplica is part of a consensus configuration this will connect it to other replicas
// in the consensus configuration.
Status Start(const consensus::ConsensusBootstrapInfo& bootstrap_info,
std::shared_ptr<tablet::Tablet> tablet,
clock::Clock* clock,
std::shared_ptr<rpc::Messenger> messenger,
scoped_refptr<rpc::ResultTracker> result_tracker,
scoped_refptr<log::Log> log,
ThreadPool* prepare_pool,
DnsResolver* resolver);
// Synchronously transition this replica to STOPPED state from any other
// state. This also stops RaftConsensus. If a Stop() operation is already in
// progress, blocks until that operation is complete.
// See tablet/metadata.proto for a description of legal state transitions.
void Stop();
// Synchronously transition this replica to SHUTDOWN state from any other state.
// See tablet/metadata.proto for a description of legal state transitions.
//
// If 'error_' has been set to a non-OK status, the final state will be
// FAILED instead of SHUTDOWN.
void Shutdown();
// Check that the tablet is in a RUNNING state.
Status CheckRunning() const;
// Whether the tablet is already shutting down or shutdown.
bool IsShuttingDown() const;
// Wait until the tablet is in a RUNNING state or if there's a timeout.
// TODO(jdcryans): have a way to wait for any state?
Status WaitUntilConsensusRunning(const MonoDelta& timeout);
// Submit a write operation 'op_state' attributed to a multi-row transaction.
// This is similar to SubmitWrite(), but it's more complicated because it is
// stateful. The necessary preliminary steps to complete before submitting the
// operation via SubmitWrite() are: (1) register the tablet as a participant
// in the corresponding transaction (2) issue BEGIN_TXN operation to the
// replica. The 'scheduler' functor is used to schedule the activities
// mentioned above.
Status SubmitTxnWrite(
std::unique_ptr<WriteOpState> op_state,
const std::function<Status(int64_t txn_id, StatusCallback cb)>& scheduler);
// Unregister TxnWriteOpDispacher for the specified transaction identifier
// 'txn_id'. If no pending write requests are accumulated by the dispatcher,
// the dispatcher is unregistered immediately and this method returns
// Status::OK. If any write request is pending, the dispatcher is marked to be
// unregistered and this method returns Status::ServiceUnavailable(),
// prompting the caller to try again later, unless 'abort_pending_ops' is set
// to 'true'. If 'abort_pending_ops' is set to true, all pending requests are
// responsed with Status::Aborted() status and the entry is removed.
Status UnregisterTxnOpDispatcher(int64_t txn_id, bool abort_pending_ops);
// Submits a write to a tablet and executes it asynchronously.
// The caller is expected to build and pass a WriteOpState that points to the
// RPC's WriteRequest, and WriteResponse.
Status SubmitWrite(std::unique_ptr<WriteOpState> op_state);
// Submits an op to update transaction participant state, executing it
// asynchonously.
Status SubmitTxnParticipantOp(std::unique_ptr<ParticipantOpState> op_state);
// Called by the tablet service to start an alter schema op.
//
// The op contains all the information required to execute the
// AlterSchema operation and send the response back.
//
// If the returned Status is OK, the response to the client will be sent
// asynchronously. Otherwise the tablet service will have to send the response directly.
//
// The AlterSchema operation is taking the tablet component lock in exclusive mode
// meaning that no other operation on the tablet can be executed while the
// AlterSchema is in progress.
Status SubmitAlterSchema(std::unique_ptr<AlterSchemaOpState> op_state);
void GetTabletStatusPB(TabletStatusPB* status_pb_out) const;
// Used by consensus to create and start a new ReplicaOp.
virtual Status StartFollowerOp(
const scoped_refptr<consensus::ConsensusRound>& round) override;
// Used by consensus to notify the tablet replica that a consensus-only round
// has finished, advancing MVCC safe time as appropriate.
virtual void FinishConsensusOnlyRound(consensus::ConsensusRound* round) override;
consensus::RaftConsensus* consensus() {
std::lock_guard<simple_spinlock> lock(lock_);
return consensus_.get();
}
std::shared_ptr<consensus::RaftConsensus> shared_consensus() const {
std::lock_guard<simple_spinlock> lock(lock_);
return consensus_;
}
Tablet* tablet() const {
std::lock_guard<simple_spinlock> lock(lock_);
return tablet_.get();
}
consensus::TimeManager* time_manager() const {
return consensus_->time_manager();
}
std::shared_ptr<Tablet> shared_tablet() const {
std::lock_guard<simple_spinlock> lock(lock_);
return tablet_;
}
const TabletStatePB state() const {
std::lock_guard<simple_spinlock> lock(lock_);
return state_;
}
std::string StateName() const;
const TabletDataState data_state() const {
std::lock_guard<simple_spinlock> lock(lock_);
return meta_->tablet_data_state();
}
// Returns the current Raft configuration.
const consensus::RaftConfigPB RaftConfig() const;
// Sets the tablet to a BOOTSTRAPPING state, indicating it is starting up.
void SetBootstrapping();
// Set a user-readable status message about the tablet. This may appear on
// the Web UI, for example.
void SetStatusMessage(const std::string& status);
// Retrieve the last human-readable status of this tablet replica.
std::string last_status() const;
// Sets the error to the provided one.
void SetError(const Status& error);
// Returns the error that occurred, when state is FAILED.
Status error() const {
std::lock_guard<simple_spinlock> lock(lock_);
return error_;
}
// Returns a human-readable string indicating the state of the tablet.
// Typically this looks like "NOT_STARTED", "TABLET_DATA_COPYING",
// etc. For use in places like the Web UI.
std::string HumanReadableState() const;
// Adds list of ops in-flight at the time of the call to 'out'.
void GetInFlightOps(Op::TraceType trace_type,
std::vector<consensus::OpStatusPB>* out) const;
// Returns the log indexes to be retained for durability and to catch up peers.
// Used for selection of log segments to delete during Log GC.
log::RetentionIndexes GetRetentionIndexes() const;
// See Log::GetReplaySizeMap(...).
//
// Returns a non-ok status if the tablet isn't running.
Status GetReplaySizeMap(std::map<int64_t, int64_t>* replay_size_map) const;
// Returns the amount of bytes that would be GC'd if RunLogGC() was called.
//
// Returns a non-ok status if the tablet isn't running.
Status GetGCableDataSize(int64_t* retention_size) const;
// Return a pointer to the Log.
// TabletReplica keeps a reference to Log after Init().
log::Log* log() const {
return log_.get();
}
clock::Clock* clock() const { return clock_; }
const scoped_refptr<log::LogAnchorRegistry>& log_anchor_registry() const {
return log_anchor_registry_;
}
const std::string& tablet_id() const { return meta_->tablet_id(); }
// Convenience method to return the permanent_uuid of this peer.
std::string permanent_uuid() const { return tablet_->metadata()->fs_manager()->uuid(); }
Status NewLeaderOpDriver(std::unique_ptr<Op> op,
scoped_refptr<OpDriver>* driver);
Status NewReplicaOpDriver(std::unique_ptr<Op> op,
scoped_refptr<OpDriver>* driver);
// Tells the tablet's log to garbage collect.
Status RunLogGC();
// Register the maintenance ops associated with this peer's tablet, also invokes
// Tablet::RegisterMaintenanceOps().
void RegisterMaintenanceOps(MaintenanceManager* maint_mgr);
// Unregister the maintenance ops associated with this replica's tablet.
void UnregisterMaintenanceOps();
// Cancels the maintenance ops associated with this replica's tablet.
// Only to be used in tests.
void CancelMaintenanceOpsForTests();
// Stops further I/O on the replica.
void MakeUnavailable(const Status& error);
// Return pointer to the op tracker for this peer.
const OpTracker* op_tracker() const { return &op_tracker_; }
const scoped_refptr<TabletMetadata>& tablet_metadata() const {
return meta_;
}
// Marks the tablet as dirty so that it's included in the next heartbeat.
void MarkTabletDirty(const std::string& reason) {
mark_dirty_clbk_(reason);
}
// Return the total on-disk size of this tablet replica, in bytes.
size_t OnDiskSize() const;
// Counts the number of live rows in this tablet replica.
//
// Returns a bad Status on failure.
Status CountLiveRows(uint64_t* live_row_count) const;
// Like CountLiveRows but returns 0 on failure.
uint64_t CountLiveRowsNoFail() const;
// Update the tablet stats.
// When the replica's stats change and it's the LEADER, it is added to
// the 'dirty_tablets'.
void UpdateTabletStats(std::vector<std::string>* dirty_tablets);
// Return the tablet stats.
ReportedTabletStatsPB GetTabletStats() const;
TxnCoordinator* txn_coordinator() const {
return txn_coordinator_.get();
}
// Whether or not to run a new staleness transactions aborting task.
// If the tablet is part of a transaction status table and is in
// RUNNING state, register the task by increasing the transaction status
// manager task counter. Once registered, the tablet will wait the task to
// be executed before shutting down. More concretely, if this returns
// 'true', callers must call DecreaseTxnCoordinatorTaskCounter().
//
// Return true if the caller should run the staleness task. Otherwise
// return false.
bool ShouldRunTxnCoordinatorStalenessTask();
// Whether or not to run a new transaction status table reloading
// metadata task. If the tablet is part of a transaction status table
// and is not shutting down, register the task by increasing the
// transaction status manager task counter if the tablet is not shutting
// down. Similar to 'ShouldRunTxnCoordinatorStalenessTask' above. if
// this returns 'true', callers must call DecreaseTxnCoordinatorTaskCounter().
//
// Return true if the caller should run the metadata reloading task
// Otherwise return false.
bool ShouldRunTxnCoordinatorStateChangedTask();
// Decrease the task counter of the transaction status manager.
void DecreaseTxnCoordinatorTaskCounter();
// Submit ParticipantOpPB::BEGIN_TXN operation for the specified transaction.
void BeginTxnParticipantOp(int64_t txn_id, StatusCallback cb);
private:
friend class kudu::AlterTableTest;
friend class RefCountedThreadSafe<TabletReplica>;
friend class TabletReplicaTest;
friend class TabletReplicaTestBase;
friend class kudu::TxnOpDispatcherITest;
FRIEND_TEST(TabletReplicaTest, TestActiveOpPreventsLogGC);
FRIEND_TEST(TabletReplicaTest, TestDMSAnchorPreventsLogGC);
FRIEND_TEST(TabletReplicaTest, TestMRSAnchorPreventsLogGC);
FRIEND_TEST(kudu::TxnOpDispatcherITest, LifecycleBasic);
// A class to properly dispatch transactional write operations arriving
// with TabletServerService::Write() RPC for the specified tablet replica.
// Before submitting the operations via TabletReplica::SubmitWrite(), it's
// necessary to register the tablet as a participant in the transaction and
// issue BEGIN_TXN operation for the target tablet. This class implements
// the logic to schedule those preliminary tasks while accumulating incoming
// write operations for the interval between the time when the very first
// write operation for a transaction arrives and the time when the preliminary
// tasks finish.
class TxnOpDispatcher: public std::enable_shared_from_this<TxnOpDispatcher> {
public:
// The 'max_queue_size' parameter sets the limit of how many operations
// this TxnOpDispatcher instance is allowed to buffer before starting to
// reject new ones with Status::ServiceUnavailable error.
TxnOpDispatcher(TabletReplica* replica,
size_t max_queue_size)
: replica_(replica),
max_queue_size_(max_queue_size),
preliminary_tasks_completed_(false),
unregistered_(false),
inflight_status_(Status::OK()) {
}
// Dispatch specified write operation: either put it into the queue,
// or submit it immediately via TabletReplica::SubmitWrite(), or reject the
// operation. In the two former cases, returns Status::OK(); in the latter
// case returns non-OK status correspondingly. The 'scheduler' function is
// invoked to schedule preliminary tasks, if necessary.
Status Dispatch(std::unique_ptr<WriteOpState> op,
const std::function<Status(int64_t txn_id,
StatusCallback cb)>& scheduler);
// Submit all pending operations. Returns OK if all operations have been
// submitted successfully, or 'inflight_status_' if any of those failed.
Status Submit();
// Invoke callbacks for every buffered operation with the 'status';
// the 'status' must be a non-OK one.
void Cancel(const Status& status);
// Mark the dispatcher as not accepting any write operations: this is to
// eventually unregister the dispatcher for the corresponding transaction
// (i.e. remove the element from the map of available dispatchers). In the
// unlikely event of the presence of pending write operations, this method
// returns Status::ServiceUnavailable().
Status MarkUnregistered();
private:
FRIEND_TEST(kudu::TxnOpDispatcherITest, DispatcherLifecycleMultipleReplicas);
FRIEND_TEST(kudu::TxnOpDispatcherITest, DuplicateTxnParticipantRegistration);
FRIEND_TEST(kudu::TxnOpDispatcherITest, ErrorInProcessingWriteOp);
FRIEND_TEST(kudu::TxnOpDispatcherITest, LifecycleBasic);
FRIEND_TEST(kudu::TxnOpDispatcherITest, NoPendingWriteOps);
FRIEND_TEST(kudu::TxnOpDispatcherITest, PreliminaryTasksTimeout);
// Add the specified operation into the queue.
Status EnqueueUnlocked(std::unique_ptr<WriteOpState> op);
// Respond to the given write operations with the specified status.
static Status RespondWithStatus(
const Status& status,
std::deque<std::unique_ptr<WriteOpState>> ops);
// Pointer to the parent TabletReplica instance which keeps this
// TxnOpDispatcher instance in its 'txn_op_dispatchers_' map.
TabletReplica* const replica_;
// Maximum number of transactional write operation to buffer in the
// 'ops_queue_' before completing all the preliminary tasks which are
// required to start processing transactional write operations for the
// tablet.
const size_t max_queue_size_;
// Protects the members below: preliminary_tasks_completed_, unregistered_,
// inflight_status_, ops_queue_.
mutable simple_spinlock lock_;
// Whether the preliminary tasks are completed and this instance is
// ready to submit incoming txn write operations directly via
// TabletReplica::SubmitWrite().
bool preliminary_tasks_completed_;
// Whether this instance has been marked as unregistered and pending
// destruction.
bool unregistered_;
// This field stores the first non-OK status (if any) returned by
// TabletReplica::SubmitWrite() when submitting pending operations from
// 'ops_queue_' upon completion of preliminary tasks.
Status inflight_status_;
// Queue to buffer txn write operations while the preliminary work of
// registering the tablet as a participant in the transaction, etc. are
// in progress.
std::deque<std::unique_ptr<WriteOpState>> ops_queue_;
};
~TabletReplica();
// Wait until the TabletReplica is fully in STOPPED, SHUTDOWN, or FAILED
// state.
void WaitUntilStopped();
// Wait until all on-going tasks for transaction status manager, if any,
// to finish.
void WaitUntilTxnCoordinatorTasksFinished();
// Handle the state change accordingly if this tablet is a part of the
// transaction status table.
void TxnStatusReplicaStateChanged(const std::string& tablet_id,
const std::string& reason);
std::string LogPrefix() const;
// Transition to another state. Requires that the caller hold 'lock_' if the
// object has already published to other threads. See tablet/metadata.proto
// for state descriptions and legal state transitions.
void set_state(TabletStatePB new_state);
const scoped_refptr<TabletMetadata> meta_;
const scoped_refptr<consensus::ConsensusMetadataManager> cmeta_manager_;
const consensus::RaftPeerPB local_peer_pb_;
scoped_refptr<log::LogAnchorRegistry> log_anchor_registry_; // Assigned in tablet_replica-test
// Pool that executes apply tasks for ops. This is a multi-threaded pool,
// constructor-injected by either the Master (for system tables) or the
// Tablet server.
ThreadPool* const apply_pool_;
// Pool that executes txn status tablet in memory state reloading. This is
// a multi-threaded pool, constructor-injected by the tablet server.
ThreadPool* const reload_txn_status_tablet_pool_;
// If this tablet is a part of the transaction status table, this is the
// entity responsible for accepting and managing requests to coordinate
// transactions.
const std::unique_ptr<TxnCoordinator> txn_coordinator_;
// Track the number of on-going tasks of the transaction status manager.
int txn_coordinator_task_counter_;
// Maps txn_id --> txn write op dispatcher. This map stores ref-counted
// pointers instead of instances/references because an element can be removed
// from the map (unregistered) while concurrently still receiving requests
// (the latter will be rejected with Status::IllegalState). An alternative to
// this scheme with ref-counted pointers would be
// (a) process _all_ transactional requests under the same giant lock
// (even for different transactions)
// (b) change the lifecycle of an entry in the txn_op_dispatcher_ map,
// introducing an alternative approach to clean-up obsolete elements
// from the map
std::unordered_map<int64_t, std::shared_ptr<TxnOpDispatcher>> txn_op_dispatchers_;
simple_spinlock txn_op_dispatchers_lock_; // protects 'txn_op_dispatchers_'
// Function to mark this TabletReplica's tablet as dirty in the TSTabletManager.
//
// Must be called whenever cluster membership or leadership changes, or when
// the tablet's schema changes.
const consensus::MarkDirtyCallback mark_dirty_clbk_;
TabletStatePB state_;
Status error_;
OpTracker op_tracker_;
OpOrderVerifier op_order_verifier_;
scoped_refptr<log::Log> log_;
std::shared_ptr<Tablet> tablet_;
std::shared_ptr<rpc::Messenger> messenger_;
std::shared_ptr<consensus::RaftConsensus> consensus_;
// Lock protecting state_, last_status_, as well as pointers to collaborating
// classes such as tablet_, consensus_, and maintenance_ops_.
mutable simple_spinlock lock_;
// The human-readable last status of the tablet, displayed on the web page, command line
// tools, etc.
std::string last_status_;
// Lock taken during Init/Shutdown which ensures that only a single thread
// attempts to perform major lifecycle operations (Init/Shutdown) at once.
// This must be acquired before acquiring lock_ if they are acquired together.
// We don't just use lock_ since the lifecycle operations may take a while
// and we'd like other threads to be able to quickly poll the state_ variable
// during them in order to reject RPCs, etc.
mutable simple_spinlock state_change_lock_;
// Token for serial task submission to the server-wide op prepare pool.
std::unique_ptr<ThreadPoolToken> prepare_pool_token_;
clock::Clock* clock_;
// List of maintenance operations for the tablet that need information that only the peer
// can provide.
std::vector<MaintenanceOp*> maintenance_ops_;
// The result tracker for writes.
scoped_refptr<rpc::ResultTracker> result_tracker_;
// Cached stats for the tablet replica.
ReportedTabletStatsPB stats_pb_;
// NOTE: it's important that this is the first member to be destructed. This
// ensures we do not attempt to collect metrics while calling the destructor.
FunctionGaugeDetacher metric_detacher_;
DISALLOW_COPY_AND_ASSIGN(TabletReplica);
};
// A callback to wait for the in-flight ops to complete and to flush
// the Log when they do.
// Tablet is passed as a raw pointer as this callback is set in TabletMetadata and
// were we to keep the tablet as a shared_ptr a circular dependency would occur:
// callback->tablet->metadata->callback. Since the tablet indirectly owns this
// callback we know that is must still be alive when it fires.
class FlushInflightsToLogCallback : public RefCountedThreadSafe<FlushInflightsToLogCallback> {
public:
FlushInflightsToLogCallback(Tablet* tablet,
const scoped_refptr<log::Log>& log)
: tablet_(tablet),
log_(log) {}
Status WaitForInflightsAndFlushLog();
private:
Tablet* tablet_;
scoped_refptr<log::Log> log_;
};
} // namespace tablet
} // namespace kudu