github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/shed/example_store_test.go (about) 1 // Copyleft 2018 The susy-graviton Authors 2 // This file is part of the susy-graviton library. 3 // 4 // The susy-graviton library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The susy-graviton library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MSRCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the susy-graviton library. If not, see <http://www.gnu.org/licenses/>. 16 17 package shed_test 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/binary" 23 "fmt" 24 "io/ioutil" 25 "log" 26 "os" 27 "time" 28 29 "github.com/susy-go/susy-graviton/swarm/shed" 30 "github.com/susy-go/susy-graviton/swarm/storage" 31 "github.com/syndtr/goleveldb/leveldb" 32 ) 33 34 // Store holds fields and indexes (including their encoding functions) 35 // and defines operations on them by composing data from them. 36 // It implements storage.ChunkStore interface. 37 // It is just an example without any support for parallel operations 38 // or real world implementation. 39 type Store struct { 40 db *shed.DB 41 42 // fields and indexes 43 schemaName shed.StringField 44 sizeCounter shed.Uint64Field 45 accessCounter shed.Uint64Field 46 retrievalIndex shed.Index 47 accessIndex shed.Index 48 gcIndex shed.Index 49 } 50 51 // New returns new Store. All fields and indexes are initialized 52 // and possible conflicts with schema from existing database is checked 53 // automatically. 54 func New(path string) (s *Store, err error) { 55 db, err := shed.NewDB(path, "") 56 if err != nil { 57 return nil, err 58 } 59 s = &Store{ 60 db: db, 61 } 62 // Identify current storage schema by arbitrary name. 63 s.schemaName, err = db.NewStringField("schema-name") 64 if err != nil { 65 return nil, err 66 } 67 // Global ever incrementing index of chunk accesses. 68 s.accessCounter, err = db.NewUint64Field("access-counter") 69 if err != nil { 70 return nil, err 71 } 72 // Index storing actual chunk address, data and store timestamp. 73 s.retrievalIndex, err = db.NewIndex("Address->StoreTimestamp|Data", shed.IndexFuncs{ 74 EncodeKey: func(fields shed.Item) (key []byte, err error) { 75 return fields.Address, nil 76 }, 77 DecodeKey: func(key []byte) (e shed.Item, err error) { 78 e.Address = key 79 return e, nil 80 }, 81 EncodeValue: func(fields shed.Item) (value []byte, err error) { 82 b := make([]byte, 8) 83 binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) 84 value = append(b, fields.Data...) 85 return value, nil 86 }, 87 DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { 88 e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) 89 e.Data = value[8:] 90 return e, nil 91 }, 92 }) 93 if err != nil { 94 return nil, err 95 } 96 // Index storing access timestamp for a particular address. 97 // It is needed in order to update gc index keys for iteration order. 98 s.accessIndex, err = db.NewIndex("Address->AccessTimestamp", shed.IndexFuncs{ 99 EncodeKey: func(fields shed.Item) (key []byte, err error) { 100 return fields.Address, nil 101 }, 102 DecodeKey: func(key []byte) (e shed.Item, err error) { 103 e.Address = key 104 return e, nil 105 }, 106 EncodeValue: func(fields shed.Item) (value []byte, err error) { 107 b := make([]byte, 8) 108 binary.BigEndian.PutUint64(b, uint64(fields.AccessTimestamp)) 109 return b, nil 110 }, 111 DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { 112 e.AccessTimestamp = int64(binary.BigEndian.Uint64(value)) 113 return e, nil 114 }, 115 }) 116 if err != nil { 117 return nil, err 118 } 119 // Index with keys ordered by access timestamp for garbage collection prioritization. 120 s.gcIndex, err = db.NewIndex("AccessTimestamp|StoredTimestamp|Address->nil", shed.IndexFuncs{ 121 EncodeKey: func(fields shed.Item) (key []byte, err error) { 122 b := make([]byte, 16, 16+len(fields.Address)) 123 binary.BigEndian.PutUint64(b[:8], uint64(fields.AccessTimestamp)) 124 binary.BigEndian.PutUint64(b[8:16], uint64(fields.StoreTimestamp)) 125 key = append(b, fields.Address...) 126 return key, nil 127 }, 128 DecodeKey: func(key []byte) (e shed.Item, err error) { 129 e.AccessTimestamp = int64(binary.BigEndian.Uint64(key[:8])) 130 e.StoreTimestamp = int64(binary.BigEndian.Uint64(key[8:16])) 131 e.Address = key[16:] 132 return e, nil 133 }, 134 EncodeValue: func(fields shed.Item) (value []byte, err error) { 135 return nil, nil 136 }, 137 DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { 138 return e, nil 139 }, 140 }) 141 if err != nil { 142 return nil, err 143 } 144 return s, nil 145 } 146 147 // Put stores the chunk and sets it store timestamp. 148 func (s *Store) Put(_ context.Context, ch storage.Chunk) (err error) { 149 return s.retrievalIndex.Put(shed.Item{ 150 Address: ch.Address(), 151 Data: ch.Data(), 152 StoreTimestamp: time.Now().UTC().UnixNano(), 153 }) 154 } 155 156 // Get retrieves a chunk with the provided address. 157 // It updates access and gc indexes by removing the previous 158 // items from them and adding new items as keys of index entries 159 // are changed. 160 func (s *Store) Get(_ context.Context, addr storage.Address) (c storage.Chunk, err error) { 161 batch := new(leveldb.Batch) 162 163 // Get the chunk data and storage timestamp. 164 item, err := s.retrievalIndex.Get(shed.Item{ 165 Address: addr, 166 }) 167 if err != nil { 168 if err == leveldb.ErrNotFound { 169 return nil, storage.ErrChunkNotFound 170 } 171 return nil, err 172 } 173 174 // Get the chunk access timestamp. 175 accessItem, err := s.accessIndex.Get(shed.Item{ 176 Address: addr, 177 }) 178 switch err { 179 case nil: 180 // Remove gc index entry if access timestamp is found. 181 err = s.gcIndex.DeleteInBatch(batch, shed.Item{ 182 Address: item.Address, 183 StoreTimestamp: accessItem.AccessTimestamp, 184 AccessTimestamp: item.StoreTimestamp, 185 }) 186 if err != nil { 187 return nil, err 188 } 189 case leveldb.ErrNotFound: 190 // Access timestamp is not found. Do not do anything. 191 // This is the firs get request. 192 default: 193 return nil, err 194 } 195 196 // Specify new access timestamp 197 accessTimestamp := time.Now().UTC().UnixNano() 198 199 // Put new access timestamp in access index. 200 err = s.accessIndex.PutInBatch(batch, shed.Item{ 201 Address: addr, 202 AccessTimestamp: accessTimestamp, 203 }) 204 if err != nil { 205 return nil, err 206 } 207 208 // Put new access timestamp in gc index. 209 err = s.gcIndex.PutInBatch(batch, shed.Item{ 210 Address: item.Address, 211 AccessTimestamp: accessTimestamp, 212 StoreTimestamp: item.StoreTimestamp, 213 }) 214 if err != nil { 215 return nil, err 216 } 217 218 // Increment access counter. 219 // Currently this information is not used anywhere. 220 _, err = s.accessCounter.IncInBatch(batch) 221 if err != nil { 222 return nil, err 223 } 224 225 // Write the batch. 226 err = s.db.WriteBatch(batch) 227 if err != nil { 228 return nil, err 229 } 230 231 // Return the chunk. 232 return storage.NewChunk(item.Address, item.Data), nil 233 } 234 235 // CollectGarbage is an example of index iteration. 236 // It provides no reliable garbage collection functionality. 237 func (s *Store) CollectGarbage() (err error) { 238 const maxTrashSize = 100 239 maxRounds := 10 // arbitrary number, needs to be calculated 240 241 // Run a few gc rounds. 242 for roundCount := 0; roundCount < maxRounds; roundCount++ { 243 var garbageCount int 244 // New batch for a new cg round. 245 trash := new(leveldb.Batch) 246 // Iterate through all index items and break when needed. 247 err = s.gcIndex.Iterate(func(item shed.Item) (stop bool, err error) { 248 // Remove the chunk. 249 err = s.retrievalIndex.DeleteInBatch(trash, item) 250 if err != nil { 251 return false, err 252 } 253 // Remove the element in gc index. 254 err = s.gcIndex.DeleteInBatch(trash, item) 255 if err != nil { 256 return false, err 257 } 258 // Remove the relation in access index. 259 err = s.accessIndex.DeleteInBatch(trash, item) 260 if err != nil { 261 return false, err 262 } 263 garbageCount++ 264 if garbageCount >= maxTrashSize { 265 return true, nil 266 } 267 return false, nil 268 }, nil) 269 if err != nil { 270 return err 271 } 272 if garbageCount == 0 { 273 return nil 274 } 275 err = s.db.WriteBatch(trash) 276 if err != nil { 277 return err 278 } 279 } 280 return nil 281 } 282 283 // GetSchema is an example of retrieveing the most simple 284 // string from a database field. 285 func (s *Store) GetSchema() (name string, err error) { 286 name, err = s.schemaName.Get() 287 if err == leveldb.ErrNotFound { 288 return "", nil 289 } 290 return name, err 291 } 292 293 // GetSchema is an example of storing the most simple 294 // string in a database field. 295 func (s *Store) PutSchema(name string) (err error) { 296 return s.schemaName.Put(name) 297 } 298 299 // Close closes the underlying database. 300 func (s *Store) Close() error { 301 return s.db.Close() 302 } 303 304 // Example_store constructs a simple storage implementation using shed package. 305 func Example_store() { 306 dir, err := ioutil.TempDir("", "ephemeral") 307 if err != nil { 308 log.Fatal(err) 309 } 310 defer os.RemoveAll(dir) 311 312 s, err := New(dir) 313 if err != nil { 314 log.Fatal(err) 315 } 316 defer s.Close() 317 318 ch := storage.GenerateRandomChunk(1024) 319 err = s.Put(context.Background(), ch) 320 if err != nil { 321 log.Fatal(err) 322 } 323 324 got, err := s.Get(context.Background(), ch.Address()) 325 if err != nil { 326 log.Fatal(err) 327 } 328 329 fmt.Println(bytes.Equal(got.Data(), ch.Data())) 330 331 //Output: true 332 }