Protocol: JSON 1.1 (X-Amz-Target: DynamoDB_20120810.*)
Endpoint: POST http://localhost:4566/
| Action | Description |
|---|---|
CreateTable |
Create a table with indexes |
DeleteTable |
Delete a table |
DescribeTable |
Get table metadata |
ListTables |
List all tables |
UpdateTable |
Update throughput, indexes, streams |
PutItem |
Write an item |
GetItem |
Read an item by primary key |
DeleteItem |
Delete an item |
UpdateItem |
Partially update an item |
Query |
Query by partition key with optional filter |
Scan |
Full table scan with optional filter |
SearchVectors |
Rank a vector index by distance from a query vector |
BatchWriteItem |
Write/delete up to 25 items across tables |
BatchGetItem |
Read up to 100 items across tables |
TransactWriteItems |
ACID write transaction |
TransactGetItems |
ACID read transaction |
DescribeTimeToLive |
Get TTL configuration |
UpdateTimeToLive |
Enable/disable TTL on a table |
TagResource |
Tag a table |
UntagResource |
Remove tags |
ListTagsOfResource |
List tags |
DescribeContinuousBackups |
Get PITR backup configuration |
UpdateContinuousBackups |
Enable/disable PITR |
DescribeKinesisStreamingDestination |
List Kinesis streaming destinations |
EnableKinesisStreamingDestination |
Enable Kinesis streaming for a table |
DisableKinesisStreamingDestination |
Disable Kinesis streaming for a table |
ExportTableToPointInTime |
Export table data to S3 as gzip NDJSON |
CreateGlobalTable |
Make an existing table a global table (2017.11.29) |
DescribeGlobalTable |
Read a global table's replication group |
UpdateGlobalTable |
Add or remove replica regions |
ListGlobalTables |
List global tables, optionally filtered by region |
DescribeExport |
Get export status and metadata |
ListExports |
List exports, optionally filtered by table ARN |
ImportTable |
Create a table and load DynamoDB JSON from S3 into it |
DescribeImport |
Get import status and metadata |
ListImports |
List imports, optionally filtered by table ARN |
DynamoDB Streams are supported via a separate target (DynamoDBStreams_20120810):
| Action | Description |
|---|---|
ListStreams |
List all streams |
DescribeStream |
Get stream and shard info |
GetShardIterator |
Get a shard iterator |
GetRecords |
Read stream records from a shard |
Redshift zero-ETL integrations can consume these stream records directly. See the Redshift DynamoDB zero-ETL section for the supported target, landing table, checkpoint, and retry behavior.
| Variable | Default | Description |
|---|---|---|
FLOCI_SERVICES_DYNAMODB_ENABLED |
true |
Enable or disable the service |
FLOCI_STORAGE_SERVICES_DYNAMODB_MODE |
(global default) | Storage mode override for DynamoDB (memory, persistent, hybrid, wal) |
FLOCI_STORAGE_SERVICES_DYNAMODB_FLUSH_INTERVAL_MS |
5000 |
Flush interval for hybrid/wal storage modes (milliseconds) |
FLOCI_SERVICES_DYNAMODB_VECTOR_INDEX_ALLOCATION_SECONDS |
4 |
Seconds a vector index added by UpdateTable spends in resource allocation |
FLOCI_SERVICES_DYNAMODB_VECTOR_INDEX_BACKFILL_SECONDS |
10 |
Seconds that index then spends backfilling before it goes ACTIVE |
Under persistent storage mode, single-item writes (PutItem, UpdateItem, DeleteItem) flush the affected table to disk synchronously. Batch operations (BatchWriteItem and TransactWriteItems) batch disk flushes per affected table across the entire operation, rather than flushing on every individual item mutation.
For write-heavy workloads under persistent setups, configuring FLOCI_STORAGE_SERVICES_DYNAMODB_MODE=wal or hybrid is recommended to avoid full-file rewrites on each write operation:
wal: Uses an append-only write-ahead log with background compaction.hybrid: Keeps data in memory with periodic asynchronous disk flushes controlled byFLOCI_STORAGE_SERVICES_DYNAMODB_FLUSH_INTERVAL_MS.
export AWS_ENDPOINT_URL=http://localhost:4566
# Create a table
aws dynamodb create-table \
--table-name Users \
--attribute-definitions \
AttributeName=userId,AttributeType=S \
--key-schema \
AttributeName=userId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--endpoint-url $AWS_ENDPOINT_URL
# Put an item
aws dynamodb put-item \
--table-name Users \
--item '{"userId":{"S":"u1"},"name":{"S":"Alice"},"age":{"N":"30"}}' \
--endpoint-url $AWS_ENDPOINT_URL
# Get an item
aws dynamodb get-item \
--table-name Users \
--key '{"userId":{"S":"u1"}}' \
--endpoint-url $AWS_ENDPOINT_URL
# Query (partition key)
aws dynamodb query \
--table-name Users \
--key-condition-expression "userId = :id" \
--expression-attribute-values '{":id":{"S":"u1"}}' \
--endpoint-url $AWS_ENDPOINT_URL
# Scan with filter
aws dynamodb scan \
--table-name Users \
--filter-expression "age > :min" \
--expression-attribute-values '{":min":{"N":"25"}}' \
--endpoint-url $AWS_ENDPOINT_URL
# Enable TTL
aws dynamodb update-time-to-live \
--table-name Users \
--time-to-live-specification Enabled=true,AttributeName=expiresAt \
--endpoint-url $AWS_ENDPOINT_URL
# Enable Streams
aws dynamodb update-table \
--table-name Users \
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \
--endpoint-url $AWS_ENDPOINT_URLaws dynamodb create-table \
--table-name Orders \
--attribute-definitions \
AttributeName=orderId,AttributeType=S \
AttributeName=customerId,AttributeType=S \
--key-schema AttributeName=orderId,KeyType=HASH \
--global-secondary-indexes '[{
"IndexName": "CustomerIndex",
"KeySchema": [{"AttributeName":"customerId","KeyType":"HASH"}],
"Projection": {"ProjectionType":"ALL"}
}]' \
--billing-mode PAY_PER_REQUEST \
--endpoint-url $AWS_ENDPOINT_URLDeviations from AWS:
- No index is stored. A
QueryorScanon an index reads the base table and applies the index's key schema and projection on the way out. - Index reads are immediately consistent. A GSI on AWS is eventually consistent, so code that tolerates replication lag never exercises that wait here.
- A new index is
ACTIVEat once. AWS copies the table's items into a new index first, which takes minutes on a large table. Nothing is copied here, so a waiter returns straight away.
A vector index serves SearchVectors, which ranks a table's items by the distance between a query
vector and the vector attribute each item carries. Indexes are declared on CreateTable or added
later with UpdateTable, on PAY_PER_REQUEST tables only. An item that does not carry the vector
attribute, or that lacks the index's HASH search schema attribute, is still written but stays out
of that index. Query, Scan and PartiQL cannot read a vector index.
# A table with a vector index
aws dynamodb create-table \
--table-name Docs \
--attribute-definitions AttributeName=docId,AttributeType=S \
--key-schema AttributeName=docId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--vector-indexes '[{
"IndexName": "embedding-index",
"VectorAttribute": {"AttributeName": "embedding"},
"Projection": {"ProjectionType": "ALL"},
"Dimensions": 3,
"DistanceFunction": "COSINE"
}]' \
--endpoint-url $AWS_ENDPOINT_URL
# Write an item carrying a vector
aws dynamodb put-item \
--table-name Docs \
--item '{"docId":{"S":"d1"},"title":{"S":"first"},"embedding":{"L":[{"N":"1"},{"N":"0"},{"N":"0"}]}}' \
--endpoint-url $AWS_ENDPOINT_URL
# Search it
aws dynamodb search-vectors \
--table-name Docs \
--index-name embedding-index \
--search-vector '[{"N":"1"},{"N":"0"},{"N":"0"}]' \
--top-k 5 \
--endpoint-url $AWS_ENDPOINT_URLDistanceFunction is COSINE, EUCLIDEAN or DOT_PRODUCT. The first two rank the lowest score
first, DOT_PRODUCT the highest. The vector attribute is left out of a result unless
--projection-expression names it, and when it is named the values returned are the index's own
32 bit copies, so a 1 written to the table comes back as 1.0.
Deviations from AWS:
- The search is exact, not approximate. AWS may return a slightly different set or order on a large index. Scoring every item makes a result here repeatable.
- No index is stored, as above. Every search reads the vectors from the base table and converts them, so the work grows with items times dimensions. A large index is slower than AWS.
- A written vector is searchable at once. AWS copies it into the index in the background, so code that polls for a new vector never waits here.
ItemCountandIndexSizeBytesalways report 0. That is what AWS reports for a fresh index, because it refreshes both roughly every six hours.- An index created with its table is
ACTIVEat once. One added byUpdateTablewalks a resource allocation phase and then a backfill phase. Their lengths areFLOCI_SERVICES_DYNAMODB_VECTOR_INDEX_ALLOCATION_SECONDSandFLOCI_SERVICES_DYNAMODB_VECTOR_INDEX_BACKFILL_SECONDS, 4 and 10 seconds by default, against minutes on AWS. SearchVectorsis served on Floci's ordinary endpoint. AWS gives the operation a dedicated search endpoint. The SDKs honor an endpoint override, so this is invisible to callers.VectorSearchRequestBytesis a fixed figure, not a measured one. It uses the same 1024 byte floor AWS uses. AWS's own value above that floor is not deterministic.
Export table data to an S3 bucket as gzip-compressed NDJSON (DynamoDB JSON format):
# Create a bucket to receive the export
aws s3 mb s3://my-exports --endpoint-url $AWS_ENDPOINT_URL
# Start an export
EXPORT_ARN=$(aws dynamodb export-table-to-point-in-time \
--table-arn arn:aws:dynamodb:us-east-1:000000000000:table/Users \
--s3-bucket my-exports \
--s3-prefix exports \
--export-format DYNAMODB_JSON \
--query ExportDescription.ExportArn --output text \
--endpoint-url $AWS_ENDPOINT_URL)
# Poll until COMPLETED
aws dynamodb describe-export \
--export-arn $EXPORT_ARN \
--query ExportDescription.ExportStatus \
--endpoint-url $AWS_ENDPOINT_URL
# List exports for a table
aws dynamodb list-exports \
--table-arn arn:aws:dynamodb:us-east-1:000000000000:table/Users \
--endpoint-url $AWS_ENDPOINT_URLThe export writes to s3://<bucket>/<prefix>/AWSDynamoDB/<exportId>/data/ as one or more .json.gz files, along with manifest-summary.json and manifest-files.json, the same layout as real AWS DynamoDB exports.
Create a new table and load it from newline-delimited DynamoDB JSON objects in S3. Each line is {"Item": {...}}, the format an export writes:
# Upload the data
printf '{"Item":{"userId":{"S":"u1"}}}\n{"Item":{"userId":{"S":"u2"}}}\n' > data.json
aws s3 cp data.json s3://my-exports/imports/data.json --endpoint-url $AWS_ENDPOINT_URL
# Start an import
IMPORT_ARN=$(aws dynamodb import-table \
--s3-bucket-source S3Bucket=my-exports,S3KeyPrefix=imports/ \
--input-format DYNAMODB_JSON \
--input-compression-type NONE \
--table-creation-parameters '{"TableName":"UsersCopy","AttributeDefinitions":[{"AttributeName":"userId","AttributeType":"S"}],"KeySchema":[{"AttributeName":"userId","KeyType":"HASH"}],"BillingMode":"PAY_PER_REQUEST"}' \
--query ImportTableDescription.ImportArn --output text \
--endpoint-url $AWS_ENDPOINT_URL)
# Poll until COMPLETED
aws dynamodb describe-import \
--import-arn $IMPORT_ARN \
--query ImportTableDescription.ImportStatus \
--endpoint-url $AWS_ENDPOINT_URL
# List imports
aws dynamodb list-imports --endpoint-url $AWS_ENDPOINT_URLThe import reads every object under the key prefix. Point it at the data/ prefix of an export with --input-compression-type GZIP to load an export back. Floci does not evaluate bucket policies, so an S3BucketOwner that is not the caller's account fails the import with S3AccessDenied, as AWS does without a policy grant. The table stays in CREATING until the import finishes, then becomes ACTIVE. DeleteTable and UpdateTable return ResourceInUseException while the table is CREATING. Item calls such as GetItem, PutItem, Query and Scan return ResourceNotFoundException until the table is ACTIVE, as on AWS. A line that is not valid DynamoDB JSON or does not match the key schema is skipped and counted in ErrorCount. An object that cannot be read, for example a plain file under a GZIP import, is skipped and counted as one error. A missing bucket or an empty prefix ends the import as FAILED with a FailureCode. A reused ClientToken with different parameters returns ImportConflictException.
Deviations from AWS: only InputFormat DYNAMODB_JSON with InputCompressionType NONE or GZIP is accepted. CSV, ION and ZSTD are rejected with a ValidationException.
When a table has an ACTIVE Kinesis streaming destination (see
EnableKinesisStreamingDestination), every item change, INSERT, MODIFY, and REMOVE,
including TTL expirations, is forwarded to the destination stream as a Kinesis record in the
AWS CDC envelope (eventName, dynamodb.Keys, NewImage/OldImage, ApproximateCreationDateTime).
ApproximateCreationDateTime follows the destination's
EnableKinesisStreamingConfiguration.ApproximateCreationDateTimePrecision: epoch milliseconds for
MILLISECOND (the default) and epoch microseconds for MICROSECOND. The precision is returned by
DescribeKinesisStreamingDestination and stamped on each record as
dynamodb.ApproximateCreationDateTimePrecision.
Enabling a Kinesis streaming destination does not change the table's DynamoDB Streams setting.
Kinesis forwarding works whether or not StreamSpecification.StreamEnabled is set.
Forwarding is bounded best-effort with in-process retry. A write is never blocked or failed by
the destination stream: the change event is enqueued and delivered on a background drain, so a slow or
unavailable Kinesis stream cannot stall a PutItem/UpdateItem/DeleteItem or the TTL sweep.
The drain gives these guarantees per destination:
- Retries transient/unknown send failures with capped exponential backoff (250 ms doubling to an 8 s cap, up to 10 attempts) rather than dropping the record on the first exception.
- Preserves FIFO across retries: the head record is not skipped past (stronger than AWS, which may reorder or duplicate).
- Drops a poison record immediately on a deterministic terminal failure
(
ValidationException/InvalidArgumentException) so it cannot wedge the queue behind it. - Gives up an episode after the retry budget is exhausted, dropping the buffered records and marking
the destination
GAVE_UP; a later change event starts a fresh episode and self-heals once the stream recovers. - Bounds memory at 1000 buffered records per destination, evicting the oldest on overflow.
Disabling a destination or deleting the table discards that destination's buffered records and stops all future sends for it; a send already in flight at that instant may still complete (teardown cannot recall an in-flight request, an inherent and harmless race). Buffered records are held in memory only: they are not durable and are lost on restart (this is an emulator, not an at-least-once pipeline). Records that are permanently dropped (terminal, give-up, or overflow) are counted and logged, and per-destination delivery health (forwarded/retried/dropped counts, queue depth, last error, and current health) is tracked for inspection.