この記事の内容に関する予備知識については、記事「openGemini 分析: クエリ エンジン フレームワーク」を参照してください。
この記事では、 openGeminiカーネルに組み込み関数 (演算子) を追加する方法を説明します。ここでは例としてhello演算子を取り上げます。その機能は、次の出力のように"hello, $filed_value"を出力することです。
この記事が、誰もがオペレーター開発プロセスに慣れ、カーネル ソース コードに精通するための一定の基礎を築くのに役立つことを願っ
hello オペレーターの開発
ここでは、文字列である 1 つのパラメータを持つ HELLO() 関数を実装します。これを行うには、 openGemini/openGemini リポジトリのクローンを作成する必要があります。
(docs.opengemini.org/zh/dev-guide/get_started/build_source_code.html)
現在、 hello オペレーター クエリを実行すると、次のエラーが報告されます。
> select hello("name") from mst
ERR: undefined function hello()
正式にソースコードの変更を開始します。
最初のステップは、定義された関数の名前「hello」を open_src/influx/query/compile.go に追加することです。
func isStringFunction(call *influxql.Call) bool {
switch call.Name {
case "str", "strlen", "substr", "hello":
return true
}
次のステップは、engine/executor/schema.go を変更し、「hello」を追加することです。
func (qs *QuerySchema) isStringFunction(call *influxql.Call) bool {
switch call.Name {
case "str", "strlen", "substr", "hello":
return true
}
次に、open_src/influx/query/functions.go を変更し、CallType に「hello」を追加します。
func (m StringFunctionTypeMapper) CallType(name string, _ []influxql.DataType) (influxql.DataType, error) {
switch name {
case "str":
return influxql.Boolean, nil
case "strlen":
return influxql.Integer, nil
case "substr":
return influxql.String, nil
case "hello":
return influxql.String, nil
最後に、hello メソッドの実際の実装を追加する必要があります。コードは、engine/executor/string_functions.go にあります。
func (v StringValuer) Call(name string, args []interface{}) (interface{}, bool) {
switch name {
case "strlen":
......
case "hello":
if len(args) != 1 {
return nil, false
}
if arg0, ok := args[0].(string); ok {
return HelloFunc(arg0), true
}
return nil, true
default:
return nil, false
}
func HelloFunc(srcStr string) string {
// 测试性能优化时放开下面注释
// var h []byte
// h = make([]byte, 200*1024*1024)
// fmt.Println(h)
return "hello, " + srcStr
}
ここで、 openGeminiを再構築し、新しく追加された機能を試す必要があります。(https://docs.opengemini.org/zh/dev-guide/get_started/build_source_code.html)
最終結果:
> insert mst name="Tom"
> SELECT HELLO(name) from mst
+----------------------+------------------+
| time | hello |
+----------------------+------------------+
| 2021-08-16T16:00:00Z | hello, Tom |
+----------------------+------------------+
単体テスト
Engine/executor/string_functions.goのHelloFunc が期待どおりであることをテストする必要があります。hello で始まります。
Engine/executor/string_function_test.go ファイルに、次のテストを追加します。
func TestStringFunctionHello(t *testing.T) {
stringValuer := executor.StringValuer{}
inputName := "hello"
inputArgs := []interface{}{"Alice", "Bob", "Carry"}
expects := []interface{}{"hello, Alice", "hello, Bob", "hello, Carry"}
outputs := make([]interface{}, 0, len(expects))
for _, arg := range inputArgs {
if out, ok := stringValuer.Call(inputName, []interface{}{arg}); ok {
outputs = append(outputs, out)
}
}
assert.Equal(t, outputs, expects)
}
統合テスト
統合テスト (https://docs.opengemini.org/zh/dev-guide/get_started/test_tutorials.html) を追加する必要がある場合は、tests/server_test.goファイルに次のテスト関数を追加してください。
func TestServer_Query_Aggregate_For_Hello_Functions(t *testing.T) {
t.Parallel()
s := OpenServer(NewParseConfig(testCfgPath))
defer s.Close()
if err := s.CreateDatabaseAndRetentionPolicy("db0", NewRetentionPolicySpec("rp0", 1, 0), true); err != nil {
t.Fatal(err)
}
writes := []string{
fmt.Sprintf(`mst,country=china,name=azhu age=12.3,height=70i,address="shenzhen",alive=TRUE 1629129600000000000`),
fmt.Sprintf(`mst,country=american,name=alan age=20.5,height=80i,address="shanghai",alive=FALSE 1629129601000000000`),
fmt.Sprintf(`mst,country=germany,name=alang age=3.4,height=90i,address="beijin",alive=TRUE 1629129602000000000`),
fmt.Sprintf(`mst,country=japan,name=ahui age=30,height=121i,address="guangzhou",alive=FALSE 1629129603000000000`),
fmt.Sprintf(`mst,country=canada,name=aqiu age=35,height=138i,address="chengdu",alive=TRUE 1629129604000000000`),
fmt.Sprintf(`mst,country=china,name=agang age=48.8,height=149i,address="wuhan" 1629129605000000000`),
fmt.Sprintf(`mst,country=american,name=agan age=52.7,height=153i,alive=TRUE 1629129606000000000`),
fmt.Sprintf(`mst,country=germany,name=alin age=28.3,address="anhui",alive=FALSE 1629129607000000000`),
fmt.Sprintf(`mst,country=japan,name=ali height=179i,address="xian",alive=TRUE 1629129608000000000`),
fmt.Sprintf(`mst,country=canada age=60.8,height=180i,address="hangzhou",alive=FALSE 1629129609000000000`),
fmt.Sprintf(`mst,name=ahuang age=102,height=191i,address="nanjin",alive=TRUE 1629129610000000000`),
fmt.Sprintf(`mst,country=china,name=ayin age=123,height=203i,address="zhengzhou",alive=FALSE 1629129611000000000`),
}
test := NewTest("db0", "rp0")
test.writes = Writes{
&Write{data: strings.Join(writes, "\n")},
}
test.addQueries([]*Query{
&Query{
name: "SELECT hello(address)",
command: `SELECT hello("address") FROM db0.rp0.mst`,
exp: `{"results":[{"statement_id":0,"series":[{"name":"mst","columns":["time","hello"],"values":[["2021-08-16T16:00:00Z","hello, shenzhen"],["2021-08-16T16:00:01Z","hello, shanghai"],["2021-08-16T16:00:02Z","hello, beijin"],["2021-08-16T16:00:03Z","hello, guangzhou"],["2021-08-16T16:00:04Z","hello, chengdu"],["2021-08-16T16:00:05Z","hello, wuhan"],["2021-08-16T16:00:07Z","hello, anhui"],["2021-08-16T16:00:08Z","hello, xian"],["2021-08-16T16:00:09Z","hello, hangzhou"],["2021-08-16T16:00:10Z","hello, nanjin"],["2021-08-16T16:00:11Z","hello, zhengzhou"]]}]}]}`,
},
}...)
for i, query := range test.queries {
t.Run(query.name, func(t *testing.T) {
if i == 0 {
if err := test.init(s); err != nil {
t.Fatalf("test init failed: %s", err)
}
}
if query.skip {
t.Skipf("SKIP:: %s", query.name)
}
if err := query.Execute(s); err != nil {
t.Error(query.Error(err))
} else if !query.success() {
t.Error(query.failureMessage())
}
})
}
}
パフォーマンス分析(プロファイリング)と最適化
(1) 性能解析(プロファイリング)
他のデータベース システムと同様に、パフォーマンスは常に重要です。パフォーマンスのボトルネックがどこにあるのかを知りたい場合は、pprof と呼ばれる強力な Go プロファイリング ツールを使用できます。
プロセスを開始するときは、SQL 側で pprof 関数を有効にするように構成ファイルを変更する必要があります (ポートは 6061)。
[http]
pprof-enabled = true
HTTPエンドポイント経由でランタイム分析情報を収集する
通常、openGemini サーバーが実行されているときは、 http://127.0.0.1:6061/debug/pprof/profileで HTTP 経由で行われます。次のコマンドを実行すると、プロファイル結果を取得できます。
curl -G "http://127.0.0.1:6061/debug/pprof/profile?seconds=45" > profile.profile
go tool pprof -http 127.0.0.1:4001 profile.profile
これらのコマンドは 45 秒間のプロファイリング情報をキャプチャし、ブラウザに「127.0.0.1:4001」と入力してプロファイリング CPU 結果の Web ビューを開きます。このビューには、実行のフレーム グラフと、パフォーマンスのボトルネックの診断に役立つその他のビューが含まれています。( https://www.brendangregg.com/flamegraphs.html )
このエンドポイントを通じて追加のランタイム情報を収集することもできます。例えば:
-
ゴルーチン
curl -G "http://127.0.0.1:6061/debug/pprof/goroutine" > goroutine.profile
go tool trace -http 127.0.0.1:4001 goroutine.profile
-
トレース(コールチェーン)
curl -G "http://127.0.0.1:6061/debug/pprof/trace?seconds=3" > trace.profile
go tool trace -http 127.0.0.1:4001 trace.profile
-
ヒープ(メモリ)
curl -G "http://127.0.0.1:6061/debug/pprof/heap" > heap.profile
go tool pprof -http 127.0.0.1:4001 heap.profile
実行時情報を分析する方法については、Go の診断ドキュメントを参照してください。(https://golang.org/doc/diagnostics)
メモリアプリケーションのフレームグラフ:
HelloFunc、845MBのメモリを申請しました。
最適化されたフレーム グラフは次のとおりです。
基本的には特別なメモリ消費は見られず、合計約8MBしか適用されません。
(2) パフォーマンスの最適化
パフォーマンスの最適化方法には通常次のようなものがあります。
1. GCを最適化し、小さなオブジェクトのアプリケーションを削減する
2. 不要なオブジェクトのメモリ アプリケーションを削除する
3. キャッシュ コンテンツに一度に十分なスペースを割り当て、適切に再利用します。
4. 同時実行性の高いタスク処理に goroutine プールを使用する
5. []byte と string の間の変換を減らし、[]byte 文字列処理を使用してみてください。
要約する
openGeminiカーネルにhello 関数を追加すること に成功し、単体テスト、統合テスト、パフォーマンス分析と最適化を完了し、この時点で完全な開発プロセスを経験したことになります。この開発リズムは、ほとんどのエンタープライズレベルのプロジェクトで採用されています。この記事はデータベース プロジェクト開発の入門章として使用でき、将来的により複雑な機能を開発する開発者に少しでも役立つと思います。
openGemini公式ウェブサイト:http://www.openGemini.org
openGemini オープンソース アドレス: https://github.com/openGemini
openGemini パブリック アカウント:
注目へようこそ~openGeminiコミュニティに参加して、一緒に未来を構築、管理、共有することを心から歓迎します!
オープンソース フレームワーク NanUI の作者がスチールの販売に切り替えたため、プロジェクトは中断されました。Apple App Store の無料リストのナンバー 1 はポルノ ソフトウェア TypeScript です。人気が出てきたばかりなのに、なぜ大手はそれを放棄し始めるのでしょうか。 ? TIOBE 10月リスト:Javaが最大の下落、C#はJavaに迫る Rust 1.73.0リリース AIガールフレンドにイギリス女王暗殺を勧められた男性に懲役9年の実刑判決 Qt 6.6正式リリース ロイター:RISC-Vテクノロジーが中米テクノロジー戦争の鍵となる 新たな戦場 RISC-V: 単一の企業や国に支配されない レノボ、Android PC の発売を計画