天天看點

OceanBase寫入限速源碼解讀

作者:愛可生

一、簡介

OceanBase中的寫入限速機制旨在控制系統中寫入操作(一般寫入操作包括插入、更新和删除等)的速率,目的是為了提高資料庫系統的穩定性。本文主要通過以下2個參數來解釋寫入限速的實作機制。

  1. writing_throttling_trigger_percentage:設定寫入速度的門檻值百分比。當記憶體使用達到該門檻值時,觸發寫入限速機制。預設值為60,取值範圍為1到100(100表示關閉寫入限速)。
  2. writing_throttling_maximum_duration:指定觸發寫入限速後,所需的剩餘記憶體配置設定時間。預設值為2小時。通常情況下,不需要修改該參數。

請注意,OceanBase 2.2.30 及之後版本才開始支援該機制。

二、實作原理

1. 進入限速邏輯

當執行寫入操作申請記憶體時,觸發寫入限速條件:已使用的 Memstore 記憶體超過設定的比例(比例門檻值由 writing_throttling_trigger_percentage 參數确定),系統進入限速邏輯。

2. 多次限速

限速邏輯會将本次申請記憶體的任務分成多次進行限速。每次限速的執行時間最多為20毫秒。

3.系統在每次限速中進行一個限速循環,在限速循環中,系統會檢查以下條件:

  • 記憶體釋放:系統檢查記憶體是否已經釋放足夠多的記憶體(滿足不進入限速的條件),系統已經不需要限速。
  • SQL執行時間限制:系統檢查SQL的執行時間是否已經達到限制。如果已經達到限制,則系統退出限速循環,并将SQL完成的資訊發送給用戶端。
  • 休眠時間:系統檢查是否已經休眠了20秒。如果已經休眠,則系統退出限速循環,并将SQL完成的資訊發送給用戶端。

4. 完成限速

如果上述任意一項條件滿足,系統将退出限速循環,并将SQL完成的資訊發送給用戶端。這樣可以確定SQL能夠成功執行完成,并保證系統的穩定性。

5. 流程參考

OceanBase寫入限速源碼解讀

三、源碼解讀

以下通過源碼以一條insert語句的部分堆棧來解釋writing_throttling_trigger_percentage和writing_throttling_maximum_duration是如何影響限速邏輯的。

ObTablet::insert_row_without_rowkey_check()

int ObTablet::insert_row_without_rowkey_check(
    ObRelativeTable &relative_table,
    ObStoreCtx &store_ctx,
    const common::ObIArray<share::schema::ObColDesc> &col_descs,
    const storage::ObStoreRow &row)
{
  int ret = OB_SUCCESS;
  {
    // insert_row_without_rowkey_check 執行結束時會調用ObStorageTableGuard的析構函數,進行限速處理
    ObStorageTableGuard guard(this, store_ctx, true);
    ObMemtable *write_memtable = nullptr;
    ...
    //write_memtable->set()會調用ObFifoArena::alloc()申請記憶體, 在配置設定記憶體時進行限速判斷
    else if (OB_FAIL(write_memtable->set(store_ctx, relative_table.get_table_id(),
    full_read_info_, col_descs, row)))
    ...
  }
  return ret;
}
           

該方法會執行個體化 ObStorageTableGuard類 , 限速的執行過程定義在該類的析構函數内, 是以程式會在執行完 write_memtable 後才進行限速。後續會進行寫Memtable的流程,這裡不做贅述, 大緻調用堆棧如下:

| > oceanbase::storage::ObTablet::insert_row_without_rowkey_check(...) (/src/storage/tablet/ob_tablet.cpp:1425)
| + > oceanbase::memtable::ObMemtable::set(...) (/src/storage/memtable/ob_memtable.cpp:339)
| + - > oceanbase::memtable::ObMemtable::set_(...) (/src/storage/memtable/ob_memtable.cpp:2538)
| + - x > oceanbase::memtable::ObMemtable::mvcc_write_(...) (/src/storage/memtable/ob_memtable.cpp:2655)
| + - x = > oceanbase::memtable::ObMvccEngine::create_kv(...) (/src/storage/memtable/mvcc/ob_mvcc_engine.cpp:275)
| + - x = | > oceanbase::memtable::ObMTKVBuilder::dup_key(...) (/src/storage/memtable/ob_memtable.h:77)
| + - x = | + > oceanbase::common::ObGMemstoreAllocator::AllocHandle::alloc(...) (/src/share/allocator/ob_gmemstore_allocator.h:84)
| + - x = | + - > oceanbase::common::ObGMemstoreAllocator::alloc(...) (/src/share/allocator/ob_gmemstore_allocator.cpp:125)
| + - x = | + - x > oceanbase::common::ObFifoArena::alloc(...) (/src/share/allocator/ob_fifo_arena.cpp:157)
| + - x = | + - x = > oceanbase::common::ObFifoArena::speed_limit(...)(/src/share/allocator/ob_fifo_arena.cpp:301)
| + - x = | + - x = | > oceanbase::common::ObFifoArena::ObWriteThrottleInfo::check_and_calc_decay_factor(...)(/src/share/allocator/ob_fifo_arena.cpp:75)
| + > oceanbase::storage::ObStorageTableGuard::~ObStorageTableGuard(...) (/src/storage/ob_storage_table_guard.cpp:53)
           

ObFifoArena::alloc()

寫memtable時會申請記憶體, 這時候會去判斷是否需要限速

void* ObFifoArena::alloc(int64_t adv_idx, Handle& handle, int64_t size)
{
  int ret = OB_SUCCESS;
  void* ptr = NULL;
  int64_t rsize = size + sizeof(Page) + sizeof(Ref);
  // 調用speed limit 判斷限速
  speed_limit(ATOMIC_LOAD(&hold_), size);
  ...
}
           

ObFifoArena::speed_limit()

這個方法主要用來判斷是否需要限速,同時根據配置的writing_throttling_maximum_duration值,計算出一個衰減因子用于等待時間的計算

void ObFifoArena::speed_limit(const int64_t cur_mem_hold, const int64_t alloc_size)
{
  int ret = OB_SUCCESS;
  //擷取租戶的writing_throttling_trigger_percentage值
  int64_t trigger_percentage = get_writing_throttling_trigger_percentage_();
  int64_t trigger_mem_limit = 0;
  bool need_speed_limit = false;
  int64_t seq = 0;
  int64_t throttling_interval = 0;
  // trigger_percentage <100 ,表示開啟限速,再進行記憶體使用是否達到觸發門檻值的判斷
  if (trigger_percentage < 100) {
    if (OB_UNLIKELY(cur_mem_hold < 0 || alloc_size <= 0 || lastest_memstore_threshold_ <= 0 || trigger_percentage <= 0)) {
      COMMON_LOG(ERROR, "invalid arguments", K(cur_mem_hold), K(alloc_size), K(lastest_memstore_threshold_), K(trigger_percentage));
    } else if (cur_mem_hold > (trigger_mem_limit = lastest_memstore_threshold_ * trigger_percentage / 100)) {
      // 目前使用記憶體超過觸發門檻值,需要限速,設定need_speed_limit 為true
      need_speed_limit = true;
      // 擷取writing_throttling_maximum_duration的值,預設 2h
      int64_t alloc_duration = get_writing_throttling_maximum_duration_();
      // 計算衰減因子,用于sleep時間計算
      if (OB_FAIL(throttle_info_.check_and_calc_decay_factor(lastest_memstore_threshold_, trigger_percentage, alloc_duration))) {
        COMMON_LOG(WARN, "failed to check_and_calc_decay_factor", K(cur_mem_hold), K(alloc_size), K(throttle_info_));
      }
    }
  
    //這塊代碼是将記憶體和時鐘值綁定,確定記憶體配置設定和寫入限速的穩定性
    advance_clock();
    seq = ATOMIC_AAF(&max_seq_, alloc_size);
    get_seq() = seq;
     
    // 将need_speed_limit 指派給tl_need_speed_limit 線程變量
    tl_need_speed_limit() = need_speed_limit;
    //日志記錄,限速資訊
    if (need_speed_limit && REACH_TIME_INTERVAL(1 * 1000 * 1000L)) {
      COMMON_LOG(INFO, "report write throttle info", K(alloc_size), K(attr_), K(throttling_interval),
                  "max_seq_", ATOMIC_LOAD(&max_seq_), K(clock_),
                  K(cur_mem_hold), K(throttle_info_), K(seq));
    }
  }
}
           

ObFifoArena::ObWriteThrottleInfo::check_and_calc_decay_factor()

計算衰減因子

int ObFifoArena::ObWriteThrottleInfo::check_and_calc_decay_factor(int64_t memstore_threshold,
                                                                  int64_t trigger_percentage,
                                                                  int64_t alloc_duration)
{
  int ret = OB_SUCCESS;
  if (memstore_threshold != memstore_threshold_
      || trigger_percentage != trigger_percentage_
      || alloc_duration != alloc_duration_
      || decay_factor_ <= 0) {
    memstore_threshold_ = memstore_threshold;
    trigger_percentage_ = trigger_percentage;
    alloc_duration_ = alloc_duration;
    int64_t available_mem = (100 - trigger_percentage_) * memstore_threshold_ / 100;
    double N =  static_cast<double>(available_mem) / static_cast<double>(MEM_SLICE_SIZE);
    decay_factor_ = (static_cast<double>(alloc_duration) - N * static_cast<double>(MIN_INTERVAL))/ static_cast<double>((((N*(N+1)*N*(N+1)))/4));
    decay_factor_ = decay_factor_ < 0 ? 0 : decay_factor_;
    COMMON_LOG(INFO, "recalculate decay factor", K(memstore_threshold_), K(trigger_percentage_),
               K(decay_factor_), K(alloc_duration), K(available_mem), K(N));
  }
  return ret;
}
           

decay_factor公式中,alloc_duration為writing_throttling_maximum_duration的值,4.0版本中為2h,MIN_INTERVAL預設值20ms。

簡單來說,這個衰減因子是根據目前可用記憶體和writing_throttling_maximum_duration的值通過一個多項式計算出來的,整個過程如果writing_throttling_maximum_duration值不做調整,每次休眠時間會随着可用記憶體逐漸減少而慢慢增加。

ObStorageTableGuard::~ObStorageTableGuard()

限速流程執行

ObStorageTableGuard::~ObStorageTableGuard()
{
  //tl_need_speed_limit 在ObFifoArena::alloc()方法中指派
  bool &need_speed_limit = tl_need_speed_limit();
  // 在寫操作的上下文中, 建立ObStorageTableGuard 執行個體時,need_control_mem_ 會被指派為true
  if (need_control_mem_ && need_speed_limit) {
    bool need_sleep = true;
    int64_t left_interval = SPEED_LIMIT_MAX_SLEEP_TIME;
    //SPEED_LIMIT_MAX_SLEEP_TIME 預設20s,表示最大sleep時間
    if (!for_replay_) {
        // 如果不是回放日志
        //store_ctx_.timeout_ - ObTimeUtility::current_time() 表示距離事務逾時還要多久,如果該值小于0,表示事務已經逾時
        //兩者取小
      left_interval = min(SPEED_LIMIT_MAX_SLEEP_TIME, store_ctx_.timeout_ - ObTimeUtility::current_time());
    }
    // 如果memtable是當機狀态,不需要限速
    if (NULL != memtable_) {
      need_sleep = memtable_->is_active_memtable();
    }
    uint64_t timeout = 10000;//10s
    //事件記錄, 可以在v$session_event中檢視,event名: memstore memory page alloc wait
    //可以通過sql: select * from v$session_event where EVENT='memstore memory page alloc wait' 查詢;
    common::ObWaitEventGuard wait_guard(common::ObWaitEventIds::MEMSTORE_MEM_PAGE_ALLOC_WAIT, timeout, 0, 0, left_interval);
 
    reset();
    int tmp_ret = OB_SUCCESS;
    bool has_sleep = false;
    int64_t sleep_time = 0;
    int time = 0;
    int64_t &seq = get_seq();
    if (store_ctx_.mvcc_acc_ctx_.is_write()) {
      ObGMemstoreAllocator* memstore_allocator = NULL;
      //擷取目前租戶的memstore記憶體配置設定器
      if (OB_SUCCESS != (tmp_ret = ObMemstoreAllocatorMgr::get_instance().get_tenant_memstore_allocator(
          MTL_ID(), memstore_allocator))) {
      } else if (OB_ISNULL(memstore_allocator)) {
        LOG_WARN_RET(OB_ALLOCATE_MEMORY_FAILED, "get_tenant_mutil_allocator failed", K(store_ctx_.tablet_id_), K(tmp_ret));
      } else {
        while (need_sleep &&
               !memstore_allocator->check_clock_over_seq(seq) &&
               (left_interval > 0)) {
          if (for_replay_) {
            // 如果是回放日志,并且目前租戶下有正在進行的日志流,不做休眠,直接break
            if(MTL(ObTenantFreezer *)->exist_ls_freezing()) {
              break;
            }
          }
          //計算休眠時間
          int64_t expected_wait_time = memstore_allocator->expected_wait_time(seq);
          if (expected_wait_time == 0) {
            break;
          }
          //SLEEP_INTERVAL_PER_TIME 單次休眠時間,預設20ms
          //線程休眠,每次最多20ms
          uint32_t sleep_interval =
            static_cast<uint32_t>(min(min(left_interval, SLEEP_INTERVAL_PER_TIME), expected_wait_time));
          ::usleep(sleep_interval);
          // 累加休眠時間
          sleep_time += sleep_interval;
          // 休眠次數
          time++;
          //每次休眠之後,減去休眠時間
          left_interval -= sleep_interval;
          has_sleep = true;
          //每次休眠之後,重新判斷是否需要限速,因為可能在休眠過程中,記憶體經過轉儲後已經釋放出來了,這時候就不需要繼續限速了
          need_sleep = memstore_allocator->need_do_writing_throttle();
        }
      }
    }
    // 日志記錄,限速執行詳情
    if (REACH_TIME_INTERVAL(100 * 1000L) &&
        sleep_time > 0) {
      int64_t cost_time = ObTimeUtility::current_time() - init_ts_;
      LOG_INFO("throttle situation", K(sleep_time), K(time), K(seq), K(for_replay_), K(cost_time));
    }
 
    if (for_replay_ && has_sleep) {
      get_replay_is_writing_throttling() = true;
    }
  }
  reset();
}
           

總結

OB的寫入限速功能是在ObStorageTableGuard類的析構函數中實作的。由于該函數會在memtable寫入完成後才被調用,是以限速行為是後置的,會影響下一次記憶體配置設定。換言之,在目前寫入操作完成後,才會判斷是否需要執行限速,若需要,會延遲下一次記憶體配置設定。這種設計既確定限速不會影響目前的寫入操作,又能有效控制記憶體的配置設定和消耗。

四、使用方法

該參數是租戶級别的參數,可以在租戶管理者賬号下或者在sys租戶中指定租戶,設定記憶體寫入達到 80% 開始限速,并保證剩餘記憶體足夠提供 2h 的寫入限速,示例:

obclient> ALTER SYSTEM SET writing_throttling_trigger_percentage = 80;
Query OK, 0 rows affected
obclient> ALTER SYSTEM SET writing_throttling_maximum_duration = '2h';
Query OK, 0 rows affected
  
或者在sys租戶中指定租戶
obclient> ALTER SYSTEM SET writing_throttling_trigger_percentage = 80 tenant=<tenant_name>;
           

五、使用場景

1.建立租戶時使用 在寫壓力比較大的情況下,比如做導入資料時,限制寫入速度也是一種簡單高效的解決方法,雖然OceanBase的LSM-Tree存儲引擎架構可以及時當機memtable并釋放記憶體,但在寫入速度高于轉儲速度的場景下,仍有可能導緻Memstore耗盡。最新版本4.0預設開啟此配置,結合轉儲配置,可以有效控制Memstore的消耗。

2.發現qps異常下降時,尤其是包含大量寫時,也可以通過以下方式确認是否是由于寫入限制導緻。

  • 系統表

如果是觸發限速導緻的qps值下降,根據上面的代碼分析可知,會記錄在session_event表中,事件名是“memstore memory page alloc wait”。

select * from v$session_event where EVENT='memstore memory page alloc wait' \G;
*************************** 94. row ***************************
           CON_ID: 1
           SVR_IP: 10.186.64.124
         SVR_PORT: 22882
              SID: 3221487713
            EVENT: memstore memory page alloc wait
      TOTAL_WAITS: 182673
   TOTAL_TIMEOUTS: 0
      TIME_WAITED: 1004.4099
     AVERAGE_WAIT: 0.005498403704981032
         MAX_WAIT: 12.3022
TIME_WAITED_MICRO: 10044099
              CPU: NULL
         EVENT_ID: 11015
    WAIT_CLASS_ID: 109
      WAIT_CLASS#: 9
       WAIT_CLASS: SYSTEM_IO           
  • 日志

通過grep 'report write throttle info' observer.log ,如果輸入如下日志就可以确定是由于限速導緻的。

[2023-04-17 17:17:30.695621] INFO  [COMMON] speed_limit (ob_fifo_arena.cpp:319) [26466][T1_L0_G0][T1][Y59620ABA407C-0005F9818D1BFE06-0-0] [lt=2] report write throttle info(alloc_size=32, attr_=tenant_id=1, label=Memstore, ctx_id=1, prio=0, throttling_interval=0, max_seq_=11045142952, clock_=11045143112, cur_mem_hold=524288000, throttle_info_={decay_factor_:"6.693207379708156213e-02", alloc_duration_:7200000000, trigger_percentage_:21, memstore_threshold_:2147483600, period_throttled_count_:0, period_throttled_time_:0, total_throttled_count_:0, total_throttled_time_:0}, seq=11045142952)
           

同時grep 'throttle situation' observer.log,可以看到這次限速的具體内容。

[2023-04-17 17:17:31.006880] INFO  [STORAGE] ~ObStorageTableGuard (ob_storage_table_guard.cpp:109) [26466][T1_L0_G0][T1][Y59620ABA407C-0005F9818D1BFE06-0-0] [lt=85] throttle situation(sleep_time=4, time=1, seq=11048795064, for_replay_=false, cost_time=7025)
           

本文關鍵字: #Oceanbase# #寫入限速#

繼續閱讀