Go(Echo)のAPIにNewRelicでパフォーマンス監視してみた

# 概要
Goの人気WebフレームワークであるEchoを使って作られたAPIにNew Relicでパフォーマンス監視してみました。
具体的には、New Relicの公式Goエージェントとnrechoミドルウェアを使い、APIエンドポイントをモニタリングする仕組みを構築してみました。

# 手順

前提：NewRelicのアカウント登録済みで、ライセンスキーが取得できること

プロジェクトに必要なパッケージを追加

```console
go get github.com/newrelic/go-agent/v3/newrelic
go get github.com/newrelic/go-agent/v3/integrations/nrecho-v4
```


## New Relicの初期化とnrechoミドルウェアの設定

New Relicアプリケーションを初期化し、nrechoミドルウェアを設定します。


```go
func main() {
	// New Relicアプリケーションの初期化
	app, err := newrelic.NewApplication(
		newrelic.ConfigAppName("GoSampleAPI"),         // アプリケーション名
		newrelic.ConfigLicense("licensekey"),          // ライセンスキー
		newrelic.ConfigDistributedTracerEnabled(true), // 分散トレーシングを有効化
	)
	if err != nil {
		log.Fatalf("New Relic initialization failed: %v", err)
	}

	// Echoのインスタンス作成
	e := echo.New()

	// nrechoミドルウェアの設定
	e.Use(nrecho.Middleware(app))

    // DB接続の初期化
	db, err := sql.Open("sqlite3", "./test.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// エンドポイントの設定
	e.GET("/api/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, New Relic!")
	})
	e.POST("/api/users", CreateUserHandler(db))

	// サーバー起動
	e.Logger.Fatal(e.Start(":8080"))
}

// ユーザー登録ハンドラ
func CreateUserHandler(db *sql.DB) echo.HandlerFunc {
	return func(c echo.Context) error {
		type Request struct {
			Name  string `json:"name"`
			Email string `json:"email"`
		}
		req := new(Request)
		if err := c.Bind(req); err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
		}
		query := `INSERT INTO users (name, email) VALUES (?, ?)`

		result, err := db.Exec(query, req.Name, req.Email)
		if err != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to create user"})
		}

		id, _ := result.LastInsertId()
		return c.JSON(http.StatusOK, map[string]interface{}{"id": id, "name": req.Name, "email": req.Email})
	}
}
```

この状態で `POST /api/users` を実行してみると 全体の処理速度を計測することができてますが、特定の処理やデータベース操作の詳細な計測はされていません。
Goではエージェントを入れただけでは全体の処理速度のみしか計測されないので、特定の処理やデータベース処理のパフォーマンスも計測する場合は、別途処理を追加する必要があります。

![スクリーンショット 2024-12-10 22.51.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929769/d86b4db1-8594-3866-64be-a0c85535a421.png)

![スクリーンショット 2024-12-10 22.52.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929769/1524fde6-6db1-d150-7b84-c582838884ce.png)


## SegmentとDatastoreSegmentを使って処理計測を行う

### 詳細計測の追加方法（Segmentの活用）

```go

func CreateUserHandler(db *sql.DB) echo.HandlerFunc {
	return func(c echo.Context) error {
		txn := nrecho.FromContext(c) // New Relicトランザクションを取得
		segment := txn.StartSegment("CreateUserHandler")
		defer segment.End()

		// 入力データのバリデーションを計測
		segment2 := txn.StartSegment("Validation")
		type Request struct {
			Name  string `json:"name"`
			Email string `json:"email"`
		}
		req := new(Request)
		if err := c.Bind(req); err != nil {
			segment2.End() // セグメントを終了
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
		}
		segment2.End() // セグメントを終了

		query := `INSERT INTO users (name, email) VALUES (?, ?)`

		result, err := db.Exec(query, req.Name, req.Email)
		if err != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to create user"})
		}

		id, _ := result.LastInsertId()
		return c.JSON(http.StatusOK, map[string]interface{}{"id": id, "name": req.Name, "email": req.Email})
	}
}
```

ポイント

・`txn.StartSegment` で セグメントを開始し、Endで終了します
・セグメント名（Validation）はNew Relicダッシュボードで確認可能


こうすることで、Segmentを切った処理に対して個別に処理時間の計測が可能になりました

![スクリーンショット 2024-12-11 0.00.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929769/285cb417-2331-63f2-3891-d39d9651dd7b.png)


### データベース操作を計測する（DatastoreSegmentの活用）

DatastoreSegmentは、データベース操作を詳細にモニタリングできます。これにより、クエリごとの処理時間や実行したクエリの詳細を把握することができます

```go
		dSegment := newrelic.DatastoreSegment{
			StartTime:          txn.StartSegmentNow(),
			Product:            newrelic.DatastoreSQLite,
			Collection:         "users",
			Operation:          "INSERT",
			ParameterizedQuery: query,
			QueryParameters:    map[string]interface{}{"name": req.Name, "email": req.Email},
		}
		result, err := db.Exec(query, req.Name, req.Email)
		dSegment.End() // セグメントを終了
```

DB操作しているソースコードをDatastoreSegmentで挟んで　`.End` してあげることで計測が可能になります。

ポイント
・newrelic.DatastoreSegmentを使うと、データベース製品（DatastoreSQLite）や操作内容（INSERT）を指定可能。
・セグメント終了後にEndを呼び出して計測を完了します。

![スクリーンショット 2024-12-11 0.12.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929769/1460ee1f-7909-a81b-9f2f-8c3b0652a4a9.png)


## まとめ

- EchoフレームワークにNew Relicを統合する場合は、nrechoを使えばでAPI全体の処理を計測が可能
    - ただし大元の速度計測のみなので、処理単位で計測するには追加処理が必要
- SegmentやDatastoreSegmentを使うことでコードブロック単位で計測可能。
    - Segmentで任意の処理を詳細に計測
    - DatastoreSegmentでデータベース操作を計測

これらを組み合わせることで、Go APIの詳細な動作をモニタリングし、パフォーマンスの改善ポイントを効率よく特定できます。