Use case: Selective caching of data for performance tweaking
The Unity Cloud Assets package exposes advanced controls for batching entity data when possible.
How do I...?
Control entity caching
Each entity exposes a cache configuration. The root configuration is defined by AssetRepositoryCacheConfiguration and must be set on creation of the IAssetRepository.
public IAssetRepository StartTransformationOnDataset(IServiceHttpClient serviceHttpClient, IServiceHostResolver serviceHostResolver)
{
var cacheConfiguration = new AssetRepositoryCacheConfiguration
{
FieldDefinitionCacheConfiguration = new FieldDefinitionCacheConfiguration(),
LabelCacheConfiguration = new LabelCacheConfiguration(),
AssetProjectCacheConfiguration = new AssetProjectCacheConfiguration(),
AssetCollectionCacheConfiguration = new AssetCollectionCacheConfiguration(),
AssetCacheConfiguration = new AssetCacheConfiguration
{
DatasetCacheConfiguration = new DatasetCacheConfiguration
{
FileCacheConfiguration = new FileCacheConfiguration()
}
},
TransformationCacheConfiguration = new TransformationCacheConfiguration()
};
return AssetRepositoryFactory.Create(serviceHttpClient, serviceHostResolver, cacheConfiguration);
}
A common parameter among cache configurations is CacheProperties.
This parameter controls whether the properties of an entity will be cached when the entity is requested and more specifically whether the GetPropertiesAsync(CancellationToken) method of an entity will trigger an HTTP request or return the cached data.
Change an entity's configuration
Entities expose their current cache configuration through the CacheConfiguration property.
A new configuration can be defined and assigned using the WithConfigurationAsync method available in each entity.
This method will return a new entity with its data cached as defined by the provided configuration.
For example, an IAsset could be configured to cache the list of datasets, files and file download urls so that subsequent calls to the entity do not require HTTP calls.
public async IAsyncEnumerable<Uri> ListFileDownloadUrlsAsync(IAsset asset, [EnumeratorCancellation] CancellationToken cancellationToken)
{
AssetCacheConfiguration cacheConfiguration = new AssetCacheConfiguration()
{
CacheProperties = false,
CacheMetadata = false,
CacheSystemMetadata = false,
CacheDatasetList = true,
DatasetCacheConfiguration = new DatasetCacheConfiguration()
{
CacheProperties = false,
CacheMetadata = false,
CacheFileList = true,
FileCacheConfiguration = new FileCacheConfiguration()
{
CacheProperties = false,
CacheMetadata = false,
CacheDownloadUrl = true
}
}
};
// This will trigger a HTTP call and populate asset information including the dataset list, file list, and download url for each file.
IAsset assetWithCachedFileUrls = await asset.WithCacheConfigurationAsync(cacheConfiguration, cancellationToken);
// This will not require a HTTP call because the dataset list has been cached.
await foreach (IDataset dataset in assetWithCachedFileUrls.ListDatasetsAsync(Range.All, cancellationToken))
{
// This will not require a HTTP call because the file list has been cached.
await foreach (IFile file in dataset.ListFilesAsync(Range.All, cancellationToken))
{
// This will not require a HTTP call because the download url has been cached.
yield return await file.GetDownloadUrlAsync(cancellationToken);
}
}
}
Configure a query builder
The AssetQueryBuilder and VersionQueryBuilder each expose a WithCacheConfiguration(AssetCacheConfiguration) for defining the cache configuration of each IAsset result.
Similarly, the LabelQueryBuilder, FieldDefinitionQueryBuilder, AssetProjectQueryBuilder, CollectionQueryBuilder, and TransformationQueryBuilder each expose a WithCacheConfiguration method for defining the cache configuration of their results.
Comparing caching strategies
There are many ways to set up the cache configurations of entities to manipulate the balance between request speed and number. Requesting all data upfront will result in fewer downstream calls, however there will be an initial cost to requesting so much data.
public async Task SearchLatestAssetsAsync(IServiceHttpClient serviceHttpClient, IServiceHostResolver serviceHostResolver)
{
// Create an asset repository with no caching by default - setup for asset repositoy should only happen once.
var assetRepository = AssetRepositoryFactory.Create(serviceHttpClient, serviceHostResolver, AssetRepositoryCacheConfiguration.NoCaching);
// Call acts synchronously
var project = await assetRepository.GetAssetProjectAsync(new ProjectDescriptor(new OrganizationId("organization-id"), new ProjectId("project-id")), CancellationToken.None);
// Setup a search query to get all assets with the label "latest"
var searchFilter = new AssetSearchFilter();
searchFilter.Include().Labels.WithValue("latest");
// Setup a cache configuration to cache properties, metadata, and the dataset list
var datasetCacheConfiguration = new DatasetCacheConfiguration
{
CacheProperties = true,
FileCacheConfiguration = FileCacheConfiguration.NoCaching
};
var assetCacheConfiguration = new AssetCacheConfiguration
{
CacheProperties = true,
CacheMetadata = true,
CacheDatasetList = true,
DatasetCacheConfiguration = datasetCacheConfiguration
};
var query = project.QueryAssets()
.SelectWhereMatchesFilter(searchFilter)
.WithCacheConfiguration(assetCacheConfiguration)
.ExecuteAsync(CancellationToken.None);
// Http call to get a paginated result.
await foreach (var asset in query)
{
// Call acts synchronously since properties were cached by the request.
var properties = await asset.GetPropertiesAsync(CancellationToken.None);
Debug.Log(properties.Name);
Debug.Log(string.Join(", ", properties.Tags));
Debug.Log(string.Join(", ", properties.Labels));
// etc.
// Metadata will enumerate synchronously since it was cached by the request.
var metadataQuery = asset.Metadata.Query().ExecuteAsync(CancellationToken.None);
await foreach (var metadata in metadataQuery)
{
Debug.Log($"{metadata.Key}::{metadata.Value.ValueType}");
}
// Dataset list will enumerate synchronously since it was cached by the request.
await foreach (var dataset in asset.ListDatasetsAsync(Range.All, CancellationToken.None))
{
// Call acts synchronously since properties were cached by the request.
var datasetProperties = await dataset.GetPropertiesAsync(CancellationToken.None);
Debug.Log(datasetProperties.Name);
// etc.
}
}
}
public async Task GetFileInfoAsync(IServiceHttpClient serviceHttpClient, IServiceHostResolver serviceHostResolver)
{
// Create an asset repository with no caching by default - setup for asset repositoy should only happen once.
var assetRepository = AssetRepositoryFactory.Create(serviceHttpClient, serviceHostResolver, AssetRepositoryCacheConfiguration.NoCaching);
// Call acts synchronously since we are not caching any properties.
var project = await assetRepository.GetAssetProjectAsync(new ProjectDescriptor( /* project info here */), CancellationToken.None);
// Call will act asynchronously because we need to fetch the `AssetVersion` of the asset.
var asset = await project.GetAssetAsync(new AssetId("asset-id"), "Latest", CancellationToken.None);
// Call acts synchronously since we are not caching any properties.
var dataset = await asset.GetDatasetAsync(new DatasetId("dataset-id"), CancellationToken.None);
//
// Scenario 1. No caching
//
// Call acts synchronously since we are not caching any properties.
var file = await dataset.GetFileAsync("file-path", CancellationToken.None);
// Call is asynchronous since we have not cached any properties.
var properties = await file.GetPropertiesAsync(CancellationToken.None);
Debug.Log(properties.SizeBytes);
Debug.Log(string.Join(", ", properties.Tags));
// etc.
// Call is asynchronous since we have not cached any properties.
var downloadUrl = await file.GetDownloadUrlAsync(CancellationToken.None);
Debug.Log(downloadUrl);
// Call is asynchronous since we have not cached any properties.
var previewUrl = await file.GetPreviewUrlAsync(CancellationToken.None);
Debug.Log(previewUrl);
//
// Scenario 2. Cache all
//
var fileCacheConfiguration = new FileCacheConfiguration
{
CacheProperties = true,
CacheDownloadUrl = true,
CachePreviewUrl = true
};
var datasetCacheConfiguration = new DatasetCacheConfiguration
{
FileCacheConfiguration = fileCacheConfiguration
};
// Here we fetch a new dataset with the specified configuration.
// Call acts synchronously since we are not caching anything in the dataset directly.
dataset = await dataset.WithCacheConfigurationAsync(datasetCacheConfiguration, CancellationToken.None);
// Call acts asynchronously since the new configuration requires caching of certain fields.
file = await dataset.GetFileAsync("file-path", CancellationToken.None);
// Call is synchronously since we have cached the properties.
properties = await file.GetPropertiesAsync(CancellationToken.None);
Debug.Log(properties.SizeBytes);
Debug.Log(string.Join(", ", properties.Tags));
// etc.
// Call is synchronously since we have cached the download url.
downloadUrl = await file.GetDownloadUrlAsync(CancellationToken.None);
Debug.Log(downloadUrl);
// Call is synchronously since we have cached the preview url.
previewUrl = await file.GetPreviewUrlAsync(CancellationToken.None);
Debug.Log(previewUrl);
}
Default SDK cache strategy
A default configuration for caching has been defined to maintain the current flow of HTTP calls. By default, an entity will have its properties cached.
Caching limitations
No caching
When a cache configuration is configured to not cache any data, the requested entity will be returned in a synchronous manner, without performing any HTTP calls. It will act as an empty container from which further actions can be performed.
Nested entities
Due to the nesting of files and datasets within Assets, it is possible to request all files and datasets for an asset in a single HTTP call. However, because there are no limits to how many datasets an asset can contain and how many files each dataset can contain, one must consider the practical limitations of requesting all this information in a single call.
It is therefore advised to use caution when caching all asset data. When there is a risk of asset dataset lists being very large, the asset configuration can be configured to not cache the dataset list, like so:
AssetCacheConfiguration assetCacheConfiguration = new AssetCacheConfiguration
{
CacheProperties = true,
CacheDatasetList = true,
DatasetCacheConfiguration = new DatasetCacheConfiguration
{
CacheProperties = true,
CacheFileList = false,
FileCacheConfiguration = new FileCacheConfiguration
{
CacheProperties = true
}
}
};
Similarly, if there is a risk that dataset file lists are very large, the asset configuration can be configured to not cache the file list, like so:
AssetCacheConfiguration assetCacheConfiguration = new AssetCacheConfiguration
{
CacheProperties = true,
CacheDatasetList = false,
DatasetCacheConfiguration = new DatasetCacheConfiguration
{
CacheProperties = true,
CacheFileList = true,
FileCacheConfiguration = new FileCacheConfiguration
{
CacheProperties = true
}
}
};