У меня есть следующий упрощенный дизайн верхнего уровня для моего проекта Cosmos. Некоторое введение, а затем несколько вопросов. Заранее благодарим за прочтение этого.
У меня есть 3 коллекции: Memberships
, Discounts
и Vouchers
:
В каждой коллекции хранятся разные сущности. Объекты в каждой коллекции не являются одинаковыми, но принадлежат одному разделу и имеют унаследованные свойства:
id
- уникальный идентификатор (требование Космоса ... я думаю)
upsertDate
- обновляется во время upsert, поэтому я могу заказать документы (см. Ниже)
Каждая сущность коллекции имеет:
code
- ключ раздела
type
- дискриминатор для различных сущностей в коллекции.
Есть тысячи членских взносов, ваучеров и скидок. Например, когда система получает код членства, она может быстро найти документ как ключ раздела.
Если это сделать, он возвращает все документы сущности, принадлежащие одному и тому же code
:
SELECT * FROM c WHERE c.code = 'YTR3IO'
Если это сделать, он возвращает все документы сущностей, которые принадлежат тем же code
и type
:
SELECT * FROM c WHERE c.code = 'YTR3IO' and c.type = 10
type
обычно является перечислением для различных сущностей, которые могут храниться в определенной коллекции. Например, в случае членства тип может быть notication
, comment
, request
, transaction
и т. Д.
Причина, по которой у меня есть upsertDate
в каждом документе, чтобы я мог делать такие вещи:
SELECT * FROM c WHERE c.code = 'YTR3IO' AND c.type = 10 ORDER BY c.upsertDate DESC
Для политики индексирования я переопределил политику на то, что мне кажется лучшим:
DocumentCollection collectionDefinition = new DocumentCollection();
collectionDefinition.Id = _docDbMembershipsCollectionName;
collectionDefinition.PartitionKey.Paths.Add("/code");
collectionDefinition.DefaultTimeToLive = -1;
// If queries are known upfront, index just the properties we need
collectionDefinition.IndexingPolicy.Automatic = true;
collectionDefinition.IndexingPolicy.IndexingMode = IndexingMode.Consistent;
collectionDefinition.IndexingPolicy.IncludedPaths.Clear();
IncludedPath path = new IncludedPath();
path.Path = "/code/?";
path.Indexes.Add(new RangeIndex(DataType.String) { Precision = -1 });
collectionDefinition.IndexingPolicy.IncludedPaths.Add(path);
path = new IncludedPath();
path.Path = "/upsertDate/?";
path.Indexes.Add(new RangeIndex(DataType.String) { Precision = -1 });
collectionDefinition.IndexingPolicy.IncludedPaths.Add(path);
path = new IncludedPath();
path.Path = "/type/?";
path.Indexes.Add(new RangeIndex(DataType.Number) { Precision = -1 });
collectionDefinition.IndexingPolicy.IncludedPaths.Add(path);
collectionDefinition.IndexingPolicy.ExcludedPaths.Clear();
collectionDefinition.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/*" });
DocumentCollection ttlEnabledCollection = await
_docDbClient.CreateDocumentCollectionAsync(
UriFactory.CreateDatabaseUri(_docDbDatabaseName), collectionDefinition, new RequestOptions { OfferThroughput = 1000 });
ВОПРОСЫ:
ВОПРОС 1: Если у меня есть запрос, у которого есть переадресация по upsertDate
, стоимость поднимается вверх:
FeedOptions queryOptions = new FeedOptions { MaxItemCount = 100, PartitionKey = new Microsoft.Azure.Documents.PartitionKey(code.ToUpper()) };
var query = this._docDbClient.CreateDocumentQuery<MembershipDeviceRegistrationItem>(
UriFactory.CreateDocumentCollectionUri(_docDbDatabaseName, _docDbMembershipsCollectionName), queryOptions)
.Where(e => e.CollectionType == MembershipCollectionTypes.DeviceRegistrationItem && e.Code.ToUpper() == code.ToUpper())
.OrderByDescending(e => e.UpsertDate)
.AsDocumentQuery();
List<MembershipDeviceRegistrationItem> allDocuments = new List<MembershipDeviceRegistrationItem>();
while (query.HasMoreResults)
{
var queryResult = await query.ExecuteNextAsync<MembershipDeviceRegistrationItem>();
cost += queryResult.RequestCharge;
allDocuments.AddRange(queryResult.ToList());
}
return allDocuments.ToList();
Как я могу использовать upsertDate
, чтобы заказать мои документы и выбрать, например, самые последние из них?
ВОПРОС 2: Как рассчитать стоимость запроса с агрегатом?
FeedOptions queryOptions = new FeedOptions { MaxItemCount = 1, PartitionKey = new Microsoft.Azure.Documents.PartitionKey(code.ToUpper()) };
return _docDbClient.CreateDocumentQuery<int>(
UriFactory.CreateDocumentCollectionUri(_docDbDatabaseName, _docDbMembershipsCollectionName),
new SqlQuerySpec()
{
QueryText = $"SELECT value count(m.id) FROM {_docDbMembershipsCollectionName} m WHERE m.code = @code AND m.collectionType = @type",
Parameters = new SqlParameterCollection()
{
new SqlParameter("@code", code.ToUpper()),
new SqlParameter("@type", MembershipCollectionTypes.DeviceRegistrationItem)
}
}, queryOptions).AsEnumerable().First();
ВОПРОС 3: Мне нужно создать документ Omni, который содержит все о конкретном членстве, и вернуться к какому-либо клиенту, чтобы они могли показать его различные части. Поэтому я создал что-то вроде этого (упрощенно):
FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1, PartitionKey = new Microsoft.Azure.Documents.PartitionKey(code.ToUpper()) };
var query = _docDbClient.CreateDocumentQuery<dynamic>(
UriFactory.CreateDocumentCollectionUri(_docDbDatabaseName, _docDbMembershipsCollectionName),
new SqlQuerySpec()
{
QueryText = $"SELECT * FROM {_docDbMembershipsCollectionName} m WHERE m.code = @code",
Parameters = new SqlParameterCollection()
{
new SqlParameter("@code", code.ToUpper())
}
}, queryOptions).AsDocumentQuery();
List<dynamic> items = new List<dynamic>();
while (query.HasMoreResults)
{
var queryResult = await query.ExecuteNextAsync();
cost += queryResult.RequestCharge;
items.AddRange(queryResult.ToList());
}
var omni = new DigitalMembershipOmni();
foreach (var item in items)
{
var collectionType = (MembershipCollectionTypes)item.collectionType;
if (collectionType == MembershipCollectionTypes.Membership)
{
omni.Clone((DigitalMembership)item);
}
else if (collectionType == MembershipCollectionTypes.RefreshItem)
{
omni.RefreshItems.Add((DigitalMembershipRefreshItem)item);
}
else if (collectionType == MembershipCollectionTypes.OfferItem)
{
omni.OfferItems.Add((DigitalMembershipOfferItem)item);
}
else if (collectionType == MembershipCollectionTypes.TrackingTransactionItem)
{
omni.TrackingTransactionItems.Add((DigitalMembershipTrackingTransactionItem)item);
}
else if (collectionType == MembershipCollectionTypes.BookingTransactionItem)
{
omni.BookingTransactionItems.Add((DigitalMembershipBookingTransactionItem)item);
}
else if (collectionType == MembershipCollectionTypes.MembershipTransactionItem)
{
omni.MembershipTransactionItems.Add((DigitalMembershipTransactionItem)item);
}
else if (collectionType == DigitalMembershipCollectionTypes.NotificationItem)
{
omni.NotificationItems.Add((DigitalMembershipNotificationItem)item);
}
else if (collectionType == DigitalMembershipCollectionTypes.CommentItem)
{
omni.CommentItems.Add((DigitalMembershipCommentItem)item);
}
else if (collectionType == DigitalMembershipCollectionTypes.ContactUsItem)
{
omni.ContactUsItems.Add((DigitalMembershipContactUsItem)item);
}
else if (collectionType == DigitalMembershipCollectionTypes.ReferralItem)
{
omni.ReferralItems.Add((DigitalMembershipReferralItem)item);
}
else if (collectionType == DigitalMembershipCollectionTypes.EcertPinItem)
{
omni.EcertPinItems.Add((DigitalMembershipEcertPinItem)item);
}
else if (collectionType == DigitalMembershipCollectionTypes.ProfileUpdateItem)
{
omni.ProfileUpdateItems.Add((DigitalMembershipProfileUpdateItem)item);
}
}
return omni;
Вышеописанное работает .... но это дорого ... и ... оно плохо масштабируется .... поэтому, если членство накапливает больше данных, стоимость значительно возрастет. Я попытался сделать запрос для каждого типа, указав лучшие элементы для создания, такие как топ-100 уведомлений ... но порядок по upsertDate
(как отмечено выше) делает запросы очень дорогими .... поэтому общая стоимость даже наихудший. Как можно достичь чего-то подобного с разумной стоимостью и производительностью.
ВОПРОС 4: У меня есть свойство membershipCode
в коллекциях vouchers
и discounts
, как я могу выполнить запрос JOIN, который собирает ваучеры, принадлежащие определенному членству, не исчерпывая мои RU и не убивая моего спектакль? Я думаю, что запросы JOIN закончат сканирование всей коллекции vouchers
с разными разделами и могут иметь значительные затраты и проблемы с пропускной способностью. Единственный способ заставить его работать достаточно хорошо - это использовать Table DB для связи vouchers
с memberships
. Запросы к Таблице БД выполняются быстро ... но это добавило существенное усложнение для моего небольшого проекта.
Я ценю любой указатель.
С уважением