017-optimize-reduce-disk-writes.patch 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. From 00366b224b2e28861b80f677e8aa604c5d08dae3 Mon Sep 17 00:00:00 2001
  2. From: Kelo <[email protected]>
  3. Date: Sat, 29 Oct 2022 16:27:26 +0800
  4. Subject: [PATCH] optimize: reduce disk writes
  5. ---
  6. service/db/boltdb.go | 43 +++++++++++++++++++++++++++++++----
  7. service/db/listOp.go | 48 +++++++++++++++++++++------------------
  8. service/db/plainOp.go | 52 ++++++++++++++++++++++++-------------------
  9. service/db/setOp.go | 20 +++++++++--------
  10. 4 files changed, 105 insertions(+), 58 deletions(-)
  11. --- a/db/boltdb.go
  12. +++ b/db/boltdb.go
  13. @@ -1,13 +1,14 @@
  14. package db
  15. import (
  16. - "go.etcd.io/bbolt"
  17. - "github.com/v2rayA/v2rayA/conf"
  18. - "github.com/v2rayA/v2rayA/pkg/util/copyfile"
  19. - "github.com/v2rayA/v2rayA/pkg/util/log"
  20. "os"
  21. "path/filepath"
  22. "sync"
  23. +
  24. + "github.com/v2rayA/v2rayA/conf"
  25. + "github.com/v2rayA/v2rayA/pkg/util/copyfile"
  26. + "github.com/v2rayA/v2rayA/pkg/util/log"
  27. + "go.etcd.io/bbolt"
  28. )
  29. var once sync.Once
  30. @@ -46,3 +47,37 @@ func DB() *bbolt.DB {
  31. once.Do(initDB)
  32. return db
  33. }
  34. +
  35. +// The function should return a dirty flag.
  36. +// If the dirty flag is true and there is no error then the transaction is commited.
  37. +// Otherwise, the transaction is rolled back.
  38. +func Transaction(db *bbolt.DB, fn func(*bbolt.Tx) (bool, error)) error {
  39. + tx, err := db.Begin(true)
  40. + if err != nil {
  41. + return err
  42. + }
  43. + defer tx.Rollback()
  44. + dirty, err := fn(tx)
  45. + if err != nil {
  46. + _ = tx.Rollback()
  47. + return err
  48. + }
  49. + if !dirty {
  50. + return nil
  51. + }
  52. + return tx.Commit()
  53. +}
  54. +
  55. +// If the bucket does not exist, the dirty flag is setted
  56. +func CreateBucketIfNotExists(tx *bbolt.Tx, name []byte, dirty *bool) (*bbolt.Bucket, error) {
  57. + bkt := tx.Bucket(name)
  58. + if bkt != nil {
  59. + return bkt, nil
  60. + }
  61. + bkt, err := tx.CreateBucket(name)
  62. + if err != nil {
  63. + return nil, err
  64. + }
  65. + *dirty = true
  66. + return bkt, nil
  67. +}
  68. --- a/db/listOp.go
  69. +++ b/db/listOp.go
  70. @@ -2,13 +2,14 @@ package db
  71. import (
  72. "fmt"
  73. - "go.etcd.io/bbolt"
  74. - jsoniter "github.com/json-iterator/go"
  75. - "github.com/tidwall/gjson"
  76. - "github.com/tidwall/sjson"
  77. "reflect"
  78. "sort"
  79. "strconv"
  80. +
  81. + jsoniter "github.com/json-iterator/go"
  82. + "github.com/tidwall/gjson"
  83. + "github.com/tidwall/sjson"
  84. + "go.etcd.io/bbolt"
  85. )
  86. func ListSet(bucket string, key string, index int, val interface{}) (err error) {
  87. @@ -31,20 +32,21 @@ func ListSet(bucket string, key string,
  88. }
  89. func ListGet(bucket string, key string, index int) (b []byte, err error) {
  90. - err = DB().Update(func(tx *bbolt.Tx) error {
  91. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  92. - return err
  93. + err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  94. + dirty := false
  95. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  96. + return dirty, err
  97. } else {
  98. v := bkt.Get([]byte(key))
  99. if v == nil {
  100. - return fmt.Errorf("ListGet: can't get element from an empty list")
  101. + return dirty, fmt.Errorf("ListGet: can't get element from an empty list")
  102. }
  103. r := gjson.GetBytes(v, strconv.Itoa(index))
  104. if r.Exists() {
  105. b = []byte(r.Raw)
  106. - return nil
  107. + return dirty, nil
  108. } else {
  109. - return fmt.Errorf("ListGet: no such element")
  110. + return dirty, fmt.Errorf("ListGet: no such element")
  111. }
  112. }
  113. })
  114. @@ -79,24 +81,25 @@ func ListAppend(bucket string, key strin
  115. }
  116. func ListGetAll(bucket string, key string) (list [][]byte, err error) {
  117. - err = DB().Update(func(tx *bbolt.Tx) error {
  118. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  119. - return err
  120. + err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  121. + dirty := false
  122. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  123. + return dirty, err
  124. } else {
  125. b := bkt.Get([]byte(key))
  126. if b == nil {
  127. - return nil
  128. + return dirty, nil
  129. }
  130. parsed := gjson.ParseBytes(b)
  131. if !parsed.IsArray() {
  132. - return fmt.Errorf("ListGetAll: is not array")
  133. + return dirty, fmt.Errorf("ListGetAll: is not array")
  134. }
  135. results := parsed.Array()
  136. for _, r := range results {
  137. list = append(list, []byte(r.Raw))
  138. }
  139. }
  140. - return nil
  141. + return dirty, nil
  142. })
  143. return list, err
  144. }
  145. @@ -143,21 +146,22 @@ func ListRemove(bucket, key string, inde
  146. }
  147. func ListLen(bucket string, key string) (length int, err error) {
  148. - err = DB().Update(func(tx *bbolt.Tx) error {
  149. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  150. - return err
  151. + err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  152. + dirty := false
  153. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  154. + return dirty, err
  155. } else {
  156. b := bkt.Get([]byte(key))
  157. if b == nil {
  158. - return nil
  159. + return dirty, nil
  160. }
  161. parsed := gjson.ParseBytes(b)
  162. if !parsed.IsArray() {
  163. - return fmt.Errorf("ListLen: is not array")
  164. + return dirty, fmt.Errorf("ListLen: is not array")
  165. }
  166. length = len(parsed.Array())
  167. }
  168. - return nil
  169. + return dirty, nil
  170. })
  171. return length, err
  172. }
  173. --- a/db/plainOp.go
  174. +++ b/db/plainOp.go
  175. @@ -2,50 +2,54 @@ package db
  176. import (
  177. "fmt"
  178. - "go.etcd.io/bbolt"
  179. +
  180. jsoniter "github.com/json-iterator/go"
  181. "github.com/v2rayA/v2rayA/common"
  182. "github.com/v2rayA/v2rayA/pkg/util/log"
  183. + "go.etcd.io/bbolt"
  184. )
  185. func Get(bucket string, key string, val interface{}) (err error) {
  186. - return DB().Update(func(tx *bbolt.Tx) error {
  187. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  188. - return err
  189. + return Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  190. + dirty := false
  191. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  192. + return dirty, err
  193. } else {
  194. if v := bkt.Get([]byte(key)); v == nil {
  195. - return fmt.Errorf("Get: key is not found")
  196. + return dirty, fmt.Errorf("Get: key is not found")
  197. } else {
  198. - return jsoniter.Unmarshal(v, val)
  199. + return dirty, jsoniter.Unmarshal(v, val)
  200. }
  201. }
  202. })
  203. }
  204. func GetRaw(bucket string, key string) (b []byte, err error) {
  205. - err = DB().Update(func(tx *bbolt.Tx) error {
  206. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  207. - return err
  208. + err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  209. + dirty := false
  210. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  211. + return dirty, err
  212. } else {
  213. v := bkt.Get([]byte(key))
  214. if v == nil {
  215. - return fmt.Errorf("GetRaw: key is not found")
  216. + return dirty, fmt.Errorf("GetRaw: key is not found")
  217. }
  218. b = common.BytesCopy(v)
  219. - return nil
  220. + return dirty, nil
  221. }
  222. })
  223. return b, err
  224. }
  225. func Exists(bucket string, key string) (exists bool) {
  226. - if err := DB().Update(func(tx *bbolt.Tx) error {
  227. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  228. - return err
  229. + if err := Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  230. + dirty := false
  231. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  232. + return dirty, err
  233. } else {
  234. v := bkt.Get([]byte(key))
  235. exists = v != nil
  236. - return nil
  237. + return dirty, nil
  238. }
  239. }); err != nil {
  240. log.Warn("%v", err)
  241. @@ -55,23 +59,25 @@ func Exists(bucket string, key string) (
  242. }
  243. func GetBucketLen(bucket string) (length int, err error) {
  244. - err = DB().Update(func(tx *bbolt.Tx) error {
  245. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  246. - return err
  247. + err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  248. + dirty := false
  249. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  250. + return dirty, err
  251. } else {
  252. length = bkt.Stats().KeyN
  253. }
  254. - return nil
  255. + return dirty, nil
  256. })
  257. return length, err
  258. }
  259. func GetBucketKeys(bucket string) (keys []string, err error) {
  260. - err = DB().Update(func(tx *bbolt.Tx) error {
  261. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  262. - return err
  263. + err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  264. + dirty := false
  265. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  266. + return dirty, err
  267. } else {
  268. - return bkt.ForEach(func(k, v []byte) error {
  269. + return dirty, bkt.ForEach(func(k, v []byte) error {
  270. keys = append(keys, string(k))
  271. return nil
  272. })
  273. --- a/db/setOp.go
  274. +++ b/db/setOp.go
  275. @@ -4,8 +4,9 @@ import (
  276. "bytes"
  277. "crypto/sha256"
  278. "encoding/gob"
  279. - "go.etcd.io/bbolt"
  280. +
  281. "github.com/v2rayA/v2rayA/common"
  282. + "go.etcd.io/bbolt"
  283. )
  284. type set map[[32]byte]interface{}
  285. @@ -28,26 +29,27 @@ func toSha256(val interface{}) (hash [32
  286. }
  287. func setOp(bucket string, key string, f func(m set) (readonly bool, err error)) (err error) {
  288. - return DB().Update(func(tx *bbolt.Tx) error {
  289. - if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
  290. - return err
  291. + return Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
  292. + dirty := false
  293. + if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
  294. + return dirty, err
  295. } else {
  296. var m set
  297. v := bkt.Get([]byte(key))
  298. if v == nil {
  299. m = make(set)
  300. } else if err := gob.NewDecoder(bytes.NewReader(v)).Decode(&m); err != nil {
  301. - return err
  302. + return dirty, err
  303. }
  304. if readonly, err := f(m); err != nil {
  305. - return err
  306. + return dirty, err
  307. } else if readonly {
  308. - return nil
  309. + return dirty, nil
  310. }
  311. if b, err := common.ToBytes(m); err != nil {
  312. - return err
  313. + return dirty, err
  314. } else {
  315. - return bkt.Put([]byte(key), b)
  316. + return true, bkt.Put([]byte(key), b)
  317. }
  318. }
  319. })