2015-01-11 4 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 

Не каждый лаглинь содержит все поля, но некоторые из полей мы разбором из включают в себя:

  • Datetime
  • Запрос Продолжительность
  • Название Тема
  • Номер соединения (например, 1234, 532434, 53433)
  • Уровень ведения журнала (например, предупреждение, ошибка, информация, отладка и т. Д.)
  • Элемент регистрации (например, Хранение, журнал, команды, Indexin и т.д.)
  • Тип операции (например, запрос, вставка, удаление и т.д.)
  • пространство имен

Общая файл_журнала часто может быть достаточно большой (несколько сотен MBs до купе ГБ). В настоящее время скрипт находится в Python, а также в полях, он также хранит исходную исходную логическую строку, а также токенизированную версию - результирующее потребление памяти, хотя на самом деле несколько кратных первоначальному размеру файла журнала. Следовательно, потребление памяти является одной из основных вещей, которые я хотел бы улучшить.

Для удовольствия/обучения я решил попробовать повторить это в Go и посмотреть, можем ли мы использовать более компактную структуру данных.

Многие из полей являются перечислениями (перечислениями) - для некоторых из них заранее известен набор значений (например, логгирование, элемент регистрации). Для других (например, имя потока, номер подключения, пространство имен) мы разработаем набор во время выполнения при анализе файла журнала.

Планируемые изменения

Во-первых, многие из этих перечислений, хранятся в виде строк. Поэтому я предполагаю, что одним из улучшений будет переход на использование чего-то вроде uint8 для его сохранения, а затем либо с помощью констант (для тех, которые мы знаем заранее), либо с какой-то таблицей сопоставления с исходной строкой (для которые мы разрабатываем.) Или есть ли какие-либо другие рекомендации, которые я бы предпочел для consts по сравнению с какой-либо структурой отображения?

Во-вторых, вместо сохранения исходной строки в виде строки мы можем, вероятно, сохранить смещение обратно к исходному файлу на диске.

Вопросы

  1. ли вы какие-либо вопросы, либо из двух запланированных изменений выше? Являются ли они хорошей отправной точкой?
  2. Есть ли у вас какие-либо другие советы/рекомендации по оптимизации потребления памяти, как мы храним логлайн?
  3. Я знаю, что для растровых изображений есть такие вещи, как реверсивные растровые изображения (http://roaringbitmap.org/), которые представляют собой сжатые растровые изображения, которые вы все равно можете получить/изменить нормально, пока они сжаты. По-видимому, общий термин для подобных вещей - сжатые структуры данных. Однако существуют ли какие-либо эквиваленты ревущих растровых изображений, но для перечислений?Или любой другой умный способ хранения этого компактного?
  4. Я также подумал о фильтрах цветения и, возможно, использовал их для хранения того, был ли каждый логлайн установлен в наборе (т.е. предупреждение уровня ведения журнала, ошибка уровня регистрации) - однако он может быть только в одном из этих наборов, Знайте, если это имеет смысл. Кроме того, не уверен, как обрабатывать ложные срабатывания.

Мысли?

+2

Непонятно, что вы просите. Просьба уточнить вашу конкретную проблему или добавить дополнительные сведения, чтобы точно указать, что вам нужно.Как это написано в настоящее время, трудно точно сказать, что вы просите. Например, укажите образец строк журнала. – peterSO

+0

@peterSO Конечно, я добавил заголовки и попытался уточнить вопрос немного больше - дайте мне знать, если это будет полезно. – victorhooi

+0

Что касается перечислений, вы можете определить тип ('type LogLevel int'), а затем использовать' iota' в своих определениях const ('const (INFO LogLevel = iota' then,' DEBUG', 'ERROR' и т. Д.) См. http://play.golang.org/p/PjrjSVIvrS https://golang.org/ref/spec#Iota – Intermernet

ответ

0

Вы видите какие-либо проблемы с одним из двух запланированных изменений выше? Являются ли они хорошей отправной точкой?

Проблем с которыми нет. Если журналы определенно ограничены линией, вы можете просто сохранить номер строки, но может быть более надежным для хранения смещения байта. Стандартный интерфейс io.Reader возвращает количество прочитанных байтов, чтобы вы могли использовать это для получения смещения.

У вас есть другие советы/предложения по оптимизации потребления памяти, как мы храним логлайн?

Это зависит от того, для чего вы хотите использовать их, но как только они были маркированы (и у вас есть данные, которые вы хотите от строки), зачем удерживать их на линии в памяти? Это уже в файле, и теперь у вас есть смещение, чтобы быстро найти его.

Есть ли эквиваленты ревущих растровых изображений, но для перечислений? Или любой другой умный способ хранения этого компактного?

Я бы просто определил каждый тип перечисления как 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 бита и т.д.

Я также подумал о цветении фильтров, и, возможно, с помощью тех, хранить ли каждый лаглинь в наборе (то есть предупреждение об уровне протоколирования, logging level error) - однако, это может быть только в одном из этих наборов, поэтому я не знаю, имеет ли это смысл. Кроме того, не уверен, как обрабатывать ложные срабатывания.

Для вашего нынешнего примера фильтры цветения могут быть излишними. Может быть проще иметь [] int для каждого перечисления или какой-либо другой главный «индекс», который отслеживает номера строк для (например) отношений уровня журнала. Как вы сказали, каждая строка журнала может быть только в одном наборе. Фактически, в зависимости от количества полей перечисления, может быть проще использовать упакованные перечисления в качестве идентификатора для чего-то вроде 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 для полного, хотя хака например.