Skip to content

Commit 1dbaa4c

Browse files
authored
Merge pull request #14 from 8JP8/fix-cosmos-index-errors-4898497408058730464
Fix Cosmos DB index creation with non-unique fallback
2 parents 24f09af + e1de0a6 commit 1dbaa4c

1 file changed

Lines changed: 91 additions & 67 deletions

File tree

backend/app.py

Lines changed: 91 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -545,21 +545,72 @@ def create_unique_indexes_batch(collection, keys):
545545
"""
546546
Create multiple unique indexes at once.
547547
Required for Cosmos DB which mandates unique indexes at collection creation.
548+
If unique creation fails due to Cosmos DB restriction, fallback to non-unique indexes.
548549
"""
549550
try:
550551
indexes = [IndexModel([(key, ASCENDING)], unique=True) for key in keys]
551552
collection.create_indexes(indexes)
552553
except Exception as e:
553554
# Check for Cosmos DB "unique index cannot be modified" error (Code 13)
554-
if hasattr(e, 'code') and e.code == 13:
555+
if (hasattr(e, 'code') and e.code == 13) or "The unique index cannot be modified" in str(e):
555556
logging.getLogger(__name__).warning(
556-
f"Skipped unique indexes on {collection.name} ({keys}) due to Cosmos DB restriction. "
557-
"Unique constraint may not be enforced if collection was created without it. "
558-
"To fix: Delete collection '{collection.name}' in Azure Portal and restart."
557+
f"Cosmos DB restriction: Cannot create unique indexes on {collection.name} ({keys}) because collection already exists. "
558+
"Falling back to non-unique indexes to ensure performance."
559559
)
560+
try:
561+
# Fallback: Create regular indexes so queries are still fast
562+
indexes = [IndexModel([(key, ASCENDING)], unique=False) for key in keys]
563+
collection.create_indexes(indexes)
564+
logging.getLogger(__name__).info(f"Successfully created fallback non-unique indexes on {collection.name}")
565+
except Exception as fallback_error:
566+
logging.getLogger(__name__).warning(f"Failed to create fallback indexes on {collection.name}: {fallback_error}")
560567
else:
561568
logging.getLogger(__name__).warning(f"Failed to create unique indexes on {collection.name}: {e}")
562569

570+
# Helper to safely create a single unique index with robust fallback
571+
def create_index_with_fallback(collection, keys, unique=True, **kwargs):
572+
"""
573+
Safely create an index.
574+
1. Try to create with desired options (e.g. unique=True).
575+
2. If 'already exists with different options', drop and retry.
576+
3. If Cosmos DB error (Code 13), fallback to unique=False.
577+
"""
578+
try:
579+
collection.create_index(keys, unique=unique, **kwargs)
580+
except Exception as e:
581+
error_msg = str(e)
582+
583+
# Case 1: Index exists with different options -> Drop and Retry
584+
if "already exists with different options" in error_msg:
585+
try:
586+
logging.getLogger(__name__).warning(f"Index conflict on {collection.name}. Dropping and recreating...")
587+
# PyMongo allows dropping by key specification
588+
collection.drop_index(keys)
589+
# Retry creation (recursive call to handle potential Code 13 on retry)
590+
create_index_with_fallback(collection, keys, unique=unique, **kwargs)
591+
return
592+
except Exception as drop_error:
593+
logging.getLogger(__name__).warning(f"Failed to drop/recreate index on {collection.name}: {drop_error}")
594+
# If drop failed, we can't do much, but maybe we can try fallback if it was a uniqueness issue?
595+
# Unlikely if drop failed.
596+
return
597+
598+
# Case 2: Cosmos DB "unique index cannot be modified" (Code 13)
599+
if unique and ((hasattr(e, 'code') and e.code == 13) or "The unique index cannot be modified" in error_msg):
600+
logging.getLogger(__name__).warning(
601+
f"Cosmos DB restriction: Cannot create unique index on {collection.name}. "
602+
"Falling back to non-unique index."
603+
)
604+
try:
605+
collection.create_index(keys, unique=False, **kwargs)
606+
logging.getLogger(__name__).info(f"Successfully created fallback non-unique index on {collection.name}")
607+
except Exception as fallback_error:
608+
logging.getLogger(__name__).warning(f"Failed to create fallback index on {collection.name}: {fallback_error}")
609+
return
610+
611+
# Other errors
612+
logging.getLogger(__name__).warning(f"Failed to create index on {collection.name}: {e}")
613+
563614
# Users collection indexes - Batch creation for unique constraints
564615
create_unique_indexes_batch(db.users, ["username", "email"])
565616

@@ -658,80 +709,49 @@ def create_unique_indexes_batch(collection, keys):
658709
db.tickets.create_index("reviewed_by")
659710

660711
# Conversation settings collection indexes (Upgrade to partial indexes)
661-
try:
662-
db.conversation_settings.create_index(
663-
[("user_id", 1), ("other_user_id", 1)],
664-
unique=True,
665-
partialFilterExpression={"other_user_id": {"$exists": True}}
666-
)
667-
except Exception as e:
668-
if "already exists with different options" in str(e):
669-
logger.warning("Index conflict detected. Dropping old index: user_id_1_other_user_id_1")
670-
db.conversation_settings.drop_index("user_id_1_other_user_id_1")
671-
db.conversation_settings.create_index(
672-
[("user_id", 1), ("other_user_id", 1)],
673-
unique=True,
674-
partialFilterExpression={"other_user_id": {"$exists": True}}
675-
)
676-
else:
677-
logger.warning(f"Index creation failed for user_id_1_other_user_id_1: {e}")
678-
679-
try:
680-
db.conversation_settings.create_index(
681-
[("user_id", 1), ("topic_id", 1), ("type", 1)],
682-
unique=True,
683-
partialFilterExpression={"topic_id": {"$exists": True}}
684-
)
685-
except Exception as e:
686-
if "already exists with different options" in str(e):
687-
logger.warning("Index conflict detected. Dropping old index: user_id_1_topic_id_1_type_1")
688-
db.conversation_settings.drop_index("user_id_1_topic_id_1_type_1")
689-
db.conversation_settings.create_index(
690-
[("user_id", 1), ("topic_id", 1), ("type", 1)],
691-
unique=True,
692-
partialFilterExpression={"topic_id": {"$exists": True}}
693-
)
694-
else:
695-
logger.warning(f"Index creation failed for user_id_1_topic_id_1_type_1: {e}")
696-
697-
try:
698-
db.conversation_settings.create_index(
699-
[("user_id", 1), ("chat_room_id", 1), ("type", 1)],
700-
unique=True,
701-
partialFilterExpression={"chat_room_id": {"$exists": True}}
702-
)
703-
except Exception as e:
704-
if "already exists with different options" in str(e):
705-
logger.warning("Index conflict detected. Dropping old index: user_id_1_chat_room_id_1_type_1")
706-
db.conversation_settings.drop_index("user_id_1_chat_room_id_1_type_1")
707-
db.conversation_settings.create_index(
708-
[("user_id", 1), ("chat_room_id", 1), ("type", 1)],
709-
unique=True,
710-
partialFilterExpression={"chat_room_id": {"$exists": True}}
711-
)
712-
else:
713-
logger.warning(f"Index creation failed for user_id_1_chat_room_id_1_type_1: {e}")
712+
create_index_with_fallback(
713+
db.conversation_settings,
714+
[("user_id", 1), ("other_user_id", 1)],
715+
unique=True,
716+
partialFilterExpression={"other_user_id": {"$exists": True}}
717+
)
718+
create_index_with_fallback(
719+
db.conversation_settings,
720+
[("user_id", 1), ("topic_id", 1), ("type", 1)],
721+
unique=True,
722+
partialFilterExpression={"topic_id": {"$exists": True}}
723+
)
724+
create_index_with_fallback(
725+
db.conversation_settings,
726+
[("user_id", 1), ("chat_room_id", 1), ("type", 1)],
727+
unique=True,
728+
partialFilterExpression={"chat_room_id": {"$exists": True}}
729+
)
714730
db.conversation_settings.create_index("type")
715731

716732
# Notification settings collection indexes
717-
db.notification_settings.create_index(
733+
create_index_with_fallback(
734+
db.notification_settings,
718735
[("user_id", 1), ("post_id", 1), ("type", 1)],
719-
unique=True,
736+
unique=True,
720737
partialFilterExpression={"type": "post"}
721738
)
722-
db.notification_settings.create_index(
739+
create_index_with_fallback(
740+
db.notification_settings,
723741
[("user_id", 1), ("chat_room_id", 1), ("type", 1)],
724-
unique=True,
742+
unique=True,
725743
partialFilterExpression={"type": "chat_room"}
726744
)
727-
db.notification_settings.create_index(
745+
create_index_with_fallback(
746+
db.notification_settings,
728747
[("user_id", 1), ("topic_id", 1), ("type", 1)],
729-
unique=True,
748+
unique=True,
730749
partialFilterExpression={"type": "topic"}
731750
)
732-
db.notification_settings.create_index(
751+
create_index_with_fallback(
752+
db.notification_settings,
733753
[("user_id", 1), ("other_user_id", 1), ("type", 1)],
734-
unique=True,
754+
unique=True,
735755
partialFilterExpression={"type": "private_message"}
736756
)
737757
db.notification_settings.create_index("type")
@@ -742,7 +762,11 @@ def create_unique_indexes_batch(collection, keys):
742762
db.private_messages.create_index("created_at")
743763

744764
# Anonymous identities collection indexes
745-
db.anonymous_identities.create_index([("user_id", 1), ("topic_id", 1)], unique=True)
765+
create_index_with_fallback(
766+
db.anonymous_identities,
767+
[("user_id", 1), ("topic_id", 1)],
768+
unique=True
769+
)
746770

747771
# Friends collection indexes (for Azure Cosmos DB compatibility)
748772
try:
@@ -761,7 +785,7 @@ def create_unique_indexes_batch(collection, keys):
761785
logger.warning(f"Failed to create some comments indexes (may already exist): {e}")
762786

763787
# Short Links collection
764-
db.short_links.create_index("code", unique=True)
788+
create_index_with_fallback(db.short_links, "code", unique=True)
765789
db.short_links.create_index("created_at")
766790

767791
logger.info("Database indexes created successfully")

0 commit comments

Comments
 (0)