Skip to content
This repository was archived by the owner on Oct 28, 2020. It is now read-only.

[wip] User cache support.#13

Open
SuperSpyTX wants to merge 6 commits into
riking:masterfrom
SuperSpyTX:patch-user-cache
Open

[wip] User cache support.#13
SuperSpyTX wants to merge 6 commits into
riking:masterfrom
SuperSpyTX:patch-user-cache

Conversation

@SuperSpyTX
Copy link
Copy Markdown
Collaborator

@SuperSpyTX SuperSpyTX commented Sep 12, 2017

Added preliminary support for user caching. When the module is loaded and enabled, it loads the cached database entries and inserts them directly into the RTM object client.users. In addition, any calls to client.ReplaceManyUserObjects and client.ReplaceUserObject will have the cache updated.

In addition to these changes, I added the delay from the Retry-After http header returned if the call was ratelimited.

Questions:

  • Should I take a different approach? Currently I modified the two functions above to call the user cache module if it's loaded.
  • Is adding the delay from the Retry-After header a good idea? It does fill the user cache (all 2,600 users) successfully.

Overall, do you think this is a good implementation?

Copy link
Copy Markdown
Owner

@riking riking left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good! Some perf problems, though.

Comment thread modules/usercache/database.go Outdated

// $1 = slack.UserID
// $2 = data (json encoded)
sqlAddEntry = `INSERT INTO module_user_cache (user_id,data) VALUES ($1, $2)`
Copy link
Copy Markdown
Owner

@riking riking Sep 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ON CONFLICT DO UPDATE SET data = EXCLUDED.data
😃

Avoids the need for a transaction / checking first.

Comment thread modules/usercache/database.go Outdated
if err != nil {
return errors.Wrap(err, "error in user cache: unmarshal user object")
}
rtmClient := mod.team.GetRTMClient().(*rtm.Client)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save this outside the for loop

Comment thread modules/usercache/database.go Outdated
for stmt.Next() {
var id string
var data string
var user slack.User
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pointer to slack.User, remove & on line 74. The object's going to live on the heap anyways.

Comment thread slack/rtm/membership_info.go Outdated
type userCacheAPI interface {
marvin.Module

GetEntry(userid slack.UserID) (slack.User, error)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetEntry is never used and can be dropped from the interface.

"github.com/riking/marvin/slack"
)

type API interface {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API type can be dropped, or at least commented with // interface duplicated in rtm package

Comment thread modules/usercache/usercache.go Outdated

import (
"sync"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run goimports

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could have sworn goimports was run on this file and this was the result... I'll run it again and see if it produces the same result.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, remove the newline between the two import blocks.

Comment thread modules/usercache/database.go Outdated
return err
}

func (mod *UserCacheModule) UpdateEntries(userobjects []*slack.User) error {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider forwarding UpdateEntry to this function instead - you can prepare the statement once and do many inserts.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also considering using transactions (.Begin and .Commit).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see what you mean.

Comment thread modules/usercache/database.go Outdated
return stmt.Err()
}

func (mod *UserCacheModule) UpdateEntry(userobject slack.User) error {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should take a pointer - slack.User is a large object.

Comment thread modules/usercache/database.go Outdated
return errors.Wrap(err, "error in user cache: unmarshal user object")
}
rtmClient := mod.team.GetRTMClient().(*rtm.Client)
rtmClient.ReplaceUserObject(&user)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method takes a lock - consider batching by hundreds?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to do an big array and loading it that way, but I didn't want to consume so much memory. Perhaps maybe I can make this so every 200 entries (like the Slack API) it'll call ReplaceManyUserObjects with an array of 200, then empty and continue.

c.ReplaceManyUserObjects(response.Members, true)

for response.PageInfo.NextCursor != "" {
c.ReplaceManyUserObjects(response.Members)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to make this change because otherwise the ReplaceManyUserObjects would get called again for the same group of objects retrieved from the last successful query.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And with the new changes, it was also not retrieving all the users.

Comment thread modules/usercache/database.go Outdated
var id string
var data string
var user slack.User
var user *slack.User = &slack.User{}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave it at nil, json.Unmarshal(&user) will allocate for you.

Comment thread modules/usercache/database.go Outdated
err = json.Unmarshal([]byte(data), user)
if err != nil {
return errors.Wrap(err, "error in user cache: unmarshal user object")
return err
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably safer to skip erroring rows - we'll re-fetch the data later.

Comment thread modules/usercache/database.go Outdated
arr = append(arr, user)
if len(arr) >= 199 {
go rtmClient.ReplaceManyUserObjects(arr, false)
arr = make([]*slack.User, 200)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function isn't blocking boot, so just call ReplaceMany directly and use arr = arr[:0] instead.

Comment thread modules/usercache/usercache.go Outdated

func (mod *UserCacheModule) Enable(team marvin.Team) {
go func() {
fmt.Printf("Loading cache entries....\n")
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user cache, until we're caching other objects as well

Comment thread modules/usercache/database.go Outdated
func (mod *UserCacheModule) UpdateEntry(userobject *slack.User) error {
var objarray = make([]*slack.User, 1)
objarray[0] = userobject
return mod.UpdateEntries(objarray)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mod.UpdateEntries([]*slack.User{userobject})

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to figure out what the easier way was...ugh.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's type{fields}

if err != nil {
return err
entrydata, err := json.Marshal(obj)
if err == nil {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if err != nil { continue } on json marshalling

Copy link
Copy Markdown
Owner

@riking riking left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to change the callers of rtm.Client.fillUsersList() - it doesn't need to be called on boot because we have the cache.

Comment thread slack/rtm/events.go
moduleCacheApi := c.team.GetModule("usercache")
if moduleCacheApi != nil && updateCache {
cacheApi = moduleCacheApi.(userCacheAPI)
cacheApi.UpdateEntries(objs)
Copy link
Copy Markdown
Owner

@riking riking Sep 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this outside the MetadataLock.

Comment thread slack/rtm/events.go
moduleCacheApi := c.team.GetModule("usercache")
if moduleCacheApi != nil {
cacheApi = moduleCacheApi.(userCacheAPI)
cacheApi.UpdateEntry(obj)
Copy link
Copy Markdown
Owner

@riking riking Sep 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same - move outside the lock.

@SuperSpyTX
Copy link
Copy Markdown
Collaborator Author

If you would like, I can squash these commits (like the old bukkit days).

@SuperSpyTX
Copy link
Copy Markdown
Collaborator Author

And yes, I've been testing my code.

delaystr, _, _ := mod.team.ModuleConfig(Identifier).GetIsDefault("delay")
timeint, _ := strconv.ParseInt(timestr, 10, 64)
var timeres = time.Unix(timeint, 0)
delayres, err := time.ParseDuration(delaystr)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should fall back to default if it fails - const defaultDelay = 72*time.Hour; if err != nil { delayres = defaultDelay }

Fail safe, not dangerous - setting a bad duration would result in reloading the entire thing every hour.

@SuperSpyTX
Copy link
Copy Markdown
Collaborator Author

Is this still an problem a year later?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants