github.com/matrixorigin/matrixone@v0.7.0/pkg/fileservice/disk_cache.go (about) 1 // Copyright 2022 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fileservice 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "io" 22 "os" 23 "path/filepath" 24 "sync" 25 "sync/atomic" 26 27 "github.com/matrixorigin/matrixone/pkg/util/trace" 28 ) 29 30 //TODO LRU 31 32 type DiskCache struct { 33 capacity int64 34 path string 35 stats *CacheStats 36 fileExists sync.Map 37 } 38 39 func NewDiskCache(path string, capacity int64) (*DiskCache, error) { 40 err := os.MkdirAll(path, 0755) 41 if err != nil { 42 return nil, err 43 } 44 return &DiskCache{ 45 capacity: capacity, 46 path: path, 47 stats: new(CacheStats), 48 }, nil 49 } 50 51 var _ Cache = new(DiskCache) 52 53 func (d *DiskCache) Read( 54 ctx context.Context, 55 vector *IOVector, 56 ) ( 57 err error, 58 ) { 59 _, span := trace.Start(ctx, "DiskCache.Read") 60 defer span.End() 61 62 numHit := 0 63 defer func() { 64 if d.stats != nil { 65 atomic.AddInt64(&d.stats.NumRead, int64(len(vector.Entries))) 66 atomic.AddInt64(&d.stats.NumHit, int64(numHit)) 67 } 68 }() 69 70 for i, entry := range vector.Entries { 71 if entry.done { 72 continue 73 } 74 if entry.Size < 0 { 75 // ignore size unknown entry 76 continue 77 } 78 79 linkPath := d.entryLinkPath(vector, entry) 80 file, err := os.Open(linkPath) 81 if err != nil { 82 // ignore error 83 continue 84 } 85 defer file.Close() 86 87 if _, err := file.Seek(entry.Offset, 0); err != nil { 88 // ignore error 89 continue 90 } 91 data := make([]byte, entry.Size) 92 if _, err := io.ReadFull(file, data); err != nil { 93 // ignore error 94 continue 95 } 96 97 entry.Data = data 98 if entry.WriterForRead != nil { 99 if _, err := entry.WriterForRead.Write(data); err != nil { 100 return err 101 } 102 } 103 if entry.ReadCloserForRead != nil { 104 *entry.ReadCloserForRead = io.NopCloser(bytes.NewReader(data)) 105 } 106 if err := entry.setObjectFromData(); err != nil { 107 return err 108 } 109 110 entry.done = true 111 112 vector.Entries[i] = entry 113 numHit++ 114 } 115 116 return nil 117 } 118 119 func (d *DiskCache) Update( 120 ctx context.Context, 121 vector *IOVector, 122 ) ( 123 err error, 124 ) { 125 126 for _, entry := range vector.Entries { 127 if len(entry.Data) == 0 { 128 // no data 129 continue 130 } 131 if entry.Size < 0 { 132 // ignore size unknown entry 133 continue 134 } 135 136 linkPath := d.entryLinkPath(vector, entry) 137 if _, ok := d.fileExists.Load(linkPath); ok { 138 // already exists 139 continue 140 } 141 _, err := os.Stat(linkPath) 142 if err == nil { 143 // file exists 144 d.fileExists.Store(linkPath, true) 145 continue 146 } 147 148 // write data 149 dataPath := d.entryDataPath(vector, entry) 150 err = os.MkdirAll(filepath.Dir(dataPath), 0755) 151 if err != nil { 152 return err 153 } 154 file, err := os.OpenFile(dataPath, os.O_RDWR|os.O_CREATE, 0644) 155 if err != nil { 156 return err 157 } 158 defer file.Close() 159 160 n, err := file.WriteAt(entry.Data, entry.Offset) 161 if err != nil { 162 return err 163 } 164 if int64(n) != entry.Size { 165 return io.ErrShortWrite 166 } 167 168 // link 169 if err := os.Symlink(dataPath, linkPath); err != nil { 170 if os.IsExist(err) { 171 // ok 172 } else { 173 return err 174 } 175 } 176 177 d.fileExists.Store(linkPath, true) 178 } 179 180 return nil 181 } 182 183 func (d *DiskCache) Flush() { 184 //TODO 185 } 186 187 func (d *DiskCache) CacheStats() *CacheStats { 188 return d.stats 189 } 190 191 func (d *DiskCache) entryLinkPath(vector *IOVector, entry IOEntry) string { 192 if entry.Size < 0 { 193 panic("should not cache size -1 entry") 194 } 195 return filepath.Join( 196 d.path, 197 toOSPath(vector.FilePath), 198 fmt.Sprintf("%d-%d", entry.Offset, entry.Size), 199 ) 200 } 201 202 func (d *DiskCache) entryDataPath(vector *IOVector, entry IOEntry) string { 203 return filepath.Join( 204 d.path, 205 toOSPath(vector.FilePath), 206 "data", 207 ) 208 }