Improve bulk export of posts (#11702)
This change modifies some of the logic for processing posts during bulk export in order to improve performance.
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5c87009ca4
Коммит
b73b6b4c36
@@ -52,34 +52,42 @@ var exportablePreferences = map[ComparablePreference]string{{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) BulkExport(writer io.Writer, file string, pathToEmojiDir string, dirNameToExportEmoji string) *model.AppError {
|
func (a *App) BulkExport(writer io.Writer, file string, pathToEmojiDir string, dirNameToExportEmoji string) *model.AppError {
|
||||||
|
mlog.Info("Bulk export: exporting version")
|
||||||
if err := a.ExportVersion(writer); err != nil {
|
if err := a.ExportVersion(writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mlog.Info("Bulk export: exporting teams")
|
||||||
if err := a.ExportAllTeams(writer); err != nil {
|
if err := a.ExportAllTeams(writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mlog.Info("Bulk export: exporting channels")
|
||||||
if err := a.ExportAllChannels(writer); err != nil {
|
if err := a.ExportAllChannels(writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mlog.Info("Bulk export: exporting users")
|
||||||
if err := a.ExportAllUsers(writer); err != nil {
|
if err := a.ExportAllUsers(writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mlog.Info("Bulk export: exporting posts")
|
||||||
if err := a.ExportAllPosts(writer); err != nil {
|
if err := a.ExportAllPosts(writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mlog.Info("Bulk export: exporting emoji")
|
||||||
if err := a.ExportCustomEmoji(writer, file, pathToEmojiDir, dirNameToExportEmoji); err != nil {
|
if err := a.ExportCustomEmoji(writer, file, pathToEmojiDir, dirNameToExportEmoji); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mlog.Info("Bulk export: exporting direct channels")
|
||||||
if err := a.ExportAllDirectChannels(writer); err != nil {
|
if err := a.ExportAllDirectChannels(writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mlog.Info("Bulk export: exporting direct posts")
|
||||||
if err := a.ExportAllDirectPosts(writer); err != nil {
|
if err := a.ExportAllDirectPosts(writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -327,15 +335,15 @@ func (a *App) buildUserNotifyProps(notifyProps model.StringMap) *UserNotifyProps
|
|||||||
|
|
||||||
func (a *App) ExportAllPosts(writer io.Writer) *model.AppError {
|
func (a *App) ExportAllPosts(writer io.Writer) *model.AppError {
|
||||||
afterId := strings.Repeat("0", 26)
|
afterId := strings.Repeat("0", 26)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
posts, err := a.Srv.Store.Post().GetParentsForExportAfter(1000, afterId)
|
posts, err := a.Srv.Store.Post().GetParentsForExportAfter(1000, afterId)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(posts) == 0 {
|
if len(posts) == 0 {
|
||||||
break
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, post := range posts {
|
for _, post := range posts {
|
||||||
@@ -348,47 +356,41 @@ func (a *App) ExportAllPosts(writer io.Writer) *model.AppError {
|
|||||||
|
|
||||||
postLine := ImportLineForPost(post)
|
postLine := ImportLineForPost(post)
|
||||||
|
|
||||||
// Do the Replies.
|
postLine.Post.Replies, err = a.buildPostReplies(post.Id)
|
||||||
replies, err := a.buildPostReplies(post.Id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reactions, err := a.BuildPostReactions(post.Id)
|
postLine.Post.Reactions = &[]ReactionImportData{}
|
||||||
if err != nil {
|
if post.HasReactions {
|
||||||
return err
|
postLine.Post.Reactions, err = a.BuildPostReactions(post.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
postLine.Post.Replies = replies
|
|
||||||
|
|
||||||
postLine.Post.Reactions = reactions
|
|
||||||
|
|
||||||
if err := a.ExportWriteLine(writer, postLine); err != nil {
|
if err := a.ExportWriteLine(writer, postLine); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) buildPostReplies(postId string) (*[]ReplyImportData, *model.AppError) {
|
func (a *App) buildPostReplies(postId string) (*[]ReplyImportData, *model.AppError) {
|
||||||
var replies []ReplyImportData
|
var replies []ReplyImportData
|
||||||
|
|
||||||
replyPosts, err := a.Srv.Store.Post().GetRepliesForExport(postId)
|
replyPosts, err := a.Srv.Store.Post().GetRepliesForExport(postId)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, reply := range replyPosts {
|
for _, reply := range replyPosts {
|
||||||
replyImportObject := ImportReplyFromPost(reply)
|
replyImportObject := ImportReplyFromPost(reply)
|
||||||
if reply.HasReactions == true {
|
if reply.HasReactions {
|
||||||
reactionsOfReply, err := a.BuildPostReactions(reply.Id)
|
replyImportObject.Reactions, err = a.BuildPostReactions(reply.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
replyImportObject.Reactions = reactionsOfReply
|
|
||||||
}
|
}
|
||||||
replies = append(replies, *replyImportObject)
|
replies = append(replies, *replyImportObject)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,12 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,7 +55,7 @@ var BulkExportCmd = &cobra.Command{
|
|||||||
Use: "bulk [file]",
|
Use: "bulk [file]",
|
||||||
Short: "Export bulk data.",
|
Short: "Export bulk data.",
|
||||||
Long: "Export data to a file compatible with the Mattermost Bulk Import format.",
|
Long: "Export data to a file compatible with the Mattermost Bulk Import format.",
|
||||||
Example: " export bulk bulk_data.json",
|
Example: "export bulk bulk_data.json",
|
||||||
RunE: bulkExportCmdF,
|
RunE: bulkExportCmdF,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
}
|
}
|
||||||
@@ -72,7 +70,7 @@ func init() {
|
|||||||
ActianceExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
|
ActianceExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
|
||||||
GlobalRelayZipExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
|
GlobalRelayZipExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
|
||||||
|
|
||||||
BulkExportCmd.Flags().Bool("all-teams", false, "Export all teams from the server.")
|
BulkExportCmd.Flags().Bool("all-teams", true, "Export all teams from the server.")
|
||||||
|
|
||||||
ExportCmd.AddCommand(ScheduleExportCmd)
|
ExportCmd.AddCommand(ScheduleExportCmd)
|
||||||
ExportCmd.AddCommand(CsvExportCmd)
|
ExportCmd.AddCommand(CsvExportCmd)
|
||||||
@@ -177,9 +175,8 @@ func bulkExportCmdF(command *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
allTeams, err := command.Flags().GetBool("all-teams")
|
allTeams, err := command.Flags().GetBool("all-teams")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Apply flag error")
|
return errors.Wrap(err, "all-teams flag error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !allTeams {
|
if !allTeams {
|
||||||
return errors.New("Nothing to export. Please specify the --all-teams flag to export all teams.")
|
return errors.New("Nothing to export. Please specify the --all-teams flag to export all teams.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1312,42 +1312,68 @@ func (s *SqlPostStore) GetMaxPostSize() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SqlPostStore) GetParentsForExportAfter(limit int, afterId string) ([]*model.PostForExport, *model.AppError) {
|
func (s *SqlPostStore) GetParentsForExportAfter(limit int, afterId string) ([]*model.PostForExport, *model.AppError) {
|
||||||
var posts []*model.PostForExport
|
for {
|
||||||
_, err := s.GetSearchReplica().Select(&posts, `
|
var rootIds []string
|
||||||
SELECT
|
_, err := s.GetReplica().Select(&rootIds,
|
||||||
p1.*,
|
`SELECT
|
||||||
Users.Username as Username,
|
Id
|
||||||
Teams.Name as TeamName,
|
FROM
|
||||||
Channels.Name as ChannelName
|
Posts
|
||||||
FROM
|
WHERE
|
||||||
Posts p1
|
Id > :AfterId
|
||||||
INNER JOIN
|
AND RootId = ''
|
||||||
Channels ON p1.ChannelId = Channels.Id
|
AND DeleteAt = 0
|
||||||
INNER JOIN
|
ORDER BY Id
|
||||||
Teams ON Channels.TeamId = Teams.Id
|
LIMIT :Limit`,
|
||||||
INNER JOIN
|
map[string]interface{}{"Limit": limit, "AfterId": afterId})
|
||||||
Users ON p1.UserId = Users.Id
|
if err != nil {
|
||||||
WHERE
|
return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error",
|
||||||
p1.Id > :AfterId
|
nil, err.Error(), http.StatusInternalServerError)
|
||||||
AND p1.ParentId = ''
|
}
|
||||||
AND p1.DeleteAt = 0
|
|
||||||
AND Channels.DeleteAt = 0
|
|
||||||
AND Teams.DeleteAt = 0
|
|
||||||
ORDER BY
|
|
||||||
p1.Id
|
|
||||||
LIMIT
|
|
||||||
:Limit`,
|
|
||||||
map[string]interface{}{"Limit": limit, "AfterId": afterId})
|
|
||||||
|
|
||||||
if err != nil {
|
var postsForExport []*model.PostForExport
|
||||||
return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error",
|
if len(rootIds) == 0 {
|
||||||
nil, err.Error(), http.StatusInternalServerError)
|
return postsForExport, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, params := MapStringsToQueryParams(rootIds, "PostId")
|
||||||
|
_, err = s.GetSearchReplica().Select(&postsForExport, `
|
||||||
|
SELECT
|
||||||
|
p1.*,
|
||||||
|
Users.Username as Username,
|
||||||
|
Teams.Name as TeamName,
|
||||||
|
Channels.Name as ChannelName
|
||||||
|
FROM
|
||||||
|
(Select * FROM Posts WHERE Id IN `+keys+`) p1
|
||||||
|
INNER JOIN
|
||||||
|
Channels ON p1.ChannelId = Channels.Id
|
||||||
|
INNER JOIN
|
||||||
|
Teams ON Channels.TeamId = Teams.Id
|
||||||
|
INNER JOIN
|
||||||
|
Users ON p1.UserId = Users.Id
|
||||||
|
WHERE
|
||||||
|
Channels.DeleteAt = 0
|
||||||
|
AND Teams.DeleteAt = 0
|
||||||
|
ORDER BY
|
||||||
|
p1.Id`,
|
||||||
|
params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error",
|
||||||
|
nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(postsForExport) == 0 {
|
||||||
|
// All of the posts were in channels or teams that were deleted.
|
||||||
|
// Update the afterId and try again.
|
||||||
|
afterId = rootIds[len(rootIds)-1]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return postsForExport, nil
|
||||||
}
|
}
|
||||||
return posts, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SqlPostStore) GetRepliesForExport(parentId string) ([]*model.ReplyForExport, *model.AppError) {
|
func (s *SqlPostStore) GetRepliesForExport(rootId string) ([]*model.ReplyForExport, *model.AppError) {
|
||||||
|
|
||||||
var posts []*model.ReplyForExport
|
var posts []*model.ReplyForExport
|
||||||
_, err := s.GetSearchReplica().Select(&posts, `
|
_, err := s.GetSearchReplica().Select(&posts, `
|
||||||
SELECT
|
SELECT
|
||||||
@@ -1358,11 +1384,11 @@ func (s *SqlPostStore) GetRepliesForExport(parentId string) ([]*model.ReplyForEx
|
|||||||
INNER JOIN
|
INNER JOIN
|
||||||
Users ON Posts.UserId = Users.Id
|
Users ON Posts.UserId = Users.Id
|
||||||
WHERE
|
WHERE
|
||||||
Posts.ParentId = :ParentId
|
Posts.RootId = :RootId
|
||||||
AND Posts.DeleteAt = 0
|
AND Posts.DeleteAt = 0
|
||||||
ORDER BY
|
ORDER BY
|
||||||
Posts.Id`,
|
Posts.Id`,
|
||||||
map[string]interface{}{"ParentId": parentId})
|
map[string]interface{}{"RootId": rootId})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return nil, model.NewAppError("SqlPostStore.GetAllAfterForExport", "store.sql_post.get_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user