Skip to content

Commit c93555e

Browse files
committed
Add OnDemand mode
1 parent 1cd3f68 commit c93555e

File tree

6 files changed

+51
-34
lines changed

6 files changed

+51
-34
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ open Amazon.DynamoDBv2
3737
open FSharp.AWS.DynamoDB.Scripting // Expose non-Async methods, e.g. PutItem/GetItem
3838
3939
let client : IAmazonDynamoDB = ``your DynamoDB client instance``
40-
let throughput = ProvisionedThroughput (readCapacityUnits = 1L, writeCapacityUnits = 10L)
41-
let table = TableContext.Initialize<WorkItemInfo>(client, tableName = "workItems", throughput)
40+
let throughput = ProvisionedThroughput(readCapacityUnits = 1L, writeCapacityUnits = 10L)
41+
let table = TableContext.Initialize<WorkItemInfo>(client, tableName = "workItems", Provisioned throughput)
4242
4343
let workItem = { ProcessId = 0L ; WorkItemId = 1L ; Name = "Test" ; UUID = guid() ; Dependencies = set ["mscorlib"] ; Started = None }
4444
@@ -120,8 +120,8 @@ type Counter private (table : TableContext<CounterEntry>, key : TableKey) =
120120
121121
static member Create(client : IAmazonDynamoDB, tableName : string) = async {
122122
let table = TableContext<CounterEntry>(client, tableName)
123-
let throughput = ProvisionedThroughput(readCapacityUnits = 100L, writeCapacityUnits = 100L)
124-
do! table.InitializeTableAsync throughput
123+
let throughput = ProvisionedThroughput(readCapacityUnits = 10L, writeCapacityUnits = 10L)
124+
do! table.InitializeTableAsync(Provisioned throughput)
125125
let initialEntry = { Id = Guid.NewGuid() ; Value = 0L }
126126
let! key = table.PutItemAsync(initialEntry)
127127
return Counter(table, key)

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* Added `TableContext.VerifyTableAsync` overload that only performs verification but never creates a Table
1111
* Added `TableContext.InitializeTableAsync` (replaces `TableContext.VerifyTableAsync(createIfNotExists = true)`)
1212
* Added `TableContext.ProvisionTableAsync` (as per `InitializeTableAsync` but does an `UpdateProvisionedThroughputAsync` if throughput has changed)
13+
* Added Support for `Throughput.OnDemand` mode (sets `BillingMode` to `PAY_PER_REQUEST` rather than attempting to configure a `ProvisionedThroughput`)
1314
* Removed `TableContext.CreateAsync` (replace with `TableContext.VerifyTableAsync` or `InitializeTableAsync`)
1415

1516
### 0.9.3-beta

src/FSharp.AWS.DynamoDB/RecordKeySchema.fs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,12 +369,10 @@ type TableKeySchemata with
369369
yield! td.LocalSecondaryIndexes |> Seq.map mkLocalSecondaryIndex |])
370370

371371
/// Create a CreateTableRequest using supplied key schema
372-
member schema.CreateCreateTableRequest (tableName : string, provisionedThroughput : ProvisionedThroughput) =
372+
member schema.CreateCreateTableRequest(tableName : string) =
373373
let ctr = CreateTableRequest(TableName = tableName)
374374
let inline mkKSE n t = KeySchemaElement(n, t)
375375

376-
ctr.ProvisionedThroughput <- provisionedThroughput
377-
378376
let keyAttrs = new Dictionary<string, KeyAttributeSchema>()
379377
for tks in schema.Schemata do
380378
keyAttrs.[tks.HashKey.AttributeName] <- tks.HashKey
@@ -391,7 +389,6 @@ type TableKeySchemata with
391389
gsi.KeySchema.Add <| mkKSE tks.HashKey.AttributeName KeyType.HASH
392390
tks.RangeKey |> Option.iter (fun rk -> gsi.KeySchema.Add <| mkKSE rk.AttributeName KeyType.RANGE)
393391
gsi.Projection <- Projection(ProjectionType = ProjectionType.ALL)
394-
gsi.ProvisionedThroughput <- provisionedThroughput
395392
ctr.GlobalSecondaryIndexes.Add gsi
396393

397394
| LocalSecondaryIndex name ->

src/FSharp.AWS.DynamoDB/Script.fsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ type Test =
5454
Bytes : byte[]
5555
}
5656

57-
let autoCreate = InitializationMode.CreateIfNotExists (ProvisionedThroughput (100L, 100L))
58-
let table = TableContext.Initialize<Test>(ddb, "test", mode = autoCreate)
57+
let throughput = ProvisionedThroughput(readCapacityUnits = 10L, writeCapacityUnits = 10L)
58+
let table = TableContext.Initialize<Test>(ddb, "test", Provisioned throughput)
5959

6060
let value = { HashKey = Guid.NewGuid() ; List = [] ; RangeKey = "2" ; Value = 3.1415926 ; Date = DateTimeOffset.Now + TimeSpan.FromDays 2. ; Value2 = None ; Values = [|{ A = "foo" ; B = System.Reflection.BindingFlags.Instance }|] ; Map = Map.ofList [("A1",1)] ; Set = [set [1L];set [2L]] ; Bytes = [|1uy..10uy|]; String = ref "1a" ; Unions = [A 42; B("42",3)]}
6161

@@ -126,8 +126,9 @@ type EasyCounters private (table : TableContext<CounterEntry>) =
126126
static member Create(client : IAmazonDynamoDB, tableName : string) : Async<EasyCounters> = async {
127127
let table = TableContext<CounterEntry>(client, tableName)
128128
// Create the table if necessary. Verifies schema is correct if it has already been created
129-
let throughput = ProvisionedThroughput(readCapacityUnits = 100L, writeCapacityUnits = 100L)
130-
do! table.InitializeTableAsync throughput
129+
// NOTE the hard coded initial throughput provisioning - arguably this belongs outside of your application logic
130+
let throughput = ProvisionedThroughput(readCapacityUnits = 10L, writeCapacityUnits = 10L)
131+
do! table.InitializeTableAsync(Provisioned throughput)
131132
return EasyCounters(table)
132133
}
133134

@@ -141,7 +142,8 @@ type SimpleCounters private (table : TableContext<CounterEntry>) =
141142
let table = TableContext<CounterEntry>(client, tableName)
142143
// normally, RCU/WCU provisioning only happens first time the Table is created and is then considered an external concern
143144
// here we use `ProvisionTableAsync` instead of `InitializeAsync` to reset it each time we start the app
144-
table.ProvisionTableAsync(ProvisionedThroughput (readCapacityUnits, writeCapacityUnits))
145+
let provisionedThroughput = ProvisionedThroughput(readCapacityUnits, writeCapacityUnits)
146+
table.ProvisionTableAsync(Provisioned provisionedThroughput)
145147

146148
/// We only want to do the initialization bit once per instance of our application
147149
/// Similar to EasyCounters.Create in that it ensures the table is provisioned correctly
@@ -168,11 +170,11 @@ let e2 = e.StartCounter() |> Async.RunSynchronously
168170
e1.Incr() |> Async.RunSynchronously
169171
e2.Incr() |> Async.RunSynchronously
170172

171-
SimpleCounters.Provision(ddb, "testing-pre-provisioned", 100L, 100L) |> Async.RunSynchronously
173+
SimpleCounters.Provision(ddb, "testing-pre-provisioned", readCapacityUnits = 10L, writeCapacityUnits = 10L) |> Async.RunSynchronously
172174
// The consuming code can assume the provisioning has been carried out as part of the deploy
173175
// that allows the creation to be synchronous (and not impede application startup)
174176
let s = SimpleCounters.Create(ddb, "testing-pre-provisioned")
175-
let s1 = s.StartCounter() |> Async.RunSynchronously // Would throw if Provision has not been carried out
177+
let s1 = s.StartCounter() |> Async.RunSynchronously // Throws if Provision step has not been executed
176178
s1.Incr() |> Async.RunSynchronously
177179

178180
// Alternately, we can have the app do an extra call (and have some asynchronous initialization work) to check the table is ready

src/FSharp.AWS.DynamoDB/TableContext.fs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ type ResourceNotFoundException = Amazon.DynamoDBv2.Model.ResourceNotFoundExcepti
1919
/// Represents the provisioned throughput for given table or index
2020
type ProvisionedThroughput = Amazon.DynamoDBv2.Model.ProvisionedThroughput
2121

22+
/// Represents the throughput configuration for a Table
23+
type Throughput =
24+
| Provisioned of ProvisionedThroughput
25+
| OnDemand
26+
module internal Throughput =
27+
let applyToCreateRequest (req : CreateTableRequest) = function
28+
| Provisioned t ->
29+
req.ProvisionedThroughput <- t
30+
for gsi in req.GlobalSecondaryIndexes do
31+
gsi.ProvisionedThroughput <- t
32+
| OnDemand ->
33+
req.BillingMode <- BillingMode.PAY_PER_REQUEST
34+
2235
/// Represents the operation performed on the table, for metrics collection purposes
2336
type Operation = GetItem | PutItem | UpdateItem | DeleteItem | BatchGetItems | BatchWriteItems | Scan | Query
2437

@@ -973,10 +986,13 @@ type TableContext<'TRecord> internal
973986

974987
checkOrCreate 9 // up to 9 retries, i.e. 10 attempts before we let exception propagate
975988

976-
member internal _.InternalCreateTableRequest(throughput) =
977-
template.Info.Schemata.CreateCreateTableRequest(tableName, throughput)
978-
member internal t.InternalCreateOrValidateTableAsync(createThroughput) =
979-
t.InternalProvision(fun () -> t.InternalCreateTableRequest(createThroughput))
989+
member internal _.InternalCreateTableRequest(applyThroughput) =
990+
let req = template.Info.Schemata.CreateCreateTableRequest(tableName)
991+
applyThroughput req
992+
req
993+
994+
member internal t.InternalCreateOrValidateTableAsync(throughput) =
995+
t.InternalProvision(fun () -> t.InternalCreateTableRequest(fun r -> Throughput.applyToCreateRequest r throughput))
980996

981997
/// <summary>
982998
/// Asynchronously verify that the table exists and is compatible with record key schema, or throw.<br/>
@@ -990,21 +1006,24 @@ type TableContext<'TRecord> internal
9901006
/// If the table is not present, it is provisioned, with the specified <c>throughput</c>.<br/>
9911007
/// See also <c>VerifyTableAsync</c>, which only verifies the Table is present and correct.
9921008
/// </summary>
993-
/// <param name="throughput">Provisioned throughput to use for the table.</param>
994-
member t.InitializeTableAsync(throughput : ProvisionedThroughput) : Async<unit> =
1009+
/// <param name="throughput">Throughput configuration to use for the table.</param>
1010+
member t.InitializeTableAsync(throughput : Throughput) : Async<unit> =
9951011
t.InternalCreateOrValidateTableAsync(throughput) |> Async.Ignore
9961012

9971013
/// <summary>
9981014
/// Asynchronously verifies that the table exists and is compatible with record key schema, throwing if it is incompatible.<br/>
9991015
/// If the table is not present, it is provisioned, with the specified <c>throughput</c>.<br/>
10001016
/// If it is present, and the throughput is not as specified, uses <c>UpdateProvisionedThroughputAsync</c> to update it. <br/>
10011017
/// </summary>
1002-
/// <param name="throughput">Provisioned throughput to use for the table.</param>
1003-
member t.ProvisionTableAsync(throughput : ProvisionedThroughput) : Async<unit> = async {
1018+
/// <param name="throughput">Throughput configuration to use for the table.</param>
1019+
member t.ProvisionTableAsync(throughput : Throughput) : Async<unit> = async {
10041020
let! tableDescription = t.InternalCreateOrValidateTableAsync(throughput)
1005-
let provisioned = tableDescription.ProvisionedThroughput
1006-
if throughput.ReadCapacityUnits <> provisioned.ReadCapacityUnits || throughput.WriteCapacityUnits <> provisioned.WriteCapacityUnits then
1007-
do! t.UpdateProvisionedThroughputAsync(throughput) }
1021+
match throughput with
1022+
| Provisioned p ->
1023+
let current = tableDescription.ProvisionedThroughput
1024+
if p.ReadCapacityUnits <> current.ReadCapacityUnits || p.WriteCapacityUnits <> current.WriteCapacityUnits then
1025+
do! t.UpdateProvisionedThroughputAsync p
1026+
| OnDemand -> () }
10081027

10091028
/// <summary>
10101029
/// Asynchronously verify that the table exists and is compatible with record key schema.
@@ -1015,7 +1034,7 @@ type TableContext<'TRecord> internal
10151034
member t.VerifyTableAsync(?createIfNotExists : bool, ?provisionedThroughput : ProvisionedThroughput) : Async<unit> =
10161035
if createIfNotExists = Some true then
10171036
let throughput = match provisionedThroughput with Some p -> p | None -> ProvisionedThroughput(10L, 10L)
1018-
t.InitializeTableAsync(throughput)
1037+
t.InitializeTableAsync(Provisioned throughput)
10191038
else
10201039
t.VerifyTableAsync()
10211040

@@ -1043,7 +1062,7 @@ type TableContext internal () =
10431062
let context = TableContext<'TRecord>(client, tableName, ?metricsCollector = metricsCollector)
10441063
if createIfNotExists = Some true then
10451064
let throughput = match provisionedThroughput with Some p -> p | None -> ProvisionedThroughput(10L, 10L)
1046-
do! context.InitializeTableAsync throughput
1065+
do! context.InitializeTableAsync(Throughput.Provisioned throughput)
10471066
elif verifyTable <> Some false then
10481067
do! context.VerifyTableAsync()
10491068
return context }

tests/FSharp.AWS.DynamoDB.Tests/Utils.fs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,8 @@ module Utils =
4040
let client = getDynamoDBAccount()
4141
let tableName = getRandomTableName()
4242

43-
member __.CreateContextAndTableIfNotExists<'TRecord>() =
44-
let createThroughput = ProvisionedThroughput(10L, 10L)
45-
Scripting.TableContext.Initialize<'TRecord>(client, tableName, createThroughput)
43+
member _.CreateContextAndTableIfNotExists<'TRecord>() =
44+
let throughput = Model.ProvisionedThroughput(readCapacityUnits = 10L, writeCapacityUnits = 10L)
45+
Scripting.TableContext.Initialize<'TRecord>(client, tableName, Provisioned throughput)
4646

47-
interface IDisposable with
48-
member __.Dispose() =
49-
client.DeleteTableAsync(tableName) |> Async.AwaitTask |> Async.RunSynchronously |> ignore
47+
interface IDisposable with member _.Dispose() = client.DeleteTableAsync(tableName) |> Async.AwaitTask |> Async.RunSynchronously |> ignore

0 commit comments

Comments
 (0)