Sentry 監控 - Snuba 資料中臺架構(Query Processing 簡介)

為少 發表於 2021-10-12

Sentry 監控 - Snuba 資料中臺架構(Query Processing 簡介)

系列

Snuba 有一個查詢處理管道,首先將 Snuba 查詢語言( legacySnQL)解析為 AST,然後在 Clickhouse 上執行 SQL 查詢。在這兩個階段之間,在 AST 上執行幾次傳遞以應用查詢處理轉換。

處理管道有兩個主要目標:優化查詢並防止對我們的基礎設施構成危險的查詢。

在資料模型上,查詢處理流水線分為邏輯部分,進行產品相關處理,物理部分專注於優化查詢。

邏輯部分包含查詢驗證等步驟,以確保它與資料模型匹配或應用自定義函式。 物理部分包括諸如提升標籤(promoting tags)和選擇預聚合檢視(pre-aggregated view)來為查詢提供服務等步驟。

查詢處理階段

本節介紹了上述各階段的程式碼和示例,並提供了一些提示。

Sentry 監控 - Snuba 資料中臺架構(Query Processing 簡介)

Legacy 和 SnQL 解析器

Snuba 支援兩種語言,傳統的基於 JSON 的語言和新的名為 SnQL 的語言。除了傳統語言不支援的連線和複合查詢之外,查詢處理管道不會更改是否使用一種或另一種語言。

Snuba 支援兩種語言,一種是基於 JSON 的舊語言,另一種是名為 SnQL 的新語言。 除了遺留語言不支援的連線和複合查詢之外,無論使用哪種語言,查詢處理管道都不會改變。

它們都生成一個邏輯查詢AST,該查詢由下面資料結構表示。

基於 JSON 的語言舊解析器原始碼:

SnQL 解析器:

查詢驗證(Query Validation)

此階段確保可以執行查詢(大多數情況下,我們還沒有捕獲所有可能的無效查詢)。 這個階段的職責是在無效查詢的情況下返回一個 HTTP400,並向使用者提供適當的有用訊息。

這分為兩個子階段:一般驗證(general validation)和實體特定驗證(entity specific validation)。

一般驗證由一組檢查組成,這些檢查在解析器生成查詢之後立即應用於每個查詢。這在 QueryEntity 函式中發生。這包括防止別名陰影(alias shadowing)和函式簽名驗證(function signature validation)等驗證。

每個實體也可以以必需列的形式提供一些驗證邏輯。這發生在 class Entity(Describable, ABC):。 這允許查詢處理拒絕在 project_id 上沒有條件或沒有時間範圍的查詢。

邏輯查詢處理器(Logical Query Processors)

查詢處理器是無狀態轉換,接收查詢物件(及其 AST)並就地轉換。這是為邏輯處理器實現的介面。在邏輯階段,每個實體提供按順序應用的查詢處理器。常見的用例是像 apdex 這樣的自定義函式,或者像時間序列處理器(time series processor)那樣的計時。

查詢處理器不應該依賴於在之前或之後執行的其他處理器,並且應該彼此獨立。

儲存選擇器(Storage Selector)

如 Snuba 資料模型中所述,每個實體可以定義多個儲存。 多個儲存代表多個表,並且出於效能原因可以定義物化檢視(materialized views),因為某些檢視可以更快地響應某些查詢。

在邏輯處理階段(完全基於實體)結束時,儲存選擇器可以檢查查詢併為查詢選擇合適的儲存。 儲存選擇器在實體資料模型中定義並實現此介面。 一個例子是 Errors 實體,它有兩個儲存,一個用於一致查詢(它們被路由到寫入事件的相同節點),另一個只包括我們沒有寫入的副本來服務大多數查詢。 這減少了我們寫入的節點上的負載。

查詢轉換器(Query Translator)

不同的 storage 有不同的 schema(這些反映了 clickhouse 表或檢視的 schema)。 它們通常都與實體模型不同,最顯著的例子是用於標籤 tags[abc] 的可下標表示式,它在 clickhouse 中不存在,其中訪問標籤看起來像 tags.values[indexOf(tags.key, 'abc')]

選擇 storage 後,需要將查詢轉換為物理查詢。Translator 是一個基於規則的系統,規則由實體(針對每個 storage)定義並按順序應用。

與查詢處理器相反,翻譯規則在查詢上沒有完整的上下文,只能翻譯單個表示式。 這使我們能夠輕鬆地編寫翻譯規則並跨實體重用它們。

這些是 transactions 實體的轉換規則。

物理查詢處理器(Physical Query Processors)

與邏輯查詢處理器相比,物理查詢處理器的工作方式非常相似。它們的介面非常相似,語義相同。 不同之處在於它們對物理查詢進行操作,因此,它們主要是為優化而設計的。 例如,該處理器在標籤上找到相等條件,並將它們替換為標籤雜湊圖(有布隆過濾器索引)上的等效條件,從而使過濾操作更快。

查詢拆分器(Query Splitter)

通過將某些查詢拆分為多個單獨的 Clickhouse 查詢並組合每個查詢的結果,可以以優化的方式執行某些查詢。

兩個例子是時間拆分和列拆分。兩者都在下面這個檔案中。

時間拆分(Time splitting)將一個查詢(不包含聚合且已正確排序)在一個可變的時間範圍內拆分為多個查詢,該時間範圍的大小逐漸增大,並在得到足夠的結果後按順序停止執行。

列拆分(Column splitting)拆分篩選和列獲取。它對最少數量的列執行查詢的篩選部分,以便 Clickhouse 載入較少的列,然後通過第二個查詢,僅為第一個查詢篩選的行獲取缺少的列。

查詢格式化器(Query Formatter)

該元件只是將查詢格式化為 Clickhouse 查詢字串。

複合查詢處理

上面的討論僅適用於簡單查詢、複合查詢(連線和包含子查詢的查詢遵循稍微不同的路徑)。

上面討論的簡單查詢管道不適用於連線查詢或包含子查詢的查詢。 為了使這項工作發揮作用,每個步驟都必須考慮連線的查詢和子查詢,這會增加過程的複雜性。

為了解決這個問題,我們將每個連線查詢轉換為多個簡單子查詢的連線。每個子查詢都是一個簡單的查詢,可以通過上述管道進行處理。這也是執行 Clickhouse 連線(join)的首選方式,因為它允許我們在連線之前應用過濾器。

Sentry 監控 - Snuba 資料中臺架構(Query Processing 簡介)

此類查詢的查詢處理管道由與上述內容相關的幾個附加步驟組成。

子查詢生成器(Subquery Generator)

該元件採用一個簡單的 SnQL 連線查詢,併為連線中的每個表建立一個子查詢。

表示式下推(Expressions Push Down)

上一步生成的查詢將是一個有效的連線,但效率極低。 這一步基本上是一個連線優化器(join optimizer),它將所有可以成為子查詢一部分的表示式下推到子查詢中。 這是一個獨立於子查詢處理的必要步驟,因為 Clickhouse join 引擎不執行任何表示式下推,所以它由 Snuba 來優化查詢。

簡單查詢處理管道(Simple Query Processing Pipeline)

這與上面討論的從邏輯查詢驗證到物理查詢處理器的管道相同。

連線優化(Join Optimizations)

在處理結束時,我們可以對整個複合查詢應用一些優化,例如將 join 轉換為 Semi Join