Persistenza granulare ADO.NET
Il codice back-end di archiviazione relazionale in Orleans è basato su funzionalità generiche di ADO.NET ed è di conseguenza indipendente dal fornitore di database. Il layout dell'archiviazione dei dati Orleans è già stato illustrato nelle tabelle di runtime. La configurazione delle stringhe di connessione viene eseguita come illustrato nella OrleansGuida alla configurazione.
Per rendere Orleans la funzione di codice con un back-end di database relazionale specifico, è necessario quanto segue:
- La libreria di ADO.NET appropriata deve essere caricata nel processo. Questa operazione deve essere definita come di consueto, ad esempio, tramite l'elemento DbProviderFactories nella configurazione dell'applicazione.
- Configurare l‘ADO.NET invariante tramite la proprietà
Invariant
nelle opzioni. - Il database deve esistere ed essere compatibile con il codice. A tale scopo, eseguire uno script di creazione del database specifico del fornitore. Per ulteriori informazioni, consultare Configurazione ADO.NET.
Il provider di archiviazione granulare ADO .NET consente di archiviare lo stato granulare nei database relazionali. Attualmente, sono supportati i seguenti database:
- SQL Server
- MySQL/MariaDB
- PostgreSQL
- Oracle
Prima di tutto, installare il pacchetto di base:
Install-Package Microsoft.Orleans.Persistence.AdoNet
Consultare l'articolo configurazione ADO.NET per informazioni sulla configurazione del database, inclusi gli ADO.NET invarianti corrispondenti e gli script di installazione.
Di seguito è riportato un esempio di come configurare un provider di archiviazione ADO.NET tramite ISiloHostBuilder:
var siloHostBuilder = new HostBuilder()
.UseOrleans(c =>
{
c.AddAdoNetGrainStorage("OrleansStorage", options =>
{
options.Invariant = "<Invariant>";
options.ConnectionString = "<ConnectionString>";
options.UseJsonFormat = true;
});
});
Essenzialmente, è sufficiente impostare la stringa di connessione specifica del fornitore del database e un Invariant
(vedere Configurazione ADO.NET) che identifica il fornitore. È anche possibile scegliere il formato in cui i dati vengono salvati, che può essere binario (impostazione predefinita), JSON o XML. Mentre il formato binario è l'opzione più compatta, è un formato opaco e non rende possibile leggere o lavorare con i dati. JSON è l'opzione consigliata.
È possibile impostare le seguenti proprietà tramite AdoNetGrainStorageOptions:
/// <summary>
/// Options for AdoNetGrainStorage
/// </summary>
public class AdoNetGrainStorageOptions
{
/// <summary>
/// Define the property of the connection string
/// for AdoNet storage.
/// </summary>
[Redact]
public string ConnectionString { get; set; }
/// <summary>
/// Set the stage of the silo lifecycle where storage should
/// be initialized. Storage must be initialized prior to use.
/// </summary>
public int InitStage { get; set; } = DEFAULT_INIT_STAGE;
/// <summary>
/// Default init stage in silo lifecycle.
/// </summary>
public const int DEFAULT_INIT_STAGE =
ServiceLifecycleStage.ApplicationServices;
/// <summary>
/// The default ADO.NET invariant will be used for
/// storage if none is given.
/// </summary>
public const string DEFAULT_ADONET_INVARIANT =
AdoNetInvariants.InvariantNameSqlServer;
/// <summary>
/// Define the invariant name for storage.
/// </summary>
public string Invariant { get; set; } =
DEFAULT_ADONET_INVARIANT;
/// <summary>
/// Determine whether the storage string payload should be formatted in JSON.
/// <remarks>If neither <see cref="UseJsonFormat"/> nor <see cref="UseXmlFormat"/> is set to true, then BinaryFormatSerializer will be configured to format the storage string payload.</remarks>
/// </summary>
public bool UseJsonFormat { get; set; }
public bool UseFullAssemblyNames { get; set; }
public bool IndentJson { get; set; }
public TypeNameHandling? TypeNameHandling { get; set; }
public Action<JsonSerializerSettings> ConfigureJsonSerializerSettings { get; set; }
/// <summary>
/// Determine whether storage string payload should be formatted in Xml.
/// <remarks>If neither <see cref="UseJsonFormat"/> nor <see cref="UseXmlFormat"/> is set to true, then BinaryFormatSerializer will be configured to format storage string payload.</remarks>
/// </summary>
public bool UseXmlFormat { get; set; }
}
La persistenza ADO.NET ha la funzionalità della versione dei dati e definisce (de)serializzatori arbitrari con regole di applicazione arbitrarie e flussi, ma attualmente non esiste alcun metodo per esporre ciò al codice dell'applicazione.
Logica di persistenza ADO.NET
I principi per l’archiviazione di persistenza supportata di ADO.NET sono i seguenti:
- Mantenere i dati aziendali sicuri e accessibili mentre i dati, il formato dei dati e il codice evolvono.
- Sfruttare le funzionalità specifiche del fornitore e dell'archiviazione.
In pratica, ciò consiste nell’aderire agli obiettivi di implementazione ADO.NET e ad alcune logiche di implementazione aggiunte in provider di archiviazione specifici di ADO.NET che consentono l'evoluzione della forma dei dati nell'archiviazione.
Oltre alle normali funzionalità del provider di archiviazione, il provider di ADO.NET offre funzionalità predefinite per:
- Modificare i dati di archiviazione da un formato a un altro (ad esempio da JSON a binario) quando si effettua il round trip dello stato.
- Modellare il tipo da salvare o leggere dalla risorsa di archiviazione in modi arbitrari. Questo permette alla versione dello stato di evolversi.
- Trasmettere dati dal database.
Sia 1.
che 2.
possono essere applicati in base a parametri decisionali arbitrari, ad esempio ID granulare, tipo di granularità, dati di payload.
Questo rende possibile scegliere un formato di serializzazione, ad esempio Codifica binaria semplice (SBE), e implementa IStorageDeserializer e IStorageSerializer. I serializzatori predefiniti sono stati compilati usando questo metodo:
- OrleansStorageDefaultXmlSerializer
- OrleansStorageDefaultXmlDeserializer
- OrleansStorageDefaultJsonSerializer
- OrleansStorageDefaultJsonDeserializer
- OrleansStorageDefaultBinarySerializer
- OrleansStorageDefaultBinaryDeserializer
Una volta che i serializzatori sono stati implementati, è necessario aggiungerli alla proprietà StorageSerializationPicker in AdoNetGrainStorage. Ecco un'implementazione di IStorageSerializationPicker
. Verrà usata l'impostazione predefinita StorageSerializationPicker
. Un esempio di modifica del formato di archiviazione dei dati o dell'uso dei serializzatori può essere visto in RelationalStorageTests.
Attualmente, non esiste alcun metodo per esporre la selezione della serializzazione all'applicazione Orleans, poiché non esiste alcun metodo per accedere al AdoNetGrainStorage
creato dal framework.
Obiettivi della progettazione
1. Consentire l'uso di qualsiasi back-end con un provider di ADO.NET
Questo dovrebbe coprire un set più ampio possibile di back-end disponibili per .NET, un fattore nelle installazioni locali. Alcuni provider sono elencati in Panoramica ADO.NET, ma alcuni, come Teradata, non fanno parte dell’elenco.
2. Mantenere la pssibilità di ottimizzare le query e la struttura del database in base alle esigenze, anche durante l'esecuzione di una distribuzione
In molti casi, i server e i database sono ospitati da terzi in relazione contrattuale con il client. Non è insolito trovare un ambiente di hosting virtualizzato dove le prestazioni fluttuano a causa di fattori imprevisti, come vicini rumorosi o hardware difettoso. Potrebbe non essere possibile modificare e ri-distribuire nuovamente i file binari Orleans (per motivi contrattuali) o anche i file binari dell'applicazione, ma è in genere possibile modificare i parametri di distribuzione del database. La modifica dei componenti standard (ad esempio i file binari Orleans), richiede una procedura più lunga perché possano essere ottimizzati in una determinata situazione.
3. Consente di usare le funzionalità specifiche del fornitore e della versione
I fornitori hanno implementato diverse estensioni e funzionalità all'interno dei loro prodotti. È opportuno usare queste funzionalità quando disponibili. Si tratta di funzionalità come UPSERT nativo o PipelineDB in PostgreSQL e PolyBase o tabelle compilate nativamente e procedure compilate archiviate in SQL Server.
4. 4. Rendere possibile l’ottimizzazione delle risorse hardware
Durante la progettazione di un'applicazione, è spesso possibile prevedere quali dati debbano essere inseriti più velocemente rispetto ad altri dati e quali dati potrebbero più probabilmente essere inseriti in un’archiviazione offline sicura (ad esempio, la suddivisione dei dati tra SSD e HDD). Altri aspetti da tenere in considerazione includono la posizione fisica dei dati (alcuni dati potrebbero essere più costosi, ad esempio, SSD RAID viz HDD RAID) o altre basi decisionali. In relazione al punto 3., alcuni database offrono schemi di partizionamento speciali, come tabelle e indici partizionati di SQL Server.
Questi principi sono applicabili durante tutto il ciclo di vita dell'applicazione. Considerando che uno dei principi di Orleans è la disponibilità elevata, dovrebbe essere possibile modificare il sistema di archiviazione senza interruzioni della distribuzione Orleans o modificare le query in base ai dati e ad altri parametri dell'applicazione. Un esempio di modifiche dinamiche può essere visto nel post di blog di Brian Harry:
Quando una tabella è piccola, non è molto importante quale sia il piano di query. Quando è media, un piano di query accettabile può andare bene, ma quando è enorme (milioni di milioni o miliardi di righe), anche una leggera variazione nel piano di query può essere fatale. Per questo motivo, forniamo massicciamente hint per le nostre query sensibili.
5. Nessun presupposto su quali strumenti, librerie o processi di distribuzione vengano usati nelle organizzazioni
Molte organizzazioni hanno familiarità con un determinato set di strumenti di database, ad esempio Dacpac o Red Gate. È possibile che la distribuzione di un database richieda un’autorizzazione o una persona, come un utente in un ruolo DBA, per eseguire questa operazione. In genere, ciò significa anche avere il layout del database di destinazione e uno schizzo approssimativo delle query che l'applicazione produrrà per l'uso durante la stima del carico. Potrebbero esserci processi, forse influenzati dagli standard del settore, che impongono la distribuzione basata su script. La presenza di query e strutture di database in uno script esterno rende possibile questa operazione.
6. Usare il set minimo di funzionalità di interfaccia necessarie per caricare le librerie e le funzionalità ADO.NET
Questa soluzione è veloce ed espone meno superficie alle discrepanze di implementazione della libreria ADO.NET.
7. Rendere la progettazione partizionabile
Quando appropriato (ad esempio, in un provider di archiviazione relazionale), rendere la progettazione facilmente partizionabile. Ad esempio, ciò significa non usare dati dipendenti dal database (ad esempio, IDENTITY
). Le informazioni che distinguono i dati di riga devono essere basate solo su dati dei parametri effettivi.
8. Semplificare il test della progettazione
Idealmente, la creazione di un nuovo back-end dovrebbe consistere semplicemente in questi passaggi: la conversione di uno degli script di distribuzione esistenti nel dialetto SQL del back-end di destinazione, l'aggiunta di una nuova stringa di connessione ai test (presupponendo parametri predefiniti), la verifica di installazione di un determinato database e quindi eseguire i test su di esso.
9. Tenendo conto dei punti precedenti, creare script di conversione per i nuovi back-end e modificare gli script back-end già distribuiti il più trasparentemente possibile
Realizzazione degli obiettivi
Il framework Orleans non conosce l'hardware specifico della distribuzione (che potrebbe cambiare durante la distribuzione attiva), la modifica dei dati durante il ciclo di vita della distribuzione o alcune funzionalità specifiche del fornitore che sono utilizzabili solo in determinate situazioni. Per questo motivo, l'interfaccia tra il database e Orleans deve rispettare il set minimo di astrazioni e regole per soddisfare questi obiettivi, essere affidabile contro l'uso improprio e, se necessario, semplificare il test. Tabelle di runtime, Gestione cluster e implementazione concreta del protocollo di appartenenza. L'implementazione di SQL Server contiene anche l'ottimizzazione specifica dell'edizione di SQL Server. Il contratto di interfaccia tra il database e Orleans viene definito come segue:
- Il concetto generale è che i dati vengano letti e scritti tramite query specifiche di Orleans. Orleans opera su nomi e tipi di colonna durante la lettura e sui nomi e tipi di parametri durante la scrittura.
- Le implementazioni devono necessariamente mantenere i nomi e i tipi di input e output. Orleans usa questi parametri per leggere i risultati della query in base al nome e al tipo. L'ottimizzazione specifica del fornitore e della distribuzione è consentita e i contributi sono incoraggiati a condizione che il contratto di interfaccia venga mantenuto.
- L'implementazione tra script specifici del fornitore deve mantenere i nomi dei vincoli. Ciò semplifica la risoluzione dei problemi, grazie alla denominazione uniforme tra implementazioni concrete.
- Version (o ETag nel codice dell'applicazione, per Orleans) rappresenta una versione univoca. Non importa quale sia il tipo dell'implementazione effettiva purché rappresenti una versione univoca. Nell'implementazione, il Orleans codice prevede un intero con segno a 32 bit.
- Per essere più espliciti e meno ambigui possibile, Orleans prevede che alcune query restituiscano TRUE come >valore 0 o FALSE come valore = 0. Ovvero, il numero di righe interessate o restituite non è rilevante. Se viene generato un errore o un'eccezione, la query deve assicurarsi che venga eseguito il rollback dell'intera transazione e può restituire FALSE o propagare l'eccezione.
- Attualmente, tutte le query tranne una sono inserimenti a riga singola o aggiornamenti (notare che è possibile sostituire le query
UPDATE
conINSERT
, purché le query associateSELECT
eseguano l'ultima scrittura).
I motori di database supportano la programmazione interna al database. Questo è analogo al caricamento di uno script eseguibile che viene richiamato per eseguire operazioni di database. In pseudocodice, può essere rappresentato come:
const int Param1 = 1;
const DateTime Param2 = DateTime.UtcNow;
const string queryFromOrleansQueryTableWithSomeKey =
"SELECT column1, column2 "+
"FROM <some Orleans table> " +
"WHERE column1 = @param1 " +
"AND column2 = @param2;";
TExpected queryResult =
SpecificQuery12InOrleans<TExpected>(query, Param1, Param2);
Questi principi sono inclusi anche negli script del database.
Alcune idee sull'applicazione di script personalizzati
- Modificare gli script in
OrleansQuery
per ottenere persistenza granulare conIF ELSE
, in modo che alcuni stati vengano salvati usando l'impostazione predefinitaINSERT
, mentre alcuni stati di granularità possono usare tabelle ottimizzate per la memoria. Le querySELECT
devono essere modificate di conseguenza. - L'idea in
1.
può essere usata per sfruttare altri aspetti specifici della distribuzione o del fornitore, come la suddivisione dei dati traSSD
oHDD
, l'inserimento di alcuni dati in tabelle crittografate, l'inserimento di dati statistici tramite SQL-Server-to-Hadoop, o anche i server collegati.
Gli script modificati possono essere testati eseguendo il gruppo di test Orleans o direttamente nel database usando, ad esempio, Progetto gruppo di test SQL Server.
Linee guida per l'aggiunta di nuovi provider di ADO.NET
- Aggiungere un nuovo script di configurazione del database in base alla sezione precedente Realizzazione degli obiettivi.
- Aggiungere il nome invariante ADO del fornitore a AdoNetInvariants e i dati specifici del provider di ADO.NET a DbConstantsStore. Questi sono (potenzialmente) usati in alcune operazioni di query. Ad esempio, per selezionare la modalità di inserimento delle statistiche corrette (come
UNION ALL
, con o senzaFROM DUAL
). - Orleans dispone di test completi per tutti gli archivi di sistema: appartenenza, promemoria e statistiche. L'aggiunta di test per il nuovo script di database viene eseguita copiando le classi di test esistenti e modificando il nome invariante ADO. Inoltre, derivare da RelationalStorageForTesting per definire la funzionalità di test per l'invariante ADO.