Skip to content

Commit

Permalink
vfs: introduce --atime-mode option to control atime update behavior (#…
Browse files Browse the repository at this point in the history
…3521)

* vfs: introduce --atime-mode option to control atime update behavior

Currently juicefs doesn't update atime on access, so introduce
--atime-mode option to select how to update atime. And support three
modes:

- noatime: don't update atime
- relatime: update atime relative to motify or change time
- strictatime: always update atime

Fixes: #3240
Signed-off-by: Eryu Guan <[email protected]>
  • Loading branch information
eryugey authored May 9, 2023
1 parent c6cb6bf commit c9b7388
Show file tree
Hide file tree
Showing 14 changed files with 514 additions and 8 deletions.
5 changes: 5 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ func clientFlags() []cli.Flag {
Name: "subdir",
Usage: "mount a sub-directory as root",
},
&cli.StringFlag{
Name: "atime-mode",
Value: "noatime",
Usage: "when to update atime, supported mode includes: noatime, relatime, strictatime",
},
}
}

Expand Down
7 changes: 7 additions & 0 deletions cmd/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,13 @@ func getMetaConf(c *cli.Context, mp string, readOnly bool) *meta.Config {
conf.Heartbeat = duration(c.String("heartbeat"))
conf.MountPoint = mp
conf.Subdir = c.String("subdir")

atimeMode := c.String("atime-mode")
if atimeMode != meta.RelAtime && atimeMode != meta.StrictAtime && atimeMode != meta.NoAtime {
logger.Warnf("unknown atime-mode \"%s\", changed to %s", atimeMode, meta.NoAtime)
atimeMode = meta.NoAtime
}
conf.AtimeMode = atimeMode
return conf
}

Expand Down
5 changes: 4 additions & 1 deletion docs/en/development/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ type Attr struct {

There are a few fields that need clarification.

- Atime/Atimensec: set only when the file is created and when `SetAttr` is actively called, while accessing and modifying the file usually does not affect the Atime value
- Atime/Atimensec: currently support three modes
- noatime: set only when the file is created and when `SetAttr` is actively called, while accessing and modifying the file usually does not affect the Atime value, this is the default behavior
- relatime: update inode access times relative to modify or change time. Access time is only updated if the previous access time was earlier than the current modify or change time, or the file's last access time is always updated if it is more than 1 day old
- strictatime: always update atime on access
- Nlink
- Directory file: initial value is 2 ('.' and '..'), add 1 for each subdirectory
- Other files: initial value is 1, add 1 for each hard link created
Expand Down
3 changes: 3 additions & 0 deletions docs/en/reference/command_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ interval (in seconds) to send heartbeat; it's recommended that all clients use t
`--no-bgjob`<br />
disable background jobs (clean-up, backup, etc.) (default: false)

`--atime-mode value`<br />
control atime behavior, support 3 modes, `noatime`, `relatime`, `strictatime`, default to `noatime`

#### Examples

```bash
Expand Down
5 changes: 4 additions & 1 deletion docs/zh_cn/development/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ type Attr struct {

其中几个需要说明的字段:

- Atime/Atimensec:仅在文件创建和主动调用 `SetAttr` 时设置,平时访问与修改文件不影响 Atime 值
- Atime/Atimensec:目前支持三种模式
- noatime: 仅在文件创建和主动调用 `SetAttr` 时设置,平时访问与修改文件不影响 Atime 值,这是默认行为
- relatime: Mtime 或者 Ctime 比 Atime 新时,或者 Atime 超过 24 小时没有更新时更新 Atime
- strictatime: 一直更新 Atime
- Nlink:
- 目录文件:初始值为 2('.' 和 '..'),每有一个子目录 Nlink 值加 1
- 其他文件:初始值为 1,每创建一个硬链接 Nlink 值加 1
Expand Down
3 changes: 3 additions & 0 deletions docs/zh_cn/reference/command_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ Consul 注册中心地址 (默认:"127.0.0.1:8500")
`--no-bgjob`<br />
禁用后台作业(清理、备份等)(默认:false)

`--atime-mode value`<br />
控制如何更新 atime,支持 3 种模式,`noatime``relatime``strictatime`,默认使用 `noatime`

#### 示例

```shell
Expand Down
23 changes: 20 additions & 3 deletions pkg/meta/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type engine interface {
scanPendingFiles(Context, pendingFileScan) error

GetSession(sid uint64, detail bool) (*Session, error)
touchAtime(ctx Context, inode Ino, attr *Attr) syscall.Errno
}

type trashSliceScan func(ss []Slice, ts int64) (clean bool, err error)
Expand Down Expand Up @@ -1494,10 +1495,19 @@ func (m *baseMeta) Rename(ctx Context, parentSrc Ino, nameSrc string, parentDst
return st
}

func (m *baseMeta) Open(ctx Context, inode Ino, flags uint32, attr *Attr) syscall.Errno {
func (m *baseMeta) Open(ctx Context, inode Ino, flags uint32, attr *Attr) (rerr syscall.Errno) {
if m.conf.ReadOnly && flags&(syscall.O_WRONLY|syscall.O_RDWR|syscall.O_TRUNC|syscall.O_APPEND) != 0 {
return syscall.EROFS
}

defer func() {
if rerr == 0 {
if err := m.en.touchAtime(ctx, inode, attr); err != 0 {
logger.Warnf("open %v update atime: %s", inode, err)
}
}
}()

if m.conf.OpenCache > 0 && m.of.OpenCheck(inode, attr) {
return 0
}
Expand Down Expand Up @@ -1561,9 +1571,16 @@ func (m *baseMeta) Close(ctx Context, inode Ino) syscall.Errno {
return 0
}

func (m *baseMeta) Readdir(ctx Context, inode Ino, plus uint8, entries *[]*Entry) syscall.Errno {
inode = m.checkRoot(inode)
func (m *baseMeta) Readdir(ctx Context, inode Ino, plus uint8, entries *[]*Entry) (rerr syscall.Errno) {
var attr Attr
defer func() {
if rerr == 0 {
if err := m.en.touchAtime(ctx, inode, &attr); err != 0 {
logger.Warnf("readdir %v update atime: %s", inode, err)
}
}
}()
inode = m.checkRoot(inode)
if err := m.GetAttr(ctx, inode, &attr); err != 0 {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/meta/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Config struct {
Heartbeat time.Duration
MountPoint string
Subdir string
AtimeMode string
}

func DefaultConf() *Config {
Expand Down
59 changes: 58 additions & 1 deletion pkg/meta/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2126,7 +2126,14 @@ func (m *redisMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
return err
}

func (m *redisMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
func (m *redisMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) (rerr syscall.Errno) {
defer func() {
if rerr == 0 {
if err := m.touchAtime(ctx, inode, nil); err != 0 {
logger.Warnf("read %v update atime: %s", inode, err)
}
}
}()
f := m.of.find(inode)
if f != nil {
f.RLock()
Expand Down Expand Up @@ -4274,3 +4281,53 @@ func (m *redisMeta) doAttachDirNode(ctx Context, parent Ino, dstIno Ino, name st
return err
}, m.inodeKey(parent), m.entryKey(parent)))
}

// caller makes sure inode is not special inode.
func (m *redisMeta) touchAtime(ctx Context, ino Ino, cur *Attr) (rerr syscall.Errno) {
if (m.conf.AtimeMode != StrictAtime && m.conf.AtimeMode != RelAtime) || m.conf.ReadOnly {
return 0
}

var attr *Attr
newAttr := &Attr{}
if cur != nil {
attr = cur
} else if m.of.Check(ino, newAttr) {
attr = newAttr
}

now := time.Now()
if attr != nil && !m.atimeNeedsUpdate(attr, now) {
return 0
}

updated := false
defer func() {
if rerr == 0 && m.of.IsOpen(ino) && updated {
m.of.Update(ino, attr)
}
}()

return errno(m.txn(ctx, func(tx *redis.Tx) error {
if attr == nil {
attr = newAttr
}
a, err := tx.Get(ctx, m.inodeKey(ino)).Bytes()
if err != nil {
return err
}
m.parseAttr(a, attr)

if !m.atimeNeedsUpdate(attr, now) {
return nil
}
attr.Atime = now.Unix()
attr.Atimensec = uint32(now.Nanosecond())
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Set(ctx, m.inodeKey(ino), m.marshal(attr), 0)
return nil
})
updated = true
return err
}, m.inodeKey(ino)))
}
58 changes: 57 additions & 1 deletion pkg/meta/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -2062,7 +2062,14 @@ func (m *dbMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
return err
}

func (m *dbMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
func (m *dbMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) (rerr syscall.Errno) {
defer func() {
if rerr == 0 {
if err := m.touchAtime(ctx, inode, nil); err != 0 {
logger.Warnf("read %v update atime: %s", inode, err)
}
}
}()
f := m.of.find(inode)
if f != nil {
f.RLock()
Expand Down Expand Up @@ -3910,3 +3917,52 @@ func (m *dbMeta) doAttachDirNode(ctx Context, parent Ino, inode Ino, name string
return err
}, parent))
}

func (m *dbMeta) touchAtime(ctx Context, ino Ino, cur *Attr) (rerr syscall.Errno) {
if (m.conf.AtimeMode != StrictAtime && m.conf.AtimeMode != RelAtime) || m.conf.ReadOnly {
return 0
}

var curNode = node{Inode: ino}
var attr *Attr
newAttr := &Attr{}
if cur != nil {
attr = cur
} else if m.of.Check(ino, newAttr) {
attr = newAttr
}

now := time.Now()
if attr != nil && !m.atimeNeedsUpdate(attr, now) {
return 0
}

updated := false
defer func() {
if rerr == 0 && m.of.IsOpen(ino) && updated {
m.of.Update(ino, attr)
}
}()

return errno(m.txn(func(s *xorm.Session) error {
if attr == nil {
attr = newAttr
}
ok, err := s.ForUpdate().Get(&curNode)
if err != nil {
return err
}
if !ok {
return syscall.ENOENT
}
m.parseAttr(&curNode, attr)

if !m.atimeNeedsUpdate(attr, now) {
return nil
}
curNode.Atime = now.Unix()*1e6 + int64(now.Nanosecond())/1e3
_, err = s.Cols("atime").Update(&curNode, &node{Inode: ino})
updated = true
return err
}))
}
55 changes: 54 additions & 1 deletion pkg/meta/tkv.go
Original file line number Diff line number Diff line change
Expand Up @@ -1816,7 +1816,14 @@ func (m *kvMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
return err
}

func (m *kvMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
func (m *kvMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) (rerr syscall.Errno) {
defer func() {
if rerr == 0 {
if err := m.touchAtime(ctx, inode, nil); err != 0 {
logger.Warnf("read %v update atime: %s", inode, err)
}
}
}()
f := m.of.find(inode)
if f != nil {
f.RLock()
Expand Down Expand Up @@ -3396,3 +3403,49 @@ func (m *kvMeta) doAttachDirNode(ctx Context, parent Ino, inode Ino, name string
return nil
}, parent))
}

func (m *kvMeta) touchAtime(_ctx Context, ino Ino, cur *Attr) (rerr syscall.Errno) {
if (m.conf.AtimeMode != StrictAtime && m.conf.AtimeMode != RelAtime) || m.conf.ReadOnly {
return 0
}

var attr *Attr
newAttr := &Attr{}
if cur != nil {
attr = cur
} else if m.of.Check(ino, newAttr) {
attr = newAttr
}

now := time.Now()
if attr != nil && !m.atimeNeedsUpdate(attr, now) {
return 0
}

updated := false
defer func() {
if rerr == 0 && m.of.IsOpen(ino) && updated {
m.of.Update(ino, attr)
}
}()

return errno(m.client.txn(func(tx *kvTxn) error {
if attr == nil {
attr = newAttr
}
a := tx.get(m.inodeKey(ino))
if a == nil {
return syscall.ENOENT
}
m.parseAttr(a, attr)

if !m.atimeNeedsUpdate(attr, now) {
return nil
}
attr.Atime = now.Unix()
attr.Atimensec = uint32(now.Nanosecond())
tx.set(m.inodeKey(ino), m.marshal(attr))
updated = true
return nil
}, 0))
}
34 changes: 34 additions & 0 deletions pkg/meta/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ const (
CLONE_MODE_CAN_OVERWRITE = 0x01
CLONE_MODE_PRESERVE_ATTR = 0x02
CLONE_MODE_PRESERVE_HARDLINKS = 0x08

// atime mode
NoAtime = "noatime"
RelAtime = "relatime"
StrictAtime = "strictatime"
)

type msgCallbacks struct {
Expand Down Expand Up @@ -553,3 +558,32 @@ func (m *baseMeta) getTreeSummary(ctx Context, tree *TreeSummary, depth, topN ui
}
return 0
}

func (m *baseMeta) atimeNeedsUpdate(attr *Attr, now time.Time) bool {
if m.conf.AtimeMode == RelAtime && relatimeNeedUpdate(attr, now) {
return true
}

atime := time.Unix(attr.Atime, int64(attr.Atimensec))
// update atime only for > 1 second accesses
return (m.conf.AtimeMode == StrictAtime) && (now.Sub(atime) > time.Second)
}

// With relative atime, only update atime if the previous atime is earlier than either the ctime or
// mtime or if at least a day has passed since the last atime update.
func relatimeNeedUpdate(attr *Attr, now time.Time) bool {
atime := time.Unix(attr.Atime, int64(attr.Atimensec))

// Is mtime younger than atime? If yes, update atime
if time.Unix(attr.Mtime, int64(attr.Mtimensec)).After(atime) {
return true
}

// Is ctime younger than atime? If yes, update atime
if time.Unix(attr.Ctime, int64(attr.Ctimensec)).After(atime) {
return true
}

// Is the previous atime value older than a day? If yes, update atime
return now.Sub(atime) > 24*time.Hour
}
Loading

0 comments on commit c9b7388

Please sign in to comment.