diff --git a/db/migrate/20160509232726_cleanup_duplicates_and_add_unique_indexes.rb b/db/migrate/20160509232726_cleanup_duplicates_and_add_unique_indexes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4f5750a3e2dd49390013307fbad2fef942993359
--- /dev/null
+++ b/db/migrate/20160509232726_cleanup_duplicates_and_add_unique_indexes.rb
@@ -0,0 +1,66 @@
+class CleanupDuplicatesAndAddUniqueIndexes < ActiveRecord::Migration
+  class Post < ActiveRecord::Base
+  end
+
+  class StatusMessage < Post
+  end
+
+  class Photo < ActiveRecord::Base
+    belongs_to :status_message, foreign_key: :status_message_guid, primary_key: :guid
+  end
+
+  class ShareVisibility < ActiveRecord::Base
+  end
+
+  def up
+    # temporary index to speed up the migration
+    add_index :photos, :guid, length: 191
+
+    # fix share visibilities for private photos
+    if AppConfig.postgres?
+      execute "UPDATE share_visibilities" \
+              " SET shareable_id = (SELECT MIN(p3.id) FROM photos as p3 WHERE p3.guid = p1.guid)" \
+              " FROM photos as p1, photos as p2" \
+              " WHERE p1.id = share_visibilities.shareable_id AND (p1.guid = p2.guid AND p1.id > p2.id)" \
+              " AND share_visibilities.shareable_type = 'Photo'"
+    else
+      execute "UPDATE share_visibilities" \
+              " INNER JOIN photos as p1 ON p1.id = share_visibilities.shareable_id" \
+              " INNER JOIN photos as p2 ON p1.guid = p2.guid AND p1.id > p2.id" \
+              " SET share_visibilities.shareable_id = (SELECT MIN(p3.id) FROM photos as p3 WHERE p3.guid = p1.guid)" \
+              " WHERE share_visibilities.shareable_type = 'Photo'"
+    end
+
+    %i(conversations messages photos polls poll_answers poll_participations).each do |table|
+      delete_duplicates_and_create_unique_index(table)
+    end
+
+    # fix photo public flag again ...
+    Photo.joins(:status_message).where(posts: {public: true}).update_all(public: true)
+
+    ShareVisibility.joins("INNER JOIN photos ON photos.id = share_visibilities.shareable_id")
+                   .where(shareable_type: "Photo", photos: {public: true}).delete_all
+  end
+
+  def down
+    raise ActiveRecord::IrreversibleMigration
+  end
+
+  private
+
+  def delete_duplicates_and_create_unique_index(table)
+    # temporary index to speed up the migration
+    add_index table, :guid, length: 191 unless table == :photos
+
+    if AppConfig.postgres?
+      execute "DELETE FROM #{table} AS t1 USING #{table} AS t2 WHERE t1.guid = t2.guid AND t1.id > t2.id"
+    else
+      execute "DELETE t1 FROM #{table} t1, #{table} t2 WHERE t1.guid = t2.guid AND t1.id > t2.id"
+    end
+
+    remove_index table, column: :guid
+
+    # now create unique index \o/
+    add_index table, :guid, length: 191, unique: true
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 382d7ef2bb82d14b0d22de374098e5e73b06c783..41c0ddf5c50c472c7450f4aa7ed277334f751bb5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -149,6 +149,7 @@ ActiveRecord::Schema.define(version: 20160531170531) do
   end
 
   add_index "conversations", ["author_id"], name: "conversations_author_id_fk", using: :btree
+  add_index "conversations", ["guid"], name: "index_conversations_on_guid", unique: true, length: {"guid"=>191}, using: :btree
 
   create_table "id_tokens", force: :cascade do |t|
     t.integer  "authorization_id", limit: 4
@@ -231,6 +232,7 @@ ActiveRecord::Schema.define(version: 20160531170531) do
 
   add_index "messages", ["author_id"], name: "index_messages_on_author_id", using: :btree
   add_index "messages", ["conversation_id"], name: "messages_conversation_id_fk", using: :btree
+  add_index "messages", ["guid"], name: "index_messages_on_guid", unique: true, length: {"guid"=>191}, using: :btree
 
   create_table "notification_actors", force: :cascade do |t|
     t.integer  "notification_id", limit: 4
@@ -360,6 +362,7 @@ ActiveRecord::Schema.define(version: 20160531170531) do
     t.integer  "width",               limit: 4
   end
 
+  add_index "photos", ["guid"], name: "index_photos_on_guid", unique: true, length: {"guid"=>191}, using: :btree
   add_index "photos", ["status_message_guid"], name: "index_photos_on_status_message_guid", length: {"status_message_guid"=>191}, using: :btree
 
   create_table "pods", force: :cascade do |t|
@@ -389,6 +392,7 @@ ActiveRecord::Schema.define(version: 20160531170531) do
     t.integer "vote_count", limit: 4,   default: 0
   end
 
+  add_index "poll_answers", ["guid"], name: "index_poll_answers_on_guid", unique: true, length: {"guid"=>191}, using: :btree
   add_index "poll_answers", ["poll_id"], name: "index_poll_answers_on_poll_id", using: :btree
 
   create_table "poll_participations", force: :cascade do |t|
@@ -401,6 +405,7 @@ ActiveRecord::Schema.define(version: 20160531170531) do
     t.datetime "updated_at"
   end
 
+  add_index "poll_participations", ["guid"], name: "index_poll_participations_on_guid", unique: true, length: {"guid"=>191}, using: :btree
   add_index "poll_participations", ["poll_id"], name: "index_poll_participations_on_poll_id", using: :btree
 
   create_table "polls", force: :cascade do |t|
@@ -412,6 +417,7 @@ ActiveRecord::Schema.define(version: 20160531170531) do
     t.datetime "updated_at"
   end
 
+  add_index "polls", ["guid"], name: "index_polls_on_guid", unique: true, length: {"guid"=>191}, using: :btree
   add_index "polls", ["status_message_id"], name: "index_polls_on_status_message_id", using: :btree
 
   create_table "posts", force: :cascade do |t|