ledisdb/rpl/file_store.go

237 lines
4.3 KiB
Go
Raw Normal View History

2014-09-22 13:50:51 +04:00
package rpl
2014-09-15 18:42:25 +04:00
import (
2014-09-17 13:54:04 +04:00
"fmt"
2014-10-08 19:23:00 +04:00
"github.com/siddontang/go/hack"
"github.com/siddontang/go/ioutil2"
2014-09-24 05:46:36 +04:00
"github.com/siddontang/go/log"
2014-09-17 13:54:04 +04:00
"io/ioutil"
2014-09-15 18:42:25 +04:00
"os"
2014-09-17 13:54:04 +04:00
"path"
"strconv"
"strings"
2014-09-15 18:42:25 +04:00
"sync"
)
const (
2014-10-31 10:40:47 +03:00
defaultMaxLogFileSize = uint32(1024 * 1024 * 1024)
//why 4G, we can use uint32 as offset, reduce memory useage
maxLogFileSize = uint32(4*1024*1024*1024 - 1)
2014-09-15 18:42:25 +04:00
)
2014-09-17 13:54:04 +04:00
/*
2014-10-31 10:40:47 +03:00
File Store:
0000001.data
0000001.meta
0000002.data
0000002.meta
data: log1 data | log2 data | magic data
meta: log1 pos in data | log2 pos in data
2014-11-03 06:22:13 +03:00
log id can not be 0, we use here for magic log
if data has no magic data, it means that we don't close replication gracefully.
so we must repair the log data
magic data = log0 data
2014-10-31 10:40:47 +03:00
we use table to mangage data + meta pair
2014-11-03 06:22:13 +03:00
we must guarantee that the log id is monotonic increment strictly.
if log1's id is 1, log2 must be 2
2014-09-17 13:54:04 +04:00
*/
2014-09-15 18:42:25 +04:00
type FileStore struct {
2014-09-22 13:50:51 +04:00
LogStore
2014-09-15 18:42:25 +04:00
m sync.Mutex
2014-10-31 10:40:47 +03:00
maxFileSize uint32
2014-09-15 18:42:25 +04:00
first uint64
last uint64
2014-09-17 13:54:04 +04:00
logFile *os.File
logNames []string
nextLogIndex int64
indexName string
path string
2014-09-15 18:42:25 +04:00
}
func NewFileStore(path string) (*FileStore, error) {
s := new(FileStore)
if err := os.MkdirAll(path, 0755); err != nil {
return nil, err
}
2014-09-17 13:54:04 +04:00
s.path = path
2014-09-15 18:42:25 +04:00
s.maxFileSize = defaultMaxLogFileSize
s.first = 0
s.last = 0
2014-09-17 13:54:04 +04:00
s.logNames = make([]string, 0, 16)
if err := s.loadIndex(); err != nil {
return nil, err
}
2014-09-15 18:42:25 +04:00
return s, nil
}
2014-10-31 10:40:47 +03:00
func (s *FileStore) SetMaxFileSize(size uint32) {
2014-09-15 18:42:25 +04:00
s.maxFileSize = size
}
func (s *FileStore) GetLog(id uint64, log *Log) error {
2014-09-22 13:50:51 +04:00
panic("not implementation")
2014-09-15 18:42:25 +04:00
return nil
}
func (s *FileStore) FirstID() (uint64, error) {
2014-09-22 13:50:51 +04:00
panic("not implementation")
2014-09-15 18:42:25 +04:00
return 0, nil
}
func (s *FileStore) LastID() (uint64, error) {
2014-09-22 13:50:51 +04:00
panic("not implementation")
2014-09-15 18:42:25 +04:00
return 0, nil
}
func (s *FileStore) StoreLog(log *Log) error {
2014-09-22 13:50:51 +04:00
panic("not implementation")
2014-09-15 18:42:25 +04:00
return nil
}
2014-09-17 13:54:04 +04:00
func (s *FileStore) Purge(n uint64) error {
2014-09-22 13:50:51 +04:00
panic("not implementation")
2014-09-17 13:54:04 +04:00
return nil
}
2014-09-22 13:50:51 +04:00
func (s *FileStore) PuregeExpired(n int64) error {
panic("not implementation")
2014-09-15 18:42:25 +04:00
return nil
}
func (s *FileStore) Clear() error {
2014-09-22 13:50:51 +04:00
panic("not implementation")
2014-09-15 18:42:25 +04:00
return nil
}
func (s *FileStore) Close() error {
2014-09-22 13:50:51 +04:00
panic("not implementation")
2014-09-15 18:42:25 +04:00
return nil
}
2014-09-17 13:54:04 +04:00
func (s *FileStore) flushIndex() error {
data := strings.Join(s.logNames, "\n")
2014-10-08 19:23:00 +04:00
if err := ioutil2.WriteFileAtomic(s.indexName, hack.Slice(data), 0644); err != nil {
log.Error("flush index error %s", err.Error())
2014-09-17 13:54:04 +04:00
return err
}
return nil
}
func (s *FileStore) fileExists(name string) bool {
p := path.Join(s.path, name)
_, err := os.Stat(p)
return !os.IsNotExist(err)
}
func (s *FileStore) loadIndex() error {
s.indexName = path.Join(s.path, fmt.Sprintf("ledis-bin.index"))
if _, err := os.Stat(s.indexName); os.IsNotExist(err) {
//no index file, nothing to do
} else {
indexData, err := ioutil.ReadFile(s.indexName)
if err != nil {
return err
}
lines := strings.Split(string(indexData), "\n")
for _, line := range lines {
line = strings.Trim(line, "\r\n ")
if len(line) == 0 {
continue
}
if s.fileExists(line) {
s.logNames = append(s.logNames, line)
} else {
log.Info("log %s has not exists", line)
}
}
}
var err error
if len(s.logNames) == 0 {
s.nextLogIndex = 1
} else {
lastName := s.logNames[len(s.logNames)-1]
if s.nextLogIndex, err = strconv.ParseInt(path.Ext(lastName)[1:], 10, 64); err != nil {
log.Error("invalid logfile name %s", err.Error())
return err
}
//like mysql, if server restart, a new log will create
s.nextLogIndex++
}
return nil
}
func (s *FileStore) openNewLogFile() error {
var err error
lastName := s.formatLogFileName(s.nextLogIndex)
logPath := path.Join(s.path, lastName)
if s.logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY, 0644); err != nil {
log.Error("open new logfile error %s", err.Error())
return err
}
s.logNames = append(s.logNames, lastName)
if err = s.flushIndex(); err != nil {
return err
}
return nil
}
func (s *FileStore) checkLogFileSize() bool {
if s.logFile == nil {
return false
}
st, _ := s.logFile.Stat()
if st.Size() >= int64(s.maxFileSize) {
s.closeLog()
return true
}
return false
}
func (s *FileStore) closeLog() {
if s.logFile == nil {
return
}
s.nextLogIndex++
s.logFile.Close()
s.logFile = nil
}
func (s *FileStore) formatLogFileName(index int64) string {
return fmt.Sprintf("ledis-bin.%07d", index)
}