diff --git a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 b/app/assets/javascripts/discourse/lib/link-mentions.js.es6
new file mode 100644
index 000000000..084564291
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/link-mentions.js.es6
@@ -0,0 +1,56 @@
+function replaceSpan($e, username) {
+  $e.replaceWith("<a href='" +
+                  Discourse.getURL("/users/") + username.toLowerCase() +
+                  "' class='mention'>@" + username + "</a>");
+}
+
+const found = [];
+const checked = [];
+
+function updateFound($mentions, usernames) {
+  Ember.run.scheduleOnce('afterRender', function() {
+    $mentions.each((i, e) => {
+      const $e = $(e);
+      const username = usernames[i];
+      if (found.indexOf(username) !== -1) {
+        replaceSpan($e, username);
+      } else {
+        $e.removeClass('mention-loading').addClass('mention-tested');
+      }
+    });
+  });
+}
+
+
+let linking = false;
+export default function linkMentions($elem) {
+  if (linking) { return Ember.RSVP.Promise.resolve(); }
+  linking = true;
+
+  return new Ember.RSVP.Promise(function(resolve) {
+    const $mentions = $('span.mention:not(.mention-tested):not(.mention-loading)', $elem);
+    if ($mentions.length) {
+      const usernames = $mentions.map((_, e) => $(e).text().substr(1).toLowerCase());
+
+      if (usernames.length) {
+        $mentions.addClass('mention-loading');
+        const uncached = _.uniq(usernames).filter((u) => { return checked.indexOf(u) === -1; });
+
+        if (uncached.length) {
+          return Discourse.ajax("/users/is_local_username", {
+            data: { usernames: uncached}
+          }).then(function(r) {
+            found.push.apply(found, r.valid);
+            checked.push.apply(checked, uncached);
+            updateFound($mentions, usernames);
+            resolve();
+          });
+        } else {
+          updateFound($mentions, usernames);
+        }
+      }
+    }
+
+    resolve();
+  }).finally(() => { linking = false });
+}
diff --git a/app/assets/javascripts/discourse/lib/mention.js b/app/assets/javascripts/discourse/lib/mention.js
deleted file mode 100644
index 7668d7bbc..000000000
--- a/app/assets/javascripts/discourse/lib/mention.js
+++ /dev/null
@@ -1,59 +0,0 @@
-
-// A local cache lookup
-var localCache = [];
-
-
-/**
-  Lookup a username and return whether it is exists or not.
-
-  @function lookup
-  @param {String} username to look up
-  @return {Promise} promise that results to whether the name was found or not
-**/
-function lookup(username) {
-  return new Em.RSVP.Promise(function (resolve) {
-    var cached = localCache[username];
-
-    // If we have a cached answer, return it
-    if (typeof cached !== "undefined") {
-      resolve(cached);
-    } else {
-      Discourse.ajax("/users/is_local_username", { data: { username: username } }).then(function(r) {
-        localCache[username] = r.valid;
-        resolve(r.valid);
-      });
-    }
-  });
-}
-
-/**
-  Help us link directly to a mentioned user's profile if the username exists.
-
-  @class Mention
-  @namespace Discourse
-  @module Discourse
-**/
-Discourse.Mention = {
-
-  /**
-    Paints an element in the DOM with the appropriate classes and markup if the username
-    it is mentioning exists.
-
-    @method paint
-    @param {Element} the element in the DOM to decorate
-  **/
-  paint: function(e) {
-    var $elem = $(e);
-    if ($elem.data('mention-tested')) return;
-    var username = $elem.text().substr(1);
-
-    $elem.addClass('mention-loading');
-    lookup(username).then(function(found) {
-      if (found) {
-        $elem.replaceWith("<a href='" + Discourse.getURL("/users/") + username.toLowerCase() + "' class='mention'>@" + username + "</a>");
-      } else {
-        $elem.removeClass('mention-loading').addClass('mention-tested');
-      }
-    });
-  }
-};
diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6
index 6c6ab4ea6..03b5c50eb 100644
--- a/app/assets/javascripts/discourse/views/composer.js.es6
+++ b/app/assets/javascripts/discourse/views/composer.js.es6
@@ -3,6 +3,7 @@ import afterTransition from 'discourse/lib/after-transition';
 import loadScript from 'discourse/lib/load-script';
 import avatarTemplate from 'discourse/lib/avatar-template';
 import positioningWorkaround from 'discourse/lib/safari-hacks';
+import linkMentions from 'discourse/lib/link-mentions';
 
 const ComposerView = Discourse.View.extend(Ember.Evented, {
   _lastKeyTimeout: null,
@@ -175,11 +176,10 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
     $('a.onebox', $wmdPreview).each(function(i, e) {
       Discourse.Onebox.load(e, refresh);
     });
-    $('span.mention', $wmdPreview).each(function(i, e) {
-      Discourse.Mention.paint(e);
-    });
 
-    this.trigger('previewRefreshed', $wmdPreview);
+    linkMentions($wmdPreview).then(() => {
+      this.trigger('previewRefreshed', $wmdPreview);
+    });
   },
 
   _applyEmojiAutocomplete() {
diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js
index cf5bbda7b..9a3b8c092 100644
--- a/app/assets/javascripts/main_include.js
+++ b/app/assets/javascripts/main_include.js
@@ -48,6 +48,7 @@
 //= require ./discourse/components/dropdown-button
 //= require ./discourse/components/notifications-button
 //= require ./discourse/components/topic-notifications-button
+//= require ./discourse/lib/link-mentions
 //= require ./discourse/views/composer
 //= require ./discourse/lib/show-modal
 //= require ./discourse/routes/discourse
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 13bf9b282..3a10834b8 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -175,10 +175,12 @@ class UsersController < ApplicationController
   end
 
   def is_local_username
-    params.require(:username)
-    u = params[:username].downcase
-    r = User.exec_sql('select 1 from users where username_lower = ?', u).values
-    render json: {valid: r.length == 1}
+    users = params[:usernames]
+    users = [params[:username]] if users.blank?
+    users.each(&:downcase!)
+
+    result = User.where(username_lower: users).pluck(:username_lower)
+    render json: {valid: result}
   end
 
   def render_available_true