2014-04-02 07:59:43 +00:00
# pragma once
# include <DB/Storages/StorageReplicatedMergeTree.h>
# include <DB/Storages/MergeTree/AbandonableLockInZooKeeper.h>
2015-09-11 02:13:59 +00:00
# include <DB/Storages/MergeTree/ReplicatedMergeTreeQuorumEntry.h>
2015-04-16 06:12:35 +00:00
# include <DB/DataStreams/IBlockOutputStream.h>
2015-09-10 21:32:33 +00:00
# include <DB/IO/Operators.h>
2014-04-02 07:59:43 +00:00
namespace DB
{
class ReplicatedMergeTreeBlockOutputStream : public IBlockOutputStream
{
public :
2015-09-10 20:43:42 +00:00
ReplicatedMergeTreeBlockOutputStream ( StorageReplicatedMergeTree & storage_ , const String & insert_id_ , size_t quorum_ )
: storage ( storage_ ) , insert_id ( insert_id_ ) , quorum ( quorum_ ) ,
2015-09-11 02:13:59 +00:00
log ( & Logger : : get ( storage . data . getLogName ( ) + " (Replicated OutputStream) " ) )
{
/// Значение кворума 1 имеет такой же смысл, как если он отключён.
if ( quorum = = 1 )
quorum = 0 ;
}
2014-04-02 07:59:43 +00:00
2014-09-17 22:57:02 +00:00
void writePrefix ( ) override
{
/// TODO Можно ли здесь не блокировать структуру таблицы?
2014-10-17 01:05:51 +00:00
storage . data . delayInsertIfNeeded ( & storage . restarting_thread - > getWakeupEvent ( ) ) ;
2014-09-17 22:57:02 +00:00
}
2014-04-02 07:59:43 +00:00
void write ( const Block & block ) override
{
2014-12-12 20:50:32 +00:00
auto zookeeper = storage . getZooKeeper ( ) ;
assertSessionIsNotExpired ( zookeeper ) ;
2015-09-10 20:43:42 +00:00
/** Если запись с кворумом, то проверим, что требуемое количество реплик сейчас живо,
2015-09-11 02:13:59 +00:00
* а т а к ж е ч т о д л я в с е х п р е д ы д у щ и х к у с к о в , д л я к о т о р ы х т р е б у е т с я к в о р у м , э т о т к в о р у м д о с т и г н у т .
2015-09-10 20:43:42 +00:00
*/
2015-09-11 02:13:59 +00:00
String quorum_status_path = storage . zookeeper_path + " /quorum/status " ;
2015-09-10 20:43:42 +00:00
if ( quorum )
{
2015-09-10 21:32:33 +00:00
/// Список живых реплик. В с е они регистрируют эфемерную ноду для leader_election.
auto live_replicas = zookeeper - > getChildren ( storage . zookeeper_path + " /leader_election " ) ;
if ( live_replicas . size ( ) < quorum )
throw Exception ( " Number of alive replicas ( "
2015-09-11 02:51:35 +00:00
+ toString ( live_replicas . size ( ) ) + " ) is less than requested quorum ( " + toString ( quorum ) + " ). " ,
2015-09-10 21:32:33 +00:00
ErrorCodes : : TOO_LESS_LIVE_REPLICAS ) ;
2015-09-11 02:13:59 +00:00
/** Достигнут ли кворум для последнего куска, для которого нужен кворум?
* З а п и с ь в с е х к у с к о в с в к л ю ч е н н ы м к в о р у м о м л и н е й н о у п о р я д о ч е н а .
* Э т о з н а ч и т , ч т о в л ю б о й м о м е н т в р е м е н и м о ж е т б ы т ь т о л ь к о о д и н к у с о к ,
* д л я к о т о р о г о н у ж е н , н о е щ ё н е д о с т и г н у т к в о р у м .
* И н ф о р м а ц и я о т а к о м к у с к е б у д е т р а с п о л о ж е н а в н о д е / quorum / status .
* Е с л и к в о р у м д о с т и г н у т , т о н о д а у д а л я е т с я .
2015-09-10 21:32:33 +00:00
*/
2015-09-11 02:13:59 +00:00
String quorum_status ;
bool quorum_unsatisfied = zookeeper - > tryGet ( quorum_status_path , quorum_status ) ;
if ( quorum_unsatisfied )
throw Exception ( " Quorum for previous write has not been satisfied yet. Status: " + quorum_status , ErrorCodes : : UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE ) ;
/// О б е проверки неявно делаются и позже (иначе был бы race condition).
2015-09-10 20:43:42 +00:00
}
2014-04-02 07:59:43 +00:00
auto part_blocks = storage . writer . splitBlockIntoParts ( block ) ;
2014-07-28 14:31:07 +00:00
2014-04-02 07:59:43 +00:00
for ( auto & current_block : part_blocks )
{
2014-12-12 20:50:32 +00:00
assertSessionIsNotExpired ( zookeeper ) ;
2014-09-03 02:32:23 +00:00
2014-04-02 07:59:43 +00:00
+ + block_index ;
String block_id = insert_id . empty ( ) ? " " : insert_id + " __ " + toString ( block_index ) ;
2014-11-06 06:32:23 +00:00
String month_name = toString ( DateLUT : : instance ( ) . toNumYYYYMMDD ( DayNum_t ( current_block . min_date ) ) / 100 ) ;
2014-05-07 13:58:20 +00:00
2014-08-07 09:23:55 +00:00
AbandonableLockInZooKeeper block_number_lock = storage . allocateBlockNumber ( month_name ) ;
2014-04-02 07:59:43 +00:00
2015-08-17 21:09:36 +00:00
Int64 part_number = block_number_lock . getNumber ( ) ;
2014-04-02 07:59:43 +00:00
MergeTreeData : : MutableDataPartPtr part = storage . writer . writeTempPart ( current_block , part_number ) ;
2014-08-08 10:41:53 +00:00
String part_name = ActiveDataPartSet : : getPartName ( part - > left_date , part - > right_date , part - > left , part - > right , part - > level ) ;
2014-04-02 07:59:43 +00:00
2015-09-24 05:47:17 +00:00
/// Хэш от данных.
SipHash hash ;
part - > checksums . summaryDataChecksum ( hash ) ;
union
{
char bytes [ 16 ] ;
UInt64 lo , hi ;
} hash_value ;
hash . get128 ( hash_value . bytes ) ;
String checksum ( hash_value . bytes , 16 ) ;
2014-04-25 12:43:10 +00:00
/// Если в запросе не указан ID, возьмем в качестве ID хеш от данных. Т о есть, не вставляем одинаковые данные дважды.
/// NOTE: Если такая дедупликация не нужна, можно вместо этого оставлять block_id пустым.
/// Можно для этого сделать настройку или синтаксис в запросе (например, ID=null).
if ( block_id . empty ( ) )
2015-09-11 02:13:59 +00:00
{
2015-09-24 05:47:17 +00:00
block_id = toString ( hash_value . lo ) + " _ " + toString ( hash_value . hi ) ;
2014-04-25 12:43:10 +00:00
2015-09-11 02:13:59 +00:00
if ( block_id . empty ( ) )
throw Exception ( " Logical error: block_id is empty. " , ErrorCodes : : LOGICAL_ERROR ) ;
}
2014-07-07 10:23:24 +00:00
LOG_DEBUG ( log , " Wrote block " < < part_number < < " with ID " < < block_id < < " , " < < current_block . block . rows ( ) < < " rows " ) ;
2014-06-27 14:05:38 +00:00
2014-04-02 10:10:37 +00:00
StorageReplicatedMergeTree : : LogEntry log_entry ;
log_entry . type = StorageReplicatedMergeTree : : LogEntry : : GET_PART ;
2015-09-27 14:22:23 +00:00
log_entry . create_time = time ( 0 ) ;
2014-04-07 15:45:46 +00:00
log_entry . source_replica = storage . replica_name ;
2014-08-08 10:41:53 +00:00
log_entry . new_part_name = part_name ;
2015-09-11 02:13:59 +00:00
log_entry . quorum = quorum ;
2015-09-27 10:45:49 +00:00
log_entry . block_id = block_id ;
2014-04-02 07:59:43 +00:00
2014-04-02 10:10:37 +00:00
/// Одновременно добавим информацию о куске во все нужные места в ZooKeeper и снимем block_number_lock.
2015-09-11 02:13:59 +00:00
/// Информация о блоке.
2014-04-02 07:59:43 +00:00
zkutil : : Ops ops ;
2015-09-24 05:47:17 +00:00
auto acl = zookeeper - > getDefaultACL ( ) ;
2015-09-11 02:13:59 +00:00
ops . push_back (
new zkutil : : Op : : Create (
2014-04-02 13:45:39 +00:00
storage . zookeeper_path + " /blocks/ " + block_id ,
" " ,
2015-09-24 05:47:17 +00:00
acl ,
2014-04-02 13:45:39 +00:00
zkutil : : CreateMode : : Persistent ) ) ;
2015-09-11 02:13:59 +00:00
ops . push_back (
new zkutil : : Op : : Create (
2015-09-24 05:47:17 +00:00
storage . zookeeper_path + " /blocks/ " + block_id + " /checksum " ,
checksum ,
acl ,
2014-07-10 08:40:59 +00:00
zkutil : : CreateMode : : Persistent ) ) ;
2015-09-11 02:13:59 +00:00
ops . push_back (
new zkutil : : Op : : Create (
2014-04-02 13:45:39 +00:00
storage . zookeeper_path + " /blocks/ " + block_id + " /number " ,
toString ( part_number ) ,
2015-09-24 05:47:17 +00:00
acl ,
2014-04-02 13:45:39 +00:00
zkutil : : CreateMode : : Persistent ) ) ;
2015-09-11 02:13:59 +00:00
/// Информация о куске, в данных реплики.
2014-08-08 10:41:53 +00:00
storage . checkPartAndAddToZooKeeper ( part , ops , part_name ) ;
2015-09-11 02:13:59 +00:00
/// Лог репликации.
2014-04-02 10:10:37 +00:00
ops . push_back ( new zkutil : : Op : : Create (
2014-07-15 14:37:49 +00:00
storage . zookeeper_path + " /log/log- " ,
2014-04-02 10:10:37 +00:00
log_entry . toString ( ) ,
2015-09-24 05:47:17 +00:00
acl ,
2014-04-02 10:10:37 +00:00
zkutil : : CreateMode : : PersistentSequential ) ) ;
2015-09-11 02:13:59 +00:00
/// Удаление информации о том, что номер блока используется для записи.
2014-04-02 10:10:37 +00:00
block_number_lock . getUnlockOps ( ops ) ;
2014-04-02 07:59:43 +00:00
2015-09-11 02:13:59 +00:00
/** Если нужен кворум - создание узла, в котором отслеживается кворум.
* ( Е с л и т а к о й у з е л у ж е с у щ е с т в у е т - з н а ч и т к т о - т о у с п е л о д н о в р е м е н н о с д е л а т ь д р у г у ю к в о р у м н у ю з а п и с ь , н о д л я н е ё к в о р у м е щ ё н е д о с т и г н у т .
* Д е л а т ь в э т о в р е м я с л е д у ю щ у ю к в о р у м н у ю з а п и с ь н е л ь з я . )
*/
if ( quorum )
{
static std : : once_flag once_flag ;
std : : call_once ( once_flag , [ & ]
{
zookeeper - > createIfNotExists ( storage . zookeeper_path + " /quorum " , " " ) ;
2015-09-20 11:02:59 +00:00
zookeeper - > createIfNotExists ( storage . zookeeper_path + " /quorum/last_part " , " " ) ;
zookeeper - > createIfNotExists ( storage . zookeeper_path + " /quorum/failed_parts " , " " ) ;
2015-09-11 02:13:59 +00:00
} ) ;
ReplicatedMergeTreeQuorumEntry quorum_entry ;
quorum_entry . part_name = part_name ;
quorum_entry . required_number_of_replicas = quorum ;
quorum_entry . replicas . insert ( storage . replica_name ) ;
/** В данный момент, этот узел будет содержать информацию о том, что текущая реплика получила кусок.
* К о г д а д р у г и е р е п л и к и б у д у т п о л у ч а т ь э т о т к у с о к ( о б ы ч н ы м с п о с о б о м , о б р а б а т ы в а я л о г р е п л и к а ц и и ) ,
* о н и б у д у т д о б а в л я т ь с е б я в с о д е р ж и м о е э т о г о у з л а .
* К о г д а в н ё м б у д е т и н ф о р м а ц и я о quorum к о л и ч е с т в е р е п л и к , э т о т у з е л у д а л я е т с я ,
* ч т о г о в о р и т о т о м , ч т о к в о р у м д о с т и г н у т .
*/
ops . push_back (
new zkutil : : Op : : Create (
quorum_status_path ,
quorum_entry . toString ( ) ,
2015-09-24 05:47:17 +00:00
acl ,
2015-09-11 02:13:59 +00:00
zkutil : : CreateMode : : Persistent ) ) ;
}
2014-08-08 10:41:53 +00:00
MergeTreeData : : Transaction transaction ; /// Если не получится добавить кусок в ZK, снова уберем е г о из рабочего набора.
storage . data . renameTempPartAndAdd ( part , nullptr , & transaction ) ;
2014-07-25 11:15:11 +00:00
try
2014-06-27 17:21:31 +00:00
{
2014-12-12 20:50:32 +00:00
auto code = zookeeper - > tryMulti ( ops ) ;
2014-07-25 11:15:11 +00:00
if ( code = = ZOK )
2014-06-27 17:21:31 +00:00
{
2014-07-25 11:15:11 +00:00
transaction . commit ( ) ;
storage . merge_selecting_event . set ( ) ;
}
else if ( code = = ZNODEEXISTS )
{
/// Если блок с таким ID уже есть в таблице, откатим е г о вставку.
2015-09-24 05:47:17 +00:00
String expected_checksum ;
2014-12-12 20:50:32 +00:00
if ( ! block_id . empty ( ) & & zookeeper - > tryGet (
2015-09-24 05:47:17 +00:00
storage . zookeeper_path + " /blocks/ " + block_id + " /checksum " , expected_checksum ) )
2014-07-25 11:15:11 +00:00
{
LOG_INFO ( log , " Block with ID " < < block_id < < " already exists; ignoring it (removing part " < < part - > name < < " ) " ) ;
/// Если данные отличались от тех, что были вставлены ранее с тем же ID, бросим исключение.
2015-09-24 05:47:17 +00:00
if ( expected_checksum ! = checksum )
{
if ( ! insert_id . empty ( ) )
throw Exception ( " Attempt to insert block with same ID but different checksum " , ErrorCodes : : CHECKSUM_DOESNT_MATCH ) ;
else
throw Exception ( " Logical error: got ZNODEEXISTS while inserting data, block ID is derived from checksum but checksum doesn't match " , ErrorCodes : : LOGICAL_ERROR ) ;
}
2015-09-09 19:03:46 +00:00
transaction . rollback ( ) ;
2014-07-25 11:15:11 +00:00
}
2015-09-11 02:13:59 +00:00
else if ( zookeeper - > exists ( quorum_status_path ) )
{
transaction . rollback ( ) ;
throw Exception ( " Another quorum insert has been already started " , ErrorCodes : : UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE ) ;
}
2014-07-25 11:15:11 +00:00
else
{
2015-09-11 02:13:59 +00:00
/// Сюда можем попасть также, если узел с кворумом существовал, но потом быстро был удалён.
2014-07-25 11:15:11 +00:00
throw Exception ( " Unexpected ZNODEEXISTS while adding block " + toString ( part_number ) + " with ID " + block_id + " : "
+ zkutil : : ZooKeeper : : error2string ( code ) , ErrorCodes : : UNEXPECTED_ZOOKEEPER_ERROR ) ;
}
2014-06-27 17:21:31 +00:00
}
else
{
2014-07-25 11:15:11 +00:00
throw Exception ( " Unexpected error while adding block " + toString ( part_number ) + " with ID " + block_id + " : "
2014-06-27 17:21:31 +00:00
+ zkutil : : ZooKeeper : : error2string ( code ) , ErrorCodes : : UNEXPECTED_ZOOKEEPER_ERROR ) ;
}
}
2014-10-02 23:31:18 +00:00
catch ( const zkutil : : KeeperException & e )
2014-07-01 15:58:25 +00:00
{
2014-07-25 11:15:11 +00:00
/** Если потерялось соединение, и мы не знаем, применились ли изменения, нельзя удалять локальный кусок:
* е с л и и з м е н е н и я п р и м е н и л и с ь , в / blocks / п о я в и л с я в с т а в л е н н ы й б л о к , и е г о н е л ь з я б у д е т в с т а в и т ь с н о в а .
*/
if ( e . code = = ZOPERATIONTIMEOUT | |
e . code = = ZCONNECTIONLOSS )
{
transaction . commit ( ) ;
2014-07-25 16:09:58 +00:00
storage . enqueuePartForCheck ( part - > name ) ;
2014-07-25 11:15:11 +00:00
}
throw ;
2014-07-01 15:58:25 +00:00
}
2015-09-11 02:13:59 +00:00
if ( quorum )
{
/// Дожидаемся достижения кворума. TODO Настраиваемый таймаут.
LOG_TRACE ( log , " Waiting for quorum " ) ;
zookeeper - > waitForDisappear ( quorum_status_path ) ;
LOG_TRACE ( log , " Quorum satisfied " ) ;
}
2014-04-02 07:59:43 +00:00
}
}
private :
StorageReplicatedMergeTree & storage ;
String insert_id ;
2015-09-10 20:43:42 +00:00
size_t quorum ;
2015-04-16 06:12:35 +00:00
size_t block_index = 0 ;
2014-04-02 13:45:39 +00:00
Logger * log ;
2014-09-04 20:26:14 +00:00
/// Позволяет проверить, что сессия в ZooKeeper ещё жива.
2014-12-12 20:50:32 +00:00
void assertSessionIsNotExpired ( zkutil : : ZooKeeperPtr & zookeeper )
2014-09-04 20:26:14 +00:00
{
2014-12-12 20:50:32 +00:00
if ( zookeeper - > expired ( ) )
2014-09-04 20:26:14 +00:00
throw Exception ( " ZooKeeper session has been expired. " , ErrorCodes : : NO_ZOOKEEPER ) ;
}
2014-04-02 07:59:43 +00:00
} ;
}