c0f4752ed2
When adding many objects to a repo with `core.fsync=loose-object`, the cost of fsync'ing each object file can become prohibitive. One major source of the cost of fsync is the implied flush of the hardware writeback cache within the disk drive. This commit introduces a new `core.fsyncMethod=batch` option that batches up hardware flushes. It hooks into the bulk-checkin odb-transaction functionality, takes advantage of tmp-objdir, and uses the writeout-only support code. When the new mode is enabled, we do the following for each new object: 1a. Create the object in a tmp-objdir. 2a. Issue a pagecache writeback request and wait for it to complete. At the end of the entire transaction when unplugging bulk checkin: 1b. Issue an fsync against a dummy file to flush the log and hardware writeback cache, which should by now have seen the tmp-objdir writes. 2b. Rename all of the tmp-objdir files to their final names. 3b. When updating the index and/or refs, we assume that Git will issue another fsync internal to that operation. This is not the default today, but the user now has the option of syncing the index and there is a separate patch series to implement syncing of refs. On a filesystem with a singular journal that is updated during name operations (e.g. create, link, rename, etc), such as NTFS, HFS+, or XFS we would expect the fsync to trigger a journal writeout so that this sequence is enough to ensure that the user's data is durable by the time the git command returns. This sequence also ensures that no object files appear in the main object store unless they are fsync-durable. Batch mode is only enabled if core.fsync includes loose-objects. If the legacy core.fsyncObjectFiles setting is enabled, but core.fsync does not include loose-objects, we will use file-by-file fsyncing. In step (1a) of the sequence, the tmp-objdir is created lazily to avoid work if no loose objects are ever added to the ODB. We use a tmp-objdir to maintain the invariant that no loose-objects are visible in the main ODB unless they are properly fsync-durable. This is important since future ODB operations that try to create an object with specific contents will silently drop the new data if an object with the target hash exists without checking that the loose-object contents match the hash. Only a full git-fsck would restore the ODB to a functional state where dataloss doesn't occur. In step (1b) of the sequence, we issue a fsync against a dummy file created specifically for the purpose. This method has a little higher cost than using one of the input object files, but makes adding new callers of this mechanism easier, since we don't need to figure out which object file is "last" or risk sharing violations by caching the fd of the last object file. _Performance numbers_: Linux - Hyper-V VM running Kernel 5.11 (Ubuntu 20.04) on a fast SSD. Mac - macOS 11.5.1 running on a Mac mini on a 1TB Apple SSD. Windows - Same host as Linux, a preview version of Windows 11. Adding 500 files to the repo with 'git add' Times reported in seconds. object file syncing | Linux | Mac | Windows --------------------|-------|-------|-------- disabled | 0.06 | 0.35 | 0.61 fsync | 1.88 | 11.18 | 2.47 batch | 0.15 | 0.41 | 1.53 Signed-off-by: Neeraj Singh <neerajsi@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
40 lines
1.1 KiB
C
40 lines
1.1 KiB
C
/*
|
|
* Copyright (c) 2011, Google Inc.
|
|
*/
|
|
#ifndef BULK_CHECKIN_H
|
|
#define BULK_CHECKIN_H
|
|
|
|
#include "cache.h"
|
|
|
|
void prepare_loose_object_bulk_checkin(void);
|
|
void fsync_loose_object_bulk_checkin(int fd, const char *filename);
|
|
|
|
int index_bulk_checkin(struct object_id *oid,
|
|
int fd, size_t size, enum object_type type,
|
|
const char *path, unsigned flags);
|
|
|
|
/*
|
|
* Tell the object database to optimize for adding
|
|
* multiple objects. end_odb_transaction must be called
|
|
* to make new objects visible. Transactions can be nested,
|
|
* and objects are only visible after the outermost transaction
|
|
* is complete or the transaction is flushed.
|
|
*/
|
|
void begin_odb_transaction(void);
|
|
|
|
/*
|
|
* Make any objects that are currently part of a pending object
|
|
* database transaction visible. It is valid to call this function
|
|
* even if no transaction is active.
|
|
*/
|
|
void flush_odb_transaction(void);
|
|
|
|
/*
|
|
* Tell the object database to make any objects from the
|
|
* current transaction visible if this is the final nested
|
|
* transaction.
|
|
*/
|
|
void end_odb_transaction(void);
|
|
|
|
#endif
|