changeset 8926:be17526a165d HEAD

dbox: Store message sizes to map index and use them. Some other save optimizations.
author Timo Sirainen <tss@iki.fi>
date Wed, 25 Feb 2009 12:23:30 -0500
parents a1d920a81adc
children a48605408d18
files src/lib-storage/index/dbox/dbox-file.c src/lib-storage/index/dbox/dbox-file.h src/lib-storage/index/dbox/dbox-map.c src/lib-storage/index/dbox/dbox-map.h src/lib-storage/index/dbox/dbox-save.c
diffstat 5 files changed, 92 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-storage/index/dbox/dbox-file.c	Wed Feb 25 12:22:33 2009 -0500
+++ b/src/lib-storage/index/dbox/dbox-file.c	Wed Feb 25 12:23:30 2009 -0500
@@ -658,32 +658,45 @@
 }
 
 static int
-dbox_file_seek_append_pos(struct dbox_file *file, uoff_t last_msg_offset)
+dbox_file_seek_append_pos(struct dbox_file *file,
+			  uoff_t last_msg_offset, uoff_t last_msg_size)
 {
 	uoff_t offset, size;
+	struct stat st;
 	bool last;
 	int ret;
 
-	if ((ret = dbox_file_read_header(file)) <= 0)
-		return ret;
-
 	if (file->file_version != DBOX_VERSION ||
 	    file->msg_header_size != sizeof(struct dbox_message_header)) {
 		/* created by an incompatible version, can't append */
 		return 0;
 	}
+	if (last_msg_size > 0) {
+		/* if last_msg_offset + last_msg_size points to EOF,
+		   we can safely use it without reading the last message. */
+		if (fstat(file->fd, &st) < 0) {
+			dbox_file_set_syscall_error(file, "fstat()");
+			return -1;
+		}
+	} else {
+		st.st_size = -1;
+	}
 
 	offset = last_msg_offset;
-	ret = dbox_file_seek_next(file, &offset, &size, &last);
-	if (ret <= 0)
-		return ret;
-	if (!last) {
-		/* not end of file? previous write probably crashed. */
-		if (ftruncate(file->fd, offset) < 0) {
-			dbox_file_set_syscall_error(file, "ftruncate()");
-			return -1;
+	if (st.st_size == (off_t)(last_msg_offset + last_msg_size)) {
+		offset += last_msg_size;
+	} else {
+		ret = dbox_file_seek_next(file, &offset, &size, &last);
+		if (ret <= 0)
+			return ret;
+		if (!last) {
+			/* not end of file? previous write probably crashed. */
+			if (ftruncate(file->fd, offset) < 0) {
+				dbox_file_set_syscall_error(file, "ftruncate()");
+				return -1;
+			}
+			i_stream_sync(file->input);
 		}
-		i_stream_sync(file->input);
 	}
 
 	file->output = o_stream_create_fd_file(file->fd, 0, FALSE);
@@ -692,7 +705,7 @@
 }
 
 int dbox_file_get_append_stream(struct dbox_file *file, uoff_t last_msg_offset,
-				struct ostream **stream_r)
+				uoff_t last_msg_size, struct ostream **stream_r)
 {
 	bool deleted;
 	int ret;
@@ -711,7 +724,8 @@
 	if (file->output == NULL) {
 		i_assert(file->lock != NULL || last_msg_offset == 0);
 
-		ret = dbox_file_seek_append_pos(file, last_msg_offset);
+		ret = dbox_file_seek_append_pos(file, last_msg_offset,
+						last_msg_size);
 		if (ret <= 0)
 			return ret;
 	}
--- a/src/lib-storage/index/dbox/dbox-file.h	Wed Feb 25 12:22:33 2009 -0500
+++ b/src/lib-storage/index/dbox/dbox-file.h	Wed Feb 25 12:23:30 2009 -0500
@@ -158,6 +158,7 @@
    1 if ok, 0 if file can't be appended to (old file version or corruption)
    or -1 if error. */
 int dbox_file_get_append_stream(struct dbox_file *file, uoff_t last_msg_offset,
+				uoff_t last_msg_size,
 				struct ostream **stream_r);
 /* Returns the next offset for append a message. dbox_file_get_append_stream()
    must have been called for this file already at least once. */
--- a/src/lib-storage/index/dbox/dbox-map.c	Wed Feb 25 12:22:33 2009 -0500
+++ b/src/lib-storage/index/dbox/dbox-map.c	Wed Feb 25 12:23:30 2009 -0500
@@ -16,6 +16,7 @@
 struct dbox_mail_index_map_record {
 	uint32_t file_id;
 	uint32_t offset;
+	uint32_t size;
 };
 
 struct dbox_map {
@@ -28,7 +29,7 @@
 
 struct dbox_map_append {
 	struct dbox_file *file;
-	uoff_t offset;
+	uoff_t offset, size;
 };
 
 struct dbox_map_append_context {
@@ -118,7 +119,8 @@
 }
 
 static int dbox_map_lookup_seq(struct dbox_map *map, uint32_t seq,
-			       uint32_t *file_id_r, uoff_t *offset_r)
+			       uint32_t *file_id_r, uoff_t *offset_r,
+			       uoff_t *size_r)
 {
 	const struct dbox_mail_index_map_record *rec;
 	const void *data;
@@ -138,6 +140,7 @@
 
 	*file_id_r = rec->file_id;
 	*offset_r = rec->offset;
+	*size_r = rec->size;
 	return 0;
 }
 
@@ -145,6 +148,7 @@
 		    uint32_t *file_id_r, uoff_t *offset_r)
 {
 	uint32_t seq;
+	uoff_t size;
 	int ret;
 
 	if ((ret = dbox_map_open(map, FALSE)) <= 0) {
@@ -160,7 +164,7 @@
 			return 0;
 	}
 
-	if (dbox_map_lookup_seq(map, seq, file_id_r, offset_r) < 0)
+	if (dbox_map_lookup_seq(map, seq, file_id_r, offset_r, &size) < 0)
 		return 0;
 	return 1;
 }
@@ -209,35 +213,36 @@
 
 static bool
 dbox_map_file_try_append(struct dbox_map_append_context *ctx,
-			 uint32_t file_id, time_t stamp,
-			 uoff_t mail_size, uoff_t last_msg_offset,
+			 uint32_t file_id, time_t stamp, uoff_t mail_size,
+			 uoff_t last_msg_offset, uoff_t last_msg_size,
 			 struct dbox_file **file_r, struct ostream **output_r,
 			 bool *retry_later_r)
 {
 	struct dbox_map *map = ctx->map;
+	struct dbox_storage *storage = map->storage;
 	const struct mail_index_header *hdr;
 	struct dbox_file *file;
 	struct stat st;
 	uint32_t seq, tmp_file_id;
-	uoff_t tmp_offset;
+	uoff_t tmp_offset, tmp_size;
 	bool deleted, file_too_old = FALSE;
 	int ret;
 
 	*file_r = NULL;
 	*retry_later_r = FALSE;
 
-	file = dbox_file_init_multi(map->storage, file_id);
+	file = dbox_file_init_multi(storage, file_id);
 	if (dbox_file_open_or_create(file, &deleted) <= 0)
 		return TRUE;
 	if (deleted)
 		return TRUE;
 
 	if (fstat(file->fd, &st) < 0) {
-		mail_storage_set_critical(&map->storage->storage,
+		mail_storage_set_critical(&storage->storage,
 			"fstat(%s) failed: %m", file->current_path);
-	} else if (I_MIN(st.st_mtime, st.st_ctime) < stamp)
+	} else if (file->create_time < stamp)
 		file_too_old = TRUE;
-	else if (st.st_size + mail_size > map->storage->rotate_size) {
+	else if (st.st_size + mail_size > storage->rotate_size) {
 		/* file too large */
 	} else if ((ret = dbox_file_try_lock(file)) <= 0) {
 		/* locking failed */
@@ -248,18 +253,20 @@
 		hdr = mail_index_get_header(map->view);
 		seq = ctx->orig_msg_count + 1;
 		for (; seq <= hdr->messages_count; seq++) {
-			if (dbox_map_lookup_seq(map, seq,
-						&tmp_file_id, &tmp_offset) < 0)
+			if (dbox_map_lookup_seq(map, seq, &tmp_file_id,
+						&tmp_offset, &tmp_size) < 0)
 				break;
 			if (tmp_file_id == file->file_id) {
 				i_assert(last_msg_offset < tmp_offset);
 				last_msg_offset = tmp_offset;
+				last_msg_size = tmp_size;
 			}
 		}
 
 		if (seq > hdr->messages_count &&
+		    last_msg_offset + last_msg_size > storage->rotate_size &&
 		    dbox_file_get_append_stream(file, last_msg_offset,
-						output_r) > 0) {
+						last_msg_size, output_r) > 0) {
 			/* success */
 			*file_r = file;
 			return TRUE;
@@ -298,7 +305,7 @@
 	const struct mail_index_header *hdr;
 	unsigned int i, count, backwards_lookup_count;
 	uint32_t seq, file_id, min_seen_file_id;
-	uoff_t offset, append_offset;
+	uoff_t offset, append_offset, size;
 	time_t stamp;
 	bool retry_later, updated_retry_seq_min;
 
@@ -310,9 +317,14 @@
 	/* first try to use files already used in this append */
 	files = array_get(&ctx->files, &count);
 	for (i = count; i > ctx->files_nonappendable_count; i--) {
+		if (files[i-1]->output == NULL) {
+			/* we already decided we can't append to this */
+			continue;
+		}
+
 		append_offset = dbox_file_get_next_append_offset(files[i-1]);
 		if (append_offset + mail_size <= map->storage->rotate_size &&
-		    dbox_file_get_append_stream(files[i-1], append_offset,
+		    dbox_file_get_append_stream(files[i-1], 0, 0,
 						output_r) > 0) {
 			*file_r = files[i-1];
 			*existing_r = TRUE;
@@ -343,16 +355,20 @@
 			seq = ctx->retry_seq_min + 1;
 			continue;
 		}
-		if (dbox_map_lookup_seq(map, seq, &file_id, &offset) < 0)
+		if (dbox_map_lookup_seq(map, seq, &file_id, &offset, &size) < 0)
 			return -1;
 		if (file_id >= min_seen_file_id)
 			continue;
 		min_seen_file_id = file_id;
 
-		/* first lookup: this doesn't really tell us if we're going
-		   to exceed the rotate size, it just potentially reduces
-		   how many files we have to open. */
-		if (offset + mail_size >= map->storage->rotate_size)
+		if (++backwards_lookup_count > MAX_BACKWARDS_LOOKUPS) {
+			/* we've wasted enough time here */
+			break;
+		}
+
+		/* first lookup: this should be enough usually, but we can't
+		   be sure until after locking */
+		if (offset + size + mail_size >= map->storage->rotate_size)
 			continue;
 
 		if (dbox_map_is_appending(ctx, file_id)) {
@@ -360,14 +376,9 @@
 			continue;
 		}
 
-		if (++backwards_lookup_count > MAX_BACKWARDS_LOOKUPS) {
-			/* we've wasted enough time here */
-			break;
-		}
-
-		if (!dbox_map_file_try_append(ctx, file_id, stamp,
-					      mail_size, offset,
-					      file_r, output_r, &retry_later)) {
+		if (!dbox_map_file_try_append(ctx, file_id, stamp, mail_size,
+					      offset, size, file_r, output_r,
+					      &retry_later)) {
 			/* file is too old. the rest of the files are too. */
 			break;
 		}
@@ -410,7 +421,7 @@
 		file = ctx->map->storage->rotate_size == 0 ?
 			dbox_file_init_single(ctx->mbox, 0) :
 			dbox_file_init_multi(ctx->map->storage, 0);
-		ret = dbox_file_get_append_stream(file, 0, output_r);
+		ret = dbox_file_get_append_stream(file, 0, 0, output_r);
 		if (ret <= 0) {
 			i_assert(ret < 0);
 			(void)unlink(file->current_path);
@@ -423,6 +434,7 @@
 		append = array_append_space(&ctx->appends);
 		append->file = file;
 		append->offset = (*output_r)->offset;
+		append->size = (uint32_t)-1;
 	}
 	if (!existing) {
 		i_assert(file->output != NULL);
@@ -432,6 +444,17 @@
 	return 0;
 }
 
+void dbox_map_append_finish_mail(struct dbox_map_append_context *ctx)
+{
+	struct dbox_map_append *appends;
+	unsigned int count;
+
+	appends = array_get_modifiable(&ctx->appends, &count);
+	i_assert(count > 0 && appends[count-1].size == (uint32_t)-1);
+	appends[count-1].size = appends[count-1].file->output->offset -
+		appends[count-1].offset;
+}
+
 static int
 dbox_map_get_next_file_id(struct dbox_map *map, struct mail_index_view *view,
 			  uint32_t *file_id_r)
@@ -534,8 +557,12 @@
 	ref16 = 1;
 	appends = array_get(&ctx->appends, &count);
 	for (i = 0; i < count; i++) {
+		i_assert(appends[i].offset <= (uint32_t)-1);
+		i_assert(appends[i].size <= (uint32_t)-1);
+
 		rec.file_id = appends[i].file->file_id;
 		rec.offset = appends[i].offset;
+		rec.size = appends[i].size;
 
 		mail_index_append(trans, 0, &seq);
 		mail_index_update_ext(trans, seq, ctx->map->map_ext_id,
--- a/src/lib-storage/index/dbox/dbox-map.h	Wed Feb 25 12:22:33 2009 -0500
+++ b/src/lib-storage/index/dbox/dbox-map.h	Wed Feb 25 12:23:30 2009 -0500
@@ -13,11 +13,13 @@
 
 struct dbox_map_append_context *
 dbox_map_append_begin(struct dbox_mailbox *mbox);
-/* Request file for saving a new message with given size. If an existing file
-   can be used, the record is locked and updated in index. Returns 0 if ok,
-   -1 if error. */
+/* Request file for saving a new message with given size (if available). If an
+   existing file can be used, the record is locked and updated in index.
+   Returns 0 if ok, -1 if error. */
 int dbox_map_append_next(struct dbox_map_append_context *ctx, uoff_t mail_size,
 			 struct dbox_file **file_r, struct ostream **output_r);
+/* Finished saving the last mail. Saves the message size. */
+void dbox_map_append_finish_mail(struct dbox_map_append_context *ctx);
 /* Assign map UIDs to all appended msgs to multi-files. */
 int dbox_map_append_assign_map_uids(struct dbox_map_append_context *ctx,
 				    uint32_t *first_map_uid_r,
--- a/src/lib-storage/index/dbox/dbox-save.c	Wed Feb 25 12:22:33 2009 -0500
+++ b/src/lib-storage/index/dbox/dbox-save.c	Wed Feb 25 12:23:30 2009 -0500
@@ -272,6 +272,7 @@
 		return -1;
 	}
 
+	dbox_map_append_finish_mail(ctx->append_ctx);
 	if (save_mail->file->single_mbox != NULL) {
 		dbox_file_close(save_mail->file);
 		ctx->single_count++;