Files
EdgeNode/internal/caches/list_file_db.go

534 lines
12 KiB
Go
Raw Normal View History

2022-03-15 18:32:39 +08:00
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches
import (
"database/sql"
"errors"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
2022-03-15 18:32:39 +08:00
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
2022-03-16 16:20:53 +08:00
"runtime"
"strings"
2022-03-15 18:32:39 +08:00
"time"
)
type FileListDB struct {
dbPath string
2022-03-16 16:20:53 +08:00
readDB *dbs.DB
writeDB *dbs.DB
2022-03-15 18:32:39 +08:00
writeBatch *dbs.Batch
2022-03-15 18:32:39 +08:00
itemsTableName string
hitsTableName string
total int64
isClosed bool
isReady bool
// cacheItems
existsByHashStmt *dbs.Stmt // 根据hash检查是否存在
insertStmt *dbs.Stmt // 写入数据
insertSQL string
selectByHashStmt *dbs.Stmt // 使用hash查询数据
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
deleteByHashSQL string
2022-03-15 18:32:39 +08:00
statStmt *dbs.Stmt // 统计
purgeStmt *dbs.Stmt // 清理
deleteAllStmt *dbs.Stmt // 删除所有数据
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
// hits
insertHitStmt *dbs.Stmt // 写入数据
increaseHitStmt *dbs.Stmt // 增加点击量
deleteHitByHashStmt *dbs.Stmt // 根据hash删除数据
lfuHitsStmt *dbs.Stmt // 读取老的数据
}
func NewFileListDB() *FileListDB {
return &FileListDB{}
}
func (this *FileListDB) Open(dbPath string) error {
this.dbPath = dbPath
2022-03-16 16:20:53 +08:00
// write db
writeDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size=32000&_secure_delete=FAST")
2022-03-15 18:32:39 +08:00
if err != nil {
2022-03-16 16:20:53 +08:00
return errors.New("open write database failed: " + err.Error())
2022-03-15 18:32:39 +08:00
}
2022-03-16 16:20:53 +08:00
writeDB.SetMaxOpenConns(1)
2022-03-15 18:32:39 +08:00
// TODO 耗时过长,暂时不整理数据库
// TODO 需要根据行数来判断是否VACUUM
2022-03-15 18:32:39 +08:00
/**_, err = db.Exec("VACUUM")
if err != nil {
return err
}**/
2022-03-16 16:20:53 +08:00
this.writeDB = dbs.NewDB(writeDB)
this.writeBatch = dbs.NewBatch(writeDB, 4)
this.writeBatch.OnFail(func(err error) {
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error())
})
goman.New(func() {
this.writeBatch.Exec()
})
2022-03-15 18:32:39 +08:00
if teaconst.EnableDBStat {
this.writeBatch.EnableStat(true)
2022-03-16 16:20:53 +08:00
this.writeDB.EnableStat(true)
}
// read db
readDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=32000")
if err != nil {
return errors.New("open read database failed: " + err.Error())
}
readDB.SetMaxOpenConns(runtime.NumCPU())
this.readDB = dbs.NewDB(readDB)
if teaconst.EnableDBStat {
this.readDB.EnableStat(true)
2022-03-15 18:32:39 +08:00
}
return nil
}
func (this *FileListDB) Init() error {
this.itemsTableName = "cacheItems"
this.hitsTableName = "hits"
// 创建
var err = this.initTables(1)
if err != nil {
return errors.New("init tables failed: " + err.Error())
}
// 读取总数量
2022-03-16 16:20:53 +08:00
row := this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
2022-03-15 18:32:39 +08:00
if row.Err() != nil {
return row.Err()
}
var total int64
err = row.Scan(&total)
if err != nil {
return err
}
this.total = total
// 常用语句
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" INDEXED BY "hash" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
2022-03-16 16:20:53 +08:00
this.selectByHashStmt, err = this.readDB.Prepare(`SELECT "key", "headerSize", "bodySize", "metaSize", "expiredAt" FROM "` + this.itemsTableName + `" WHERE "hash"=? LIMIT 1`)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
this.deleteByHashSQL = `DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`
this.deleteByHashStmt, err = this.writeDB.Prepare(this.deleteByHashSQL)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
2022-03-16 16:20:53 +08:00
this.statStmt, err = this.readDB.Prepare(`SELECT COUNT(*), IFNULL(SUM(headerSize+bodySize+metaSize), 0), IFNULL(SUM(headerSize+bodySize), 0) FROM "` + this.itemsTableName + `"`)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
2022-03-16 16:20:53 +08:00
this.purgeStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" WHERE staleAt<=? LIMIT ?`)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
2022-03-16 16:20:53 +08:00
this.deleteAllStmt, err = this.writeDB.Prepare(`DELETE FROM "` + this.itemsTableName + `"`)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
2022-03-16 16:20:53 +08:00
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "id" ASC LIMIT ?`)
2022-03-15 18:32:39 +08:00
2022-03-16 16:20:53 +08:00
this.insertHitStmt, err = this.writeDB.Prepare(`INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`)
2022-03-15 18:32:39 +08:00
2022-03-16 16:20:53 +08:00
this.increaseHitStmt, err = this.writeDB.Prepare(`INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?) ON CONFLICT("hash") DO UPDATE SET "week1Hits"=IIF("week"=?, "week1Hits", "week2Hits"), "week2Hits"=IIF("week"=?, "week2Hits"+1, 1), "week"=?`)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
2022-03-16 16:20:53 +08:00
this.deleteHitByHashStmt, err = this.writeDB.Prepare(`DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
2022-03-16 16:20:53 +08:00
this.lfuHitsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.hitsTableName + `" ORDER BY "week" ASC, "week1Hits"+"week2Hits" ASC LIMIT ?`)
2022-03-15 18:32:39 +08:00
if err != nil {
return err
}
this.isReady = true
return nil
}
func (this *FileListDB) IsReady() bool {
return this.isReady
}
func (this *FileListDB) Total() int64 {
return this.total
}
func (this *FileListDB) AddAsync(hash string, item *Item) error {
if item.StaleAt == 0 {
item.StaleAt = item.ExpiredAt
}
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime())
return nil
}
func (this *FileListDB) AddSync(hash string, item *Item) error {
2022-03-15 18:32:39 +08:00
if item.StaleAt == 0 {
item.StaleAt = item.ExpiredAt
}
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime())
if err != nil {
return this.WrapError(err)
2022-03-15 18:32:39 +08:00
}
return nil
}
func (this *FileListDB) DeleteAsync(hash string) error {
this.writeBatch.Add(this.deleteByHashSQL, hash)
return nil
}
func (this *FileListDB) DeleteSync(hash string) error {
_, err := this.deleteByHashStmt.Exec(hash)
if err != nil {
return err
}
return nil
}
2022-03-15 18:32:39 +08:00
func (this *FileListDB) ListExpiredItems(count int) (hashList []string, err error) {
if !this.isReady {
return nil, nil
}
if count <= 0 {
count = 100
}
rows, err := this.purgeStmt.Query(time.Now().Unix(), count)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
var hash string
err = rows.Scan(&hash)
if err != nil {
return nil, err
}
hashList = append(hashList, hash)
}
return hashList, nil
}
func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) {
if !this.isReady {
return nil, nil
}
if count <= 0 {
count = 100
}
hashList, err = this.listLFUItems(count)
if err != nil {
return
}
if len(hashList) > count/2 {
return
}
// 不足补齐
olderHashList, err := this.listOlderItems(count - len(hashList))
if err != nil {
return nil, err
}
hashList = append(hashList, olderHashList...)
return
}
func (this *FileListDB) IncreaseHit(hash string) error {
var week = timeutil.Format("YW")
_, err := this.increaseHitStmt.Exec(hash, week, week, week, week)
return this.WrapError(err)
2022-03-15 18:32:39 +08:00
}
func (this *FileListDB) CleanPrefix(prefix string) error {
if !this.isReady {
return nil
}
var count = int64(10000)
var staleLife = 600 // TODO 需要可以设置
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
for {
2022-03-16 16:20:53 +08:00
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
2022-03-15 18:32:39 +08:00
if err != nil {
return this.WrapError(err)
2022-03-15 18:32:39 +08:00
}
affectedRows, err := result.RowsAffected()
if err != nil {
return err
}
if affectedRows < count {
return nil
}
}
}
func (this *FileListDB) CleanAll() error {
if !this.isReady {
return nil
}
_, err := this.deleteAllStmt.Exec()
if err != nil {
return this.WrapError(err)
2022-03-15 18:32:39 +08:00
}
return nil
}
func (this *FileListDB) Close() error {
this.isClosed = true
this.isReady = false
2022-03-16 16:20:53 +08:00
if this.existsByHashStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.existsByHashStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.insertStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.insertStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.selectByHashStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.selectByHashStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.deleteByHashStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.deleteByHashStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.statStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.statStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.purgeStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.purgeStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.deleteAllStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.deleteAllStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.listOlderItemsStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.listOlderItemsStmt.Close()
2022-03-16 16:20:53 +08:00
}
2022-03-15 18:32:39 +08:00
2022-03-16 16:20:53 +08:00
if this.insertHitStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.insertHitStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.increaseHitStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.increaseHitStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.deleteHitByHashStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.deleteHitByHashStmt.Close()
2022-03-16 16:20:53 +08:00
}
if this.lfuHitsStmt != nil {
2022-03-15 18:32:39 +08:00
_ = this.lfuHitsStmt.Close()
2022-03-16 16:20:53 +08:00
}
2022-03-15 18:32:39 +08:00
2022-03-16 16:20:53 +08:00
var errStrings []string
if this.readDB != nil {
err := this.readDB.Close()
if err != nil {
errStrings = append(errStrings, err.Error())
}
2022-03-15 18:32:39 +08:00
}
2022-03-16 16:20:53 +08:00
if this.writeDB != nil {
err := this.writeDB.Close()
if err != nil {
errStrings = append(errStrings, err.Error())
}
}
if this.writeBatch != nil {
this.writeBatch.Close()
}
2022-03-16 16:20:53 +08:00
if len(errStrings) == 0 {
return nil
}
return errors.New("close database failed: " + strings.Join(errStrings, ", "))
2022-03-15 18:32:39 +08:00
}
func (this *FileListDB) WrapError(err error) error {
if err == nil {
return nil
}
return errors.New(err.Error() + "(file: " + this.dbPath + ")")
}
2022-03-15 18:32:39 +08:00
// 初始化
func (this *FileListDB) initTables(times int) error {
{
// expiredAt - 过期时间,用来判断有无过期
// staleAt - 过时缓存最大时间,用来清理缓存
2022-03-16 16:20:53 +08:00
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
2022-03-15 18:32:39 +08:00
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"hash" varchar(32),
"key" varchar(1024),
"tag" varchar(64),
"headerSize" integer DEFAULT 0,
"bodySize" integer DEFAULT 0,
"metaSize" integer DEFAULT 0,
"expiredAt" integer DEFAULT 0,
"staleAt" integer DEFAULT 0,
"createdAt" integer DEFAULT 0,
"host" varchar(128),
"serverId" integer
);
DROP INDEX IF EXISTS "createdAt";
DROP INDEX IF EXISTS "expiredAt";
DROP INDEX IF EXISTS "serverId";
2022-03-15 18:32:39 +08:00
CREATE INDEX IF NOT EXISTS "staleAt"
ON "` + this.itemsTableName + `" (
"staleAt" ASC
);
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
ON "` + this.itemsTableName + `" (
"hash" ASC
);
`)
if err != nil {
// 尝试删除重建
if times < 3 {
2022-03-16 16:20:53 +08:00
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
2022-03-15 18:32:39 +08:00
if dropErr == nil {
return this.initTables(times + 1)
}
return this.WrapError(err)
2022-03-15 18:32:39 +08:00
}
return this.WrapError(err)
2022-03-15 18:32:39 +08:00
}
}
{
2022-03-16 16:20:53 +08:00
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
2022-03-15 18:32:39 +08:00
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"hash" varchar(32),
"week1Hits" integer DEFAULT 0,
"week2Hits" integer DEFAULT 0,
"week" varchar(6)
);
CREATE UNIQUE INDEX IF NOT EXISTS "hits_hash"
ON "` + this.hitsTableName + `" (
"hash" ASC
);
`)
if err != nil {
// 尝试删除重建
if times < 3 {
2022-03-16 16:20:53 +08:00
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.hitsTableName + `"`)
2022-03-15 18:32:39 +08:00
if dropErr == nil {
return this.initTables(times + 1)
}
return this.WrapError(err)
2022-03-15 18:32:39 +08:00
}
return this.WrapError(err)
2022-03-15 18:32:39 +08:00
}
}
return nil
}
func (this *FileListDB) listLFUItems(count int) (hashList []string, err error) {
rows, err := this.lfuHitsStmt.Query(count)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
var hash string
err = rows.Scan(&hash)
if err != nil {
return nil, err
}
hashList = append(hashList, hash)
}
return hashList, nil
}
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) {
rows, err := this.listOlderItemsStmt.Query(count)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
var hash string
err = rows.Scan(&hash)
if err != nil {
return nil, err
}
hashList = append(hashList, hash)
}
return hashList, nil
}