2015-01-11 30 views
-1

我正在研究解析和繪製數據庫日誌文件信息的腳本。一些例子loglines可能是:緊湊的數據結構,用於在Go中存儲已解析的日誌行(即Go中的多個枚舉的緊湊數據結構)

Tue Dec 2 03:21:09.543 [rsHealthPoll] DBClientCursor::init call() failed 
Tue Dec 2 03:21:09.543 [rsHealthPoll] replset info example.com:27017 heartbeat failed, retrying 
Thu Nov 20 00:05:13.189 [conn1264369] insert foobar.fs.chunks ninserted:1 keyUpdates:0 locks(micros) w:110298 110ms 
Thu Nov 20 00:06:19.136 [conn1263135] update foobar.fs.chunks query: { files_id: ObjectId('54661657b23a225c1e4b00ac'), n: 0 } update: { $set: { data: BinData } } nscanned:1 nupdated:1 keyUpdates:0 locks(micros) w:675 137ms 
Thu Nov 20 00:06:19.136 [conn1258266] update foobar.fs.chunks query: { files_id: ObjectId('54661657ae3a22741e0132df'), n: 0 } update: { $set: { data: BinData } } nscanned:1 nupdated:1 keyUpdates:0 locks(micros) w:687 186ms 
Thu Nov 20 00:12:14.859 [conn1113639] getmore local.oplog.rs query: { ts: { $gte: Timestamp 1416453003000|74 } } cursorid:7965836327322142721 ntoreturn:0 keyUpdates:0 numYields: 15 locks(micros) r:351042 nreturned:3311 reslen:56307 188ms 

並不是每一個的logline包含了所有領域,但有些我們解析出的字段包括:

  • 日期時間
  • 查詢持續時間
  • 螺紋
  • 的名稱
  • 連接號碼(例如1234,532434,53433)
  • 日誌級別(例如警告,錯誤,信息,調試等)
  • 記錄組件(例如存儲,日記,命令,Indexin等)
  • 類型的操作(如查詢,插入,刪除等)
  • 命名空間

總日誌文件往往是相當大的(幾百MB的最多GB的coupe)。目前這個腳本是用Python編寫的,除了字段之外,它還存儲原始的原始記錄以及一個標記版本 - 最終的內存消耗實際上是原始日誌文件大小的幾倍。因此,內存消耗是我想要改進的主要因素之一。爲了好玩/學習,我想我可能會嘗試在Go中重新執行此操作,並查看我們是否可以使用更緊湊的數據結構。

許多字段都是枚舉(枚舉) - 對於其中的一些字段,預先知道一組值(例如日誌記錄組件)。對於其他人(例如線程的名稱,連接號,名稱空間),我們將在運行時解析日誌文件。

計劃更改

首先,許多這些枚舉存儲爲字符串。所以我猜測一個改進將會轉向使用類似uint8的東西來存儲它,然後使用const(對於我們事先知道的),或者將某種映射表映射回原始字符串(對於我們制定出來的)。或者還有其他的反應,我更喜歡const與某種映射結構?其次,不是將原始的logline存儲爲字符串,我們可能會將偏移量存儲回磁盤上的原始文件。

問題

  1. 你看到的任何問題與任何的上述兩個計劃的變化?這是一個好的起點嗎?
  2. 您是否有任何其他提示/建議來優化我們如何存儲loglines的內存消耗?
  3. 我知道位圖,有一些像Roaring Bitmaps(http://roaringbitmap.org/)這樣的壓縮位圖,在壓縮的時候你仍然可以正常訪問/修改。顯然,像這樣的東西的總體術語是簡潔的數據結構。 但是,有沒有任何等同於咆哮的位圖,但枚舉?或者任何其他聰明的方式來存儲這個緊湊?
  4. 我也想過bloom過濾器,也許使用這些來存儲每個logline是否在一個集合中(即,日誌級別警告,日誌級別錯誤) - 但是,它只能在其中一個集合中,所以我不'不知道這是否有道理。另外,不知道如何處理誤報。

想法?

+2

目前尚不清楚你要問什麼請說明你的具體問題或添加額外的細節,以確切地突出你需要什麼。正如目前所寫,很難確切地說出你在問什麼。例如,提供日誌文件行的示例。 – peterSO

+0

@peterSO當然,我已經添加了標題,並試圖澄清這個問題多一點 - 讓我知道如果這是有幫助的。 – victorhooi

+0

關於枚舉,你可能想要定義一個類型('LogLevel類型'),然後在你的const定義中使用'iota'(const(INFO LogLevel = iota' then,'DEBUG','ERROR'等)。 http://play.golang.org/p/PjrjSVIvrS https://golang.org/ref/spec#Iota – Intermernet

回答

0

您是否發現上述兩個計劃更改中的任何一個有問題?這是一個好的起點嗎?

沒有問題。如果日誌絕對是行分隔的,你可以直接存儲行號,但是它可以更強大的存儲字節偏移量。標準io.Reader接口返回讀取的字節數,以便您可以使用它獲得偏移量。

您是否有任何其他提示/建議來優化我們如何存儲loglines的內存消耗?

這取決於你想要使用它們,但是一旦它們被標記化(並且你已經得到了你想要的數據),爲什麼要保持內存中的行呢?它已經在文件中,現在你有了一個偏移量來快速查找它。

是否有任何等同於咆哮的位圖,但枚舉?或者任何其他聰明的方式來存儲這個緊湊?

我傾向於將每個枚舉類型定義爲int,並使用iota。例如:

package main 

import (
    "fmt" 
    "time" 
) 

type LogLevel int 
type LogComponent int 
type Operation int 

const (
    Info LogLevel = iota 
    Warning 
    Debug 
    Error 
) 

const (
    Storage LogComponent = iota 
    Journal 
    Commands 
    Indexin 
) 

const (
    Query Operation = iota 
    Insert 
    Delete 
) 

type LogLine struct { 
    DateTime  time.Time 
    QueryDuration time.Duration 
    ThreadName string 
    ConNum  uint 
    Level   LogLevel 
    Comp   LogComponent 
    Op   Operation 
    Namespace  string 
} 

func main() { 
    l := &LogLine{ 
     time.Now(), 
     10 * time.Second, 
     "query1", 
     1000, 
     Info, 
     Journal, 
     Delete, 
     "ns1", 
    } 
    fmt.Printf("%v\n", l) 
} 

產生&{2009-11-10 23:00:00 +0000 UTC 10s query1 1000 0 1 2 ns1}

Playground

你可以收拾一些結構域,但你需要定義位範圍爲每個字段,你失去一些的無限制性。例如限定LogLevel的作爲第一個2位,組分作爲下一個2個比特等

我還想到布隆過濾器的,也許使用這些存儲每個的logline是否是一組(即日誌記錄級別警告,日誌記錄級錯誤) - 但是,它只能在其中一個集合中,所以我不知道這是否合理。另外,不知道如何處理誤報。

對於您當前的示例,布隆過濾器可能會矯枉過正。爲每個枚舉或其他一些主索引記錄行號(例如)日誌級別關係可能更容易。如你所說,每個日誌行只能在一個集合中。實際上,根據枚舉字段的數量,使用打包的枚舉作爲標識符可能會更容易,如map[int][]int

Set := make(map[int][]int) 
Set[int(Delete) << 4 + int(Journal) << 2 + int(Debug)] = []int{7, 45, 900} // Line numbers in this set. 

請參閱here瞭解一個完整的,儘管hackish的例子。