mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-07 16:16:37 +08:00
[优化] 数据库结构优化
This commit is contained in:
@@ -41,6 +41,9 @@ func registeredModels() []any {
|
||||
&NodeRequestReport{},
|
||||
&NodeAccessLog{},
|
||||
&NodeHealthEvent{},
|
||||
&NodeObservationOpenresty{},
|
||||
&NodeObservationFrps{},
|
||||
&NodeObservationFrpc{},
|
||||
&TLSCertificate{},
|
||||
&ManagedDomain{},
|
||||
&AcmeAccount{},
|
||||
|
||||
@@ -32,6 +32,16 @@ func V20() Migration {
|
||||
}
|
||||
|
||||
func migrateV20(ctx Context, db *gorm.DB, backend string) error {
|
||||
if !db.Migrator().HasColumn(&nodeV20{}, "relay_frps_connections") {
|
||||
if err := db.Migrator().AddColumn(&nodeV20{}, "RelayFrpsConnections"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !db.Migrator().HasColumn(&nodeV20{}, "relay_frps_proxy_count") {
|
||||
if err := db.Migrator().AddColumn(&nodeV20{}, "RelayFrpsProxyCount"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.ApplyCurrentSchema(db, backend); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// v21 renames agent_token to access_token, unifies versions, and separates node observabilities.
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type nodeV21 struct{}
|
||||
|
||||
func (nodeV21) TableName() string {
|
||||
return "nodes"
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(V21())
|
||||
}
|
||||
|
||||
func V21() Migration {
|
||||
return Migration{
|
||||
FromVersion: 20,
|
||||
ToVersion: 21,
|
||||
Migrate: migrateV21,
|
||||
Validate: validateV21,
|
||||
}
|
||||
}
|
||||
|
||||
func migrateV21(ctx Context, db *gorm.DB, backend string) error {
|
||||
slog.Info("starting v21 database migration (Node Optimization & Observation Split)")
|
||||
|
||||
migrator := db.Migrator()
|
||||
|
||||
if migrator.HasColumn(&nodeV21{}, "agent_token") {
|
||||
if err := migrator.RenameColumn(&nodeV21{}, "agent_token", "access_token"); err != nil {
|
||||
return fmt.Errorf("failed to rename agent_token to access_token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if migrator.HasColumn(&nodeV21{}, "agent_version") {
|
||||
if err := migrator.RenameColumn(&nodeV21{}, "agent_version", "version"); err != nil {
|
||||
return fmt.Errorf("failed to rename agent_version to version: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if migrator.HasColumn(&nodeV21{}, "nginx_version") {
|
||||
if err := migrator.RenameColumn(&nodeV21{}, "nginx_version", "ext_version"); err != nil {
|
||||
return fmt.Errorf("failed to rename nginx_version to ext_version: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Drop old merged columns
|
||||
columnsToDrop := []string{
|
||||
"relay_version",
|
||||
"relay_frp_version",
|
||||
"relay_frps_connections",
|
||||
"relay_frps_proxy_count",
|
||||
}
|
||||
|
||||
for _, col := range columnsToDrop {
|
||||
if migrator.HasColumn(&nodeV21{}, col) {
|
||||
if err := migrator.DropColumn(&nodeV21{}, col); err != nil {
|
||||
slog.Warn("failed to drop column in v21 migration", "column", col, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctx.ApplyCurrentSchema(db, backend); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Info("completed v21 database migration")
|
||||
return validateV21(ctx, db, backend)
|
||||
}
|
||||
|
||||
func validateV21(ctx Context, db *gorm.DB, backend string) error {
|
||||
if err := ctx.ValidateDatabaseSchemaVersion(db, backend, 21); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -86,6 +86,8 @@ func (databaseSchemaMigrationContext) ValidateDatabaseSchemaVersion(db *gorm.DB,
|
||||
return validateDatabaseSchemaV19(db, backend)
|
||||
case 20:
|
||||
return validateDatabaseSchemaV20(db, backend)
|
||||
case 21:
|
||||
return validateDatabaseSchemaV21(db, backend)
|
||||
default:
|
||||
return fmt.Errorf("database schema validation for v%d is not defined", version)
|
||||
}
|
||||
@@ -1264,11 +1266,8 @@ func validateExternalDatabaseSchema(ctx databaseSchemaMigrationContext, db *gorm
|
||||
return ctx.ValidateDatabaseSchemaVersion(db, backend, targetVersion)
|
||||
}
|
||||
for _, migration := range schemamigrate.Migrations() {
|
||||
if migration.ToVersion > targetVersion {
|
||||
continue
|
||||
}
|
||||
if err := migration.Validate(ctx, db, backend); err != nil {
|
||||
return err
|
||||
if migration.ToVersion == targetVersion {
|
||||
return migration.Validate(ctx, db, backend)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -1373,6 +1372,22 @@ func initializeFreshDatabaseSchema(db *gorm.DB, backend string) error {
|
||||
return saveDatabaseSchemaVersion(db, currentDatabaseSchemaVersion)
|
||||
}
|
||||
|
||||
func validateDatabaseSchemaV21(db *gorm.DB, backend string) error {
|
||||
if err := validateDatabaseSchemaV19(db, backend); err != nil {
|
||||
return err
|
||||
}
|
||||
if !db.Migrator().HasColumn(&Node{}, "access_token") {
|
||||
return fmt.Errorf("column nodes.access_token is missing")
|
||||
}
|
||||
if !db.Migrator().HasColumn(&Node{}, "version") {
|
||||
return fmt.Errorf("column nodes.version is missing")
|
||||
}
|
||||
if !db.Migrator().HasColumn(&Node{}, "ext_version") {
|
||||
return fmt.Errorf("column nodes.ext_version is missing")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureDatabaseSchemaUpToDate(db *gorm.DB, backend string) error {
|
||||
version, exists, err := loadDatabaseSchemaVersion(db)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,14 +12,14 @@ type Node struct {
|
||||
GeoLatitude *float64 `json:"geo_latitude"`
|
||||
GeoLongitude *float64 `json:"geo_longitude"`
|
||||
GeoManualOverride bool `json:"geo_manual_override" gorm:"not null;default:false"`
|
||||
AgentToken string `json:"-" gorm:"size:128;index"`
|
||||
AccessToken string `json:"-" gorm:"column:access_token;size:128;index"`
|
||||
AutoUpdateEnabled bool `json:"auto_update_enabled" gorm:"not null;default:false"`
|
||||
UpdateRequested bool `json:"update_requested" gorm:"not null;default:false"`
|
||||
UpdateChannel string `json:"update_channel" gorm:"size:16;not null;default:'stable'"`
|
||||
UpdateTag string `json:"update_tag" gorm:"size:64"`
|
||||
RestartOpenrestyRequested bool `json:"restart_openresty_requested" gorm:"not null;default:false"`
|
||||
AgentVersion string `json:"agent_version" gorm:"size:64;not null"`
|
||||
NginxVersion string `json:"nginx_version" gorm:"size:64"`
|
||||
Version string `json:"version" gorm:"size:64;not null"`
|
||||
ExtVersion string `json:"ext_version" gorm:"size:64"`
|
||||
OpenrestyStatus string `json:"openresty_status" gorm:"size:16;not null;default:'unknown'"`
|
||||
OpenrestyMessage string `json:"openresty_message" gorm:"type:text"`
|
||||
Status string `json:"status" gorm:"size:16;not null;default:'offline'"`
|
||||
@@ -28,7 +28,7 @@ type Node struct {
|
||||
LastError string `json:"last_error" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
// Node type: edge_node (default) | tunnel_relay
|
||||
// Node type: edge_node (default) | tunnel_relay | tunnel_client
|
||||
NodeType string `json:"node_type" gorm:"size:32;not null;default:'edge_node'"`
|
||||
// TunnelRelay specific fields
|
||||
RelayBindPort int `json:"relay_bind_port" gorm:"not null;default:0"`
|
||||
@@ -38,10 +38,6 @@ type Node struct {
|
||||
RelayClientAccessAddr string `json:"relay_client_access_addr" gorm:"size:255"`
|
||||
RelayClientProxyURL string `json:"relay_client_proxy_url" gorm:"size:512"`
|
||||
RelayStatus string `json:"relay_status" gorm:"size:16;not null;default:'unknown'"`
|
||||
RelayFrpVersion string `json:"relay_frp_version" gorm:"size:64"`
|
||||
RelayVersion string `json:"relay_version" gorm:"size:64"`
|
||||
RelayFrpsConnections int `json:"relay_frps_connections" gorm:"not null;default:0"`
|
||||
RelayFrpsProxyCount int `json:"relay_frps_proxy_count" gorm:"not null;default:0"`
|
||||
}
|
||||
|
||||
func ListNodes() (nodes []*Node, err error) {
|
||||
@@ -69,9 +65,9 @@ func GetNodeByID(id uint) (*Node, error) {
|
||||
return node, err
|
||||
}
|
||||
|
||||
func GetNodeByAgentToken(token string) (*Node, error) {
|
||||
func GetNodeByAccessToken(token string) (*Node, error) {
|
||||
node := &Node{}
|
||||
err := DB.Where("agent_token = ?", token).First(node).Error
|
||||
err := DB.Where("access_token = ?", token).First(node).Error
|
||||
return node, err
|
||||
}
|
||||
|
||||
|
||||
@@ -8,22 +8,19 @@ import (
|
||||
)
|
||||
|
||||
type NodeMetricSnapshot struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
CapturedAt time.Time `json:"captured_at" gorm:"index"`
|
||||
CPUUsagePercent float64 `json:"cpu_usage_percent"`
|
||||
MemoryUsedBytes int64 `json:"memory_used_bytes"`
|
||||
MemoryTotalBytes int64 `json:"memory_total_bytes"`
|
||||
StorageUsedBytes int64 `json:"storage_used_bytes"`
|
||||
StorageTotalBytes int64 `json:"storage_total_bytes"`
|
||||
DiskReadBytes int64 `json:"disk_read_bytes"`
|
||||
DiskWriteBytes int64 `json:"disk_write_bytes"`
|
||||
NetworkRxBytes int64 `json:"network_rx_bytes"`
|
||||
NetworkTxBytes int64 `json:"network_tx_bytes"`
|
||||
OpenrestyRxBytes int64 `json:"openresty_rx_bytes"`
|
||||
OpenrestyTxBytes int64 `json:"openresty_tx_bytes"`
|
||||
OpenrestyConnections int64 `json:"openresty_connections"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
CapturedAt time.Time `json:"captured_at" gorm:"index"`
|
||||
CPUUsagePercent float64 `json:"cpu_usage_percent"`
|
||||
MemoryUsedBytes int64 `json:"memory_used_bytes"`
|
||||
MemoryTotalBytes int64 `json:"memory_total_bytes"`
|
||||
StorageUsedBytes int64 `json:"storage_used_bytes"`
|
||||
StorageTotalBytes int64 `json:"storage_total_bytes"`
|
||||
DiskReadBytes int64 `json:"disk_read_bytes"`
|
||||
DiskWriteBytes int64 `json:"disk_write_bytes"`
|
||||
NetworkRxBytes int64 `json:"network_rx_bytes"`
|
||||
NetworkTxBytes int64 `json:"network_tx_bytes"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (snapshot *NodeMetricSnapshot) GetID() uint {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"openflare/utils"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type NodeObservationFrpc struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
CapturedAt time.Time `json:"captured_at" gorm:"index"`
|
||||
TunnelStatus string `json:"tunnel_status" gorm:"size:16"`
|
||||
ConnectedRelaysCount int `json:"connected_relays_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (obs *NodeObservationFrpc) GetID() uint {
|
||||
return obs.ID
|
||||
}
|
||||
|
||||
func (obs *NodeObservationFrpc) GetTime() time.Time {
|
||||
return obs.CapturedAt
|
||||
}
|
||||
|
||||
func (obs *NodeObservationFrpc) BeforeCreate(tx *gorm.DB) error {
|
||||
return assignObservabilityID(&obs.ID)
|
||||
}
|
||||
|
||||
func (obs *NodeObservationFrpc) Insert() error {
|
||||
return DB.Create(obs).Error
|
||||
}
|
||||
|
||||
func ListNodeObservationFrpcs(nodeID string, since time.Time, limit int) (observations []*NodeObservationFrpc, err error) {
|
||||
rows, err := queryAcrossShards("node_observation_frpcs", func(tx *gorm.DB) ([]*NodeObservationFrpc, error) {
|
||||
var shardRows []*NodeObservationFrpc
|
||||
query := tx.Order("captured_at desc, id desc")
|
||||
if nodeID != "" {
|
||||
query = query.Where("node_id = ?", nodeID)
|
||||
}
|
||||
if !since.IsZero() {
|
||||
query = query.Where("captured_at >= ?", since)
|
||||
}
|
||||
if err := query.Find(&shardRows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shardRows, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SortAndLimitRecords(rows, limit), nil
|
||||
}
|
||||
|
||||
func DeleteNodeObservationFrpcsBefore(db *gorm.DB, before time.Time) (int64, error) {
|
||||
return deleteAcrossShards(db, "node_observation_frpcs", &NodeObservationFrpc{}, func(tx *gorm.DB) *gorm.DB {
|
||||
return tx.Where("captured_at < ?", before)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"openflare/utils"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type NodeObservationFrps struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
CapturedAt time.Time `json:"captured_at" gorm:"index"`
|
||||
FrpsConnections int `json:"frps_connections"`
|
||||
FrpsProxyCount int `json:"frps_proxy_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (obs *NodeObservationFrps) GetID() uint {
|
||||
return obs.ID
|
||||
}
|
||||
|
||||
func (obs *NodeObservationFrps) GetTime() time.Time {
|
||||
return obs.CapturedAt
|
||||
}
|
||||
|
||||
func (obs *NodeObservationFrps) BeforeCreate(tx *gorm.DB) error {
|
||||
return assignObservabilityID(&obs.ID)
|
||||
}
|
||||
|
||||
func (obs *NodeObservationFrps) Insert() error {
|
||||
return DB.Create(obs).Error
|
||||
}
|
||||
|
||||
func ListNodeObservationFrps(nodeID string, since time.Time, limit int) (observations []*NodeObservationFrps, err error) {
|
||||
rows, err := queryAcrossShards("node_observation_frps", func(tx *gorm.DB) ([]*NodeObservationFrps, error) {
|
||||
var shardRows []*NodeObservationFrps
|
||||
query := tx.Order("captured_at desc, id desc")
|
||||
if nodeID != "" {
|
||||
query = query.Where("node_id = ?", nodeID)
|
||||
}
|
||||
if !since.IsZero() {
|
||||
query = query.Where("captured_at >= ?", since)
|
||||
}
|
||||
if err := query.Find(&shardRows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shardRows, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SortAndLimitRecords(rows, limit), nil
|
||||
}
|
||||
|
||||
func DeleteNodeObservationFrpsBefore(db *gorm.DB, before time.Time) (int64, error) {
|
||||
return deleteAcrossShards(db, "node_observation_frps", &NodeObservationFrps{}, func(tx *gorm.DB) *gorm.DB {
|
||||
return tx.Where("captured_at < ?", before)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"openflare/utils"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type NodeObservationOpenresty struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
CapturedAt time.Time `json:"captured_at" gorm:"index"`
|
||||
OpenrestyRxBytes int64 `json:"openresty_rx_bytes"`
|
||||
OpenrestyTxBytes int64 `json:"openresty_tx_bytes"`
|
||||
OpenrestyConnections int64 `json:"openresty_connections"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (obs *NodeObservationOpenresty) GetID() uint {
|
||||
return obs.ID
|
||||
}
|
||||
|
||||
func (obs *NodeObservationOpenresty) GetTime() time.Time {
|
||||
return obs.CapturedAt
|
||||
}
|
||||
|
||||
func (obs *NodeObservationOpenresty) BeforeCreate(tx *gorm.DB) error {
|
||||
return assignObservabilityID(&obs.ID)
|
||||
}
|
||||
|
||||
func (obs *NodeObservationOpenresty) Insert() error {
|
||||
return DB.Create(obs).Error
|
||||
}
|
||||
|
||||
func ListNodeObservationOpenresty(nodeID string, since time.Time, limit int) (observations []*NodeObservationOpenresty, err error) {
|
||||
rows, err := queryAcrossShards("node_observation_openresties", func(tx *gorm.DB) ([]*NodeObservationOpenresty, error) {
|
||||
var shardRows []*NodeObservationOpenresty
|
||||
query := tx.Order("captured_at desc, id desc")
|
||||
if nodeID != "" {
|
||||
query = query.Where("node_id = ?", nodeID)
|
||||
}
|
||||
if !since.IsZero() {
|
||||
query = query.Where("captured_at >= ?", since)
|
||||
}
|
||||
if err := query.Find(&shardRows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shardRows, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SortAndLimitRecords(rows, limit), nil
|
||||
}
|
||||
|
||||
func DeleteNodeObservationOpenrestiesBefore(db *gorm.DB, before time.Time) (int64, error) {
|
||||
return deleteAcrossShards(db, "node_observation_openresties", &NodeObservationOpenresty{}, func(tx *gorm.DB) *gorm.DB {
|
||||
return tx.Where("captured_at < ?", before)
|
||||
})
|
||||
}
|
||||
@@ -48,6 +48,9 @@ func shardedObservabilityTables() []any {
|
||||
&NodeMetricSnapshot{},
|
||||
&NodeRequestReport{},
|
||||
&NodeAccessLog{},
|
||||
&NodeObservationOpenresty{},
|
||||
&NodeObservationFrps{},
|
||||
&NodeObservationFrpc{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +59,15 @@ func shardedObservabilityBaseTables() []string {
|
||||
"node_metric_snapshots",
|
||||
"node_request_reports",
|
||||
"node_access_logs",
|
||||
"node_observation_openresties",
|
||||
"node_observation_frps",
|
||||
"node_observation_frpcs",
|
||||
}
|
||||
}
|
||||
|
||||
func isShardedObservabilityTable(tableName string) bool {
|
||||
switch strings.TrimSpace(tableName) {
|
||||
case "node_metric_snapshots", "node_request_reports", "node_access_logs":
|
||||
case "node_metric_snapshots", "node_request_reports", "node_access_logs", "node_observation_openresties", "node_observation_frps", "node_observation_frpcs":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user