Repository: borgbackup/borg

Issue:
Consider making `--no-atime` the default
The [docs already mention](https://borgbackup.readthedocs.io/en/stable/usage/notes.html#noatime-noctime) the problem that storing `atime` can create:

> it might also affect metadata stream deduplication: if only this timestamp changes between backups and is stored into the metadata stream, the metadata stream chunks won’t deduplicate just because of that.

What the docs don't mention is that on systems that don't support `O_NOATIME` (e.g. macOS), every single file will have a modified `atime` by virtue of borg scanning it. This means that the metadata stream will get almost no deduplication at all, resulting in large archive sizes even when ~no data has changed.

Additionally, the stored `atime` values won't really be useful, since they'll just reflect the time of the backup.

So, I think it makes sense for `--noatime` to be the default (with a `--atime` flag to opt-in), at least on systems where `O_NOATIME` is not available. (But I'd lean towards making it the default everywhere, since it's unclear that the value of storing it outweighs the downside for the average user.)

There of course could be suitable compatibility measures, like keeping the old default for existing repos.



Discussion:
I don't want to make the default of a setting dependent on presence of a system feature.
What borg does should be determined by the options given (if possible).

I agree that noatime behaviour is likely what many users want, so borg 1.2 might be a good time to change the default and add a new option. Opinions?
> I don't want to make the default of a setting dependent on presence of a system feature.
What borg does should be determined by the options given (if possible).

Agreed (I was mostly hedging in case there were reasons for not changing it everywhere).

> I agree that noatime behaviour is likely what many users want, so borg 1.2 might be a good time to change the default and add a new option

Yep, that'd be perfect.

Agree on --noatime being the default (I am already using it).

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -561,11 +561,11 @@ def create_inner(archive, cache, fso):
                        cache_mode=args.files_cache_mode) as cache:
                 archive = Archive(repository, key, manifest, args.location.archive, cache=cache,
                                   create=True, checkpoint_interval=args.checkpoint_interval,
-                                  numeric_owner=args.numeric_owner, noatime=args.noatime, noctime=args.noctime,
+                                  numeric_owner=args.numeric_owner, noatime=not args.atime, noctime=args.noctime,
                                   progress=args.progress,
                                   chunker_params=args.chunker_params, start=t0, start_monotonic=t0_monotonic,
                                   log_json=args.log_json)
-                metadata_collector = MetadataCollector(noatime=args.noatime, noctime=args.noctime,
+                metadata_collector = MetadataCollector(noatime=not args.atime, noctime=args.noctime,
                     nobsdflags=args.nobsdflags, numeric_owner=args.numeric_owner, nobirthtime=args.nobirthtime)
                 cp = ChunksProcessor(cache=cache, key=key,
                     add_item=archive.add_item, write_checkpoint=archive.write_checkpoint,
@@ -2332,6 +2332,7 @@ def do_subcommand_help(self, parser, args):
     def preprocess_args(self, args):
         deprecations = [
             # ('--old', '--new' or None, 'Warning: "--old" has been deprecated. Use "--new" instead.'),
+            ('--noatime', None, 'Warning: "--noatime" has been deprecated because it is the default now.')
         ]
         for i, arg in enumerate(args[:]):
             for old_name, new_name, warning in deprecations:
@@ -3014,8 +3015,12 @@ def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
                               help='stay in the same file system and do not store mount points of other file systems')
         fs_group.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
                               help='only store numeric user and group identifiers')
+        # --noatime is the default now and the flag is deprecated. args.noatime is not used any more.
+        # use --atime if you want to store the atime (default behaviour before borg 1.2.0a7)..
         fs_group.add_argument('--noatime', dest='noatime', action='store_true',
                               help='do not store atime into archive')
+        fs_group.add_argument('--atime', dest='atime', action='store_true',
+                              help='do store atime into archive')
         fs_group.add_argument('--noctime', dest='noctime', action='store_true',
                               help='do not store ctime into archive')
         fs_group.add_argument('--nobirthtime', dest='nobirthtime', action='store_true',
diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py
--- a/src/borg/testsuite/archiver.py
+++ b/src/borg/testsuite/archiver.py
@@ -501,7 +501,7 @@ def has_noatime(some_file):
         have_noatime = has_noatime('input/file1')
         os.utime('input/file1', (atime, mtime))
         self.cmd('init', '--encryption=repokey', self.repository_location)
-        self.cmd('create', self.repository_location + '::test', 'input')
+        self.cmd('create', '--atime', self.repository_location + '::test', 'input')
         with changedir('output'):
             self.cmd('extract', self.repository_location + '::test')
         sti = os.stat('input/file1')
@@ -2172,8 +2172,8 @@ def has_noatime(some_file):
         self.cmd('init', '--encryption=repokey', self.repository_location)
         self.create_test_files()
         have_noatime = has_noatime('input/file1')
-        self.cmd('create', '--exclude-nodump', self.repository_location + '::archive', 'input')
-        self.cmd('create', '--exclude-nodump', self.repository_location + '::archive2', 'input')
+        self.cmd('create', '--exclude-nodump', '--atime', self.repository_location + '::archive', 'input')
+        self.cmd('create', '--exclude-nodump', '--atime', self.repository_location + '::archive2', 'input')
         if has_lchflags:
             # remove the file we did not backup, so input and output become equal
             os.remove(os.path.join('input', 'flagfile'))


Test patch: