Skip to content

Commit 83937e9

Browse files
committed
Merge branch 'ns/batch-fsync'
Introduce a filesystem-dependent mechanism to optimize the way the bits for many loose object files are ensured to hit the disk platter. * ns/batch-fsync: core.fsyncmethod: performance tests for batch mode t/perf: add iteration setup mechanism to perf-lib core.fsyncmethod: tests for batch mode test-lib-functions: add parsing helpers for ls-files and ls-tree core.fsync: use batch mode and sync loose objects by default on Windows unpack-objects: use the bulk-checkin infrastructure update-index: use the bulk-checkin infrastructure builtin/add: add ODB transaction around add_files_to_cache cache-tree: use ODB transaction around writing a tree core.fsyncmethod: batched disk flushes for loose-objects bulk-checkin: rebrand plug/unplug APIs as 'odb transactions' bulk-checkin: rename 'state' variable and separate 'plugged' boolean
2 parents 377d347 + 112a9fe commit 83937e9

25 files changed

+513
-120
lines changed

Documentation/config/core.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,14 @@ core.fsyncMethod::
628628
* `writeout-only` issues pagecache writeback requests, but depending on the
629629
filesystem and storage hardware, data added to the repository may not be
630630
durable in the event of a system crash. This is the default mode on macOS.
631+
* `batch` enables a mode that uses writeout-only flushes to stage multiple
632+
updates in the disk writeback cache and then does a single full fsync of
633+
a dummy file to trigger the disk cache flush at the end of the operation.
634+
+
635+
Currently `batch` mode only applies to loose-object files. Other repository
636+
data is made durable as if `fsync` was specified. This mode is expected to
637+
be as safe as `fsync` on macOS for repos stored on HFS+ or APFS filesystems
638+
and on Windows for repos stored on NTFS or ReFS filesystems.
631639

632640
core.fsyncObjectFiles::
633641
This boolean will enable 'fsync()' when writing object files.

builtin/add.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,16 @@ int add_files_to_cache(const char *prefix,
141141
rev.diffopt.format_callback_data = &data;
142142
rev.diffopt.flags.override_submodule_config = 1;
143143
rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
144+
145+
/*
146+
* Use an ODB transaction to optimize adding multiple objects.
147+
* This function is invoked from commands other than 'add', which
148+
* may not have their own transaction active.
149+
*/
150+
begin_odb_transaction();
144151
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
152+
end_odb_transaction();
153+
145154
clear_pathspec(&rev.prune_data);
146155
return !!data.add_errors;
147156
}
@@ -665,7 +674,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
665674
string_list_clear(&only_match_skip_worktree, 0);
666675
}
667676

668-
plug_bulk_checkin();
677+
begin_odb_transaction();
669678

670679
if (add_renormalize)
671680
exit_status |= renormalize_tracked_files(&pathspec, flags);
@@ -677,7 +686,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
677686

678687
if (chmod_arg && pathspec.nr)
679688
exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only);
680-
unplug_bulk_checkin();
689+
end_odb_transaction();
681690

682691
finish:
683692
if (write_locked_index(&the_index, &lock_file,

builtin/unpack-objects.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "builtin.h"
22
#include "cache.h"
3+
#include "bulk-checkin.h"
34
#include "config.h"
45
#include "object-store.h"
56
#include "object.h"
@@ -503,10 +504,12 @@ static void unpack_all(void)
503504
if (!quiet)
504505
progress = start_progress(_("Unpacking objects"), nr_objects);
505506
CALLOC_ARRAY(obj_list, nr_objects);
507+
begin_odb_transaction();
506508
for (i = 0; i < nr_objects; i++) {
507509
unpack_one(i);
508510
display_progress(progress, i + 1);
509511
}
512+
end_odb_transaction();
510513
stop_progress(&progress);
511514

512515
if (delta_list)

builtin/update-index.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
#define USE_THE_INDEX_COMPATIBILITY_MACROS
77
#include "cache.h"
8+
#include "bulk-checkin.h"
89
#include "config.h"
910
#include "lockfile.h"
1011
#include "quote.h"
@@ -57,6 +58,14 @@ static void report(const char *fmt, ...)
5758
if (!verbose)
5859
return;
5960

61+
/*
62+
* It is possible, though unlikely, that a caller could use the verbose
63+
* output to synchronize with addition of objects to the object
64+
* database. The current implementation of ODB transactions leaves
65+
* objects invisible while a transaction is active, so flush the
66+
* transaction here before reporting a change made by update-index.
67+
*/
68+
flush_odb_transaction();
6069
va_start(vp, fmt);
6170
vprintf(fmt, vp);
6271
putchar('\n');
@@ -1116,6 +1125,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
11161125
*/
11171126
parse_options_start(&ctx, argc, argv, prefix,
11181127
options, PARSE_OPT_STOP_AT_NON_OPTION);
1128+
1129+
/*
1130+
* Allow the object layer to optimize adding multiple objects in
1131+
* a batch.
1132+
*/
1133+
begin_odb_transaction();
11191134
while (ctx.argc) {
11201135
if (parseopt_state != PARSE_OPT_DONE)
11211136
parseopt_state = parse_options_step(&ctx, options,
@@ -1190,6 +1205,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
11901205
strbuf_release(&buf);
11911206
}
11921207

1208+
/*
1209+
* By now we have added all of the new objects
1210+
*/
1211+
end_odb_transaction();
1212+
11931213
if (split_index > 0) {
11941214
if (git_config_get_split_index() == 0)
11951215
warning(_("core.splitIndex is set to false; "

bulk-checkin.c

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
*/
44
#include "cache.h"
55
#include "bulk-checkin.h"
6+
#include "lockfile.h"
67
#include "repository.h"
78
#include "csum-file.h"
89
#include "pack.h"
910
#include "strbuf.h"
11+
#include "string-list.h"
12+
#include "tmp-objdir.h"
1013
#include "packfile.h"
1114
#include "object-store.h"
1215

13-
static struct bulk_checkin_state {
14-
unsigned plugged:1;
16+
static int odb_transaction_nesting;
1517

18+
static struct tmp_objdir *bulk_fsync_objdir;
19+
20+
static struct bulk_checkin_packfile {
1621
char *pack_tmp_name;
1722
struct hashfile *f;
1823
off_t offset;
@@ -21,7 +26,7 @@ static struct bulk_checkin_state {
2126
struct pack_idx_entry **written;
2227
uint32_t alloc_written;
2328
uint32_t nr_written;
24-
} state;
29+
} bulk_checkin_packfile;
2530

2631
static void finish_tmp_packfile(struct strbuf *basename,
2732
const char *pack_tmp_name,
@@ -39,7 +44,7 @@ static void finish_tmp_packfile(struct strbuf *basename,
3944
free(idx_tmp_name);
4045
}
4146

42-
static void finish_bulk_checkin(struct bulk_checkin_state *state)
47+
static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)
4348
{
4449
unsigned char hash[GIT_MAX_RAWSZ];
4550
struct strbuf packname = STRBUF_INIT;
@@ -80,7 +85,41 @@ static void finish_bulk_checkin(struct bulk_checkin_state *state)
8085
reprepare_packed_git(the_repository);
8186
}
8287

83-
static int already_written(struct bulk_checkin_state *state, struct object_id *oid)
88+
/*
89+
* Cleanup after batch-mode fsync_object_files.
90+
*/
91+
static void flush_batch_fsync(void)
92+
{
93+
struct strbuf temp_path = STRBUF_INIT;
94+
struct tempfile *temp;
95+
96+
if (!bulk_fsync_objdir)
97+
return;
98+
99+
/*
100+
* Issue a full hardware flush against a temporary file to ensure
101+
* that all objects are durable before any renames occur. The code in
102+
* fsync_loose_object_bulk_checkin has already issued a writeout
103+
* request, but it has not flushed any writeback cache in the storage
104+
* hardware or any filesystem logs. This fsync call acts as a barrier
105+
* to ensure that the data in each new object file is durable before
106+
* the final name is visible.
107+
*/
108+
strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", get_object_directory());
109+
temp = xmks_tempfile(temp_path.buf);
110+
fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
111+
delete_tempfile(&temp);
112+
strbuf_release(&temp_path);
113+
114+
/*
115+
* Make the object files visible in the primary ODB after their data is
116+
* fully durable.
117+
*/
118+
tmp_objdir_migrate(bulk_fsync_objdir);
119+
bulk_fsync_objdir = NULL;
120+
}
121+
122+
static int already_written(struct bulk_checkin_packfile *state, struct object_id *oid)
84123
{
85124
int i;
86125

@@ -112,7 +151,7 @@ static int already_written(struct bulk_checkin_state *state, struct object_id *o
112151
* status before calling us just in case we ask it to call us again
113152
* with a new pack.
114153
*/
115-
static int stream_to_pack(struct bulk_checkin_state *state,
154+
static int stream_to_pack(struct bulk_checkin_packfile *state,
116155
git_hash_ctx *ctx, off_t *already_hashed_to,
117156
int fd, size_t size, enum object_type type,
118157
const char *path, unsigned flags)
@@ -189,7 +228,7 @@ static int stream_to_pack(struct bulk_checkin_state *state,
189228
}
190229

191230
/* Lazily create backing packfile for the state */
192-
static void prepare_to_stream(struct bulk_checkin_state *state,
231+
static void prepare_to_stream(struct bulk_checkin_packfile *state,
193232
unsigned flags)
194233
{
195234
if (!(flags & HASH_WRITE_OBJECT) || state->f)
@@ -204,7 +243,7 @@ static void prepare_to_stream(struct bulk_checkin_state *state,
204243
die_errno("unable to write pack header");
205244
}
206245

207-
static int deflate_to_pack(struct bulk_checkin_state *state,
246+
static int deflate_to_pack(struct bulk_checkin_packfile *state,
208247
struct object_id *result_oid,
209248
int fd, size_t size,
210249
enum object_type type, const char *path,
@@ -251,7 +290,7 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
251290
BUG("should not happen");
252291
hashfile_truncate(state->f, &checkpoint);
253292
state->offset = checkpoint.offset;
254-
finish_bulk_checkin(state);
293+
flush_bulk_checkin_packfile(state);
255294
if (lseek(fd, seekback, SEEK_SET) == (off_t) -1)
256295
return error("cannot seek back");
257296
}
@@ -274,25 +313,67 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
274313
return 0;
275314
}
276315

316+
void prepare_loose_object_bulk_checkin(void)
317+
{
318+
/*
319+
* We lazily create the temporary object directory
320+
* the first time an object might be added, since
321+
* callers may not know whether any objects will be
322+
* added at the time they call begin_odb_transaction.
323+
*/
324+
if (!odb_transaction_nesting || bulk_fsync_objdir)
325+
return;
326+
327+
bulk_fsync_objdir = tmp_objdir_create("bulk-fsync");
328+
if (bulk_fsync_objdir)
329+
tmp_objdir_replace_primary_odb(bulk_fsync_objdir, 0);
330+
}
331+
332+
void fsync_loose_object_bulk_checkin(int fd, const char *filename)
333+
{
334+
/*
335+
* If we have an active ODB transaction, we issue a call that
336+
* cleans the filesystem page cache but avoids a hardware flush
337+
* command. Later on we will issue a single hardware flush
338+
* before renaming the objects to their final names as part of
339+
* flush_batch_fsync.
340+
*/
341+
if (!bulk_fsync_objdir ||
342+
git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
343+
fsync_or_die(fd, filename);
344+
}
345+
}
346+
277347
int index_bulk_checkin(struct object_id *oid,
278348
int fd, size_t size, enum object_type type,
279349
const char *path, unsigned flags)
280350
{
281-
int status = deflate_to_pack(&state, oid, fd, size, type,
351+
int status = deflate_to_pack(&bulk_checkin_packfile, oid, fd, size, type,
282352
path, flags);
283-
if (!state.plugged)
284-
finish_bulk_checkin(&state);
353+
if (!odb_transaction_nesting)
354+
flush_bulk_checkin_packfile(&bulk_checkin_packfile);
285355
return status;
286356
}
287357

288-
void plug_bulk_checkin(void)
358+
void begin_odb_transaction(void)
289359
{
290-
state.plugged = 1;
360+
odb_transaction_nesting += 1;
291361
}
292362

293-
void unplug_bulk_checkin(void)
363+
void flush_odb_transaction(void)
294364
{
295-
state.plugged = 0;
296-
if (state.f)
297-
finish_bulk_checkin(&state);
365+
flush_batch_fsync();
366+
flush_bulk_checkin_packfile(&bulk_checkin_packfile);
367+
}
368+
369+
void end_odb_transaction(void)
370+
{
371+
odb_transaction_nesting -= 1;
372+
if (odb_transaction_nesting < 0)
373+
BUG("Unbalanced ODB transaction nesting");
374+
375+
if (odb_transaction_nesting)
376+
return;
377+
378+
flush_odb_transaction();
298379
}

bulk-checkin.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,34 @@
66

77
#include "cache.h"
88

9+
void prepare_loose_object_bulk_checkin(void);
10+
void fsync_loose_object_bulk_checkin(int fd, const char *filename);
11+
912
int index_bulk_checkin(struct object_id *oid,
1013
int fd, size_t size, enum object_type type,
1114
const char *path, unsigned flags);
1215

13-
void plug_bulk_checkin(void);
14-
void unplug_bulk_checkin(void);
16+
/*
17+
* Tell the object database to optimize for adding
18+
* multiple objects. end_odb_transaction must be called
19+
* to make new objects visible. Transactions can be nested,
20+
* and objects are only visible after the outermost transaction
21+
* is complete or the transaction is flushed.
22+
*/
23+
void begin_odb_transaction(void);
24+
25+
/*
26+
* Make any objects that are currently part of a pending object
27+
* database transaction visible. It is valid to call this function
28+
* even if no transaction is active.
29+
*/
30+
void flush_odb_transaction(void);
31+
32+
/*
33+
* Tell the object database to make any objects from the
34+
* current transaction visible if this is the final nested
35+
* transaction.
36+
*/
37+
void end_odb_transaction(void);
1538

1639
#endif

cache-tree.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "tree.h"
44
#include "tree-walk.h"
55
#include "cache-tree.h"
6+
#include "bulk-checkin.h"
67
#include "object-store.h"
78
#include "replace-object.h"
89
#include "promisor-remote.h"
@@ -474,8 +475,10 @@ int cache_tree_update(struct index_state *istate, int flags)
474475

475476
trace_performance_enter();
476477
trace2_region_enter("cache_tree", "update", the_repository);
478+
begin_odb_transaction();
477479
i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
478480
"", 0, &skip, flags);
481+
end_odb_transaction();
479482
trace2_region_leave("cache_tree", "update", the_repository);
480483
trace_performance_leave("cache_tree_update");
481484
if (i < 0)

0 commit comments

Comments
 (0)