Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@
- `CODEOWNERS`, `.editorconfig`, and Dependabot config (cargo + GitHub Actions,
weekly).
- Expanded `.gitignore` with IDE and build directories.

### Fixed

- `get_tables` no longer issues DescribeTable calls serially. On AWS accounts
with hundreds of tables the serial loop took over a minute (~300ms per
table), exceeding the GUI's connection timeout and failing the initial
connection. Describes now run with bounded concurrency (16 in flight) and
results are re-sorted alphabetically to preserve ListTables ordering.
52 changes: 51 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ aws-config = { version = "1", features = ["behavior-version-latest"] }
aws-sdk-dynamodb = { version = "1", features = ["behavior-version-latest"] }
aws-smithy-types = "1"
base64 = "0.23"
futures = "0.3"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
Expand Down
41 changes: 28 additions & 13 deletions src/handlers/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Schema metadata: tables, columns, indexes, foreign keys.

use futures::stream::{self, StreamExt};
use serde_json::{json, Value};

use crate::error::ErrorCode;
Expand All @@ -8,6 +9,13 @@ use crate::handlers::models::ColumnResponse;
use crate::rpc::{error_response, ok_response};
use crate::utils::extractor;

/// Max in-flight DescribeTable calls while building the table list. On
/// accounts with hundreds of tables a serial loop takes over a minute
/// (~300ms per describe), which trips the GUI's connection timeout. Bounding
/// concurrency keeps the full metadata (item count, size, status) without
/// overwhelming the account's DescribeTable rate limit.
const DESCRIBE_TABLE_CONCURRENCY: usize = 16;

/// Returns the list of tables in DynamoDB with metadata.
pub async fn get_tables(id: Value, params: &Value) -> Value {
let client = match connection::build_client(params).await {
Expand All @@ -17,31 +25,38 @@ pub async fn get_tables(id: Value, params: &Value) -> Value {

match client.list_tables().await {
Ok(table_names) => {
let mut results = Vec::new();
for name in table_names {
// Fetch full table metadata via describe_table
match client.describe_table(&name).await {
Ok(desc) => {
results.push(json!({
let mut results: Vec<Value> = stream::iter(table_names.into_iter().map(|name| {
let client = client.clone();
async move {
match client.describe_table(&name).await {
Ok(desc) => json!({
"name": name,
"comment": null,
"item_count": desc.item_count.unwrap_or(0),
"table_size_bytes": desc.table_size_bytes.unwrap_or(0),
"table_status": desc.table_status.unwrap_or_else(|| "ACTIVE".to_string()),
}));
}
Err(_) => {
// Fallback if describe_table fails
results.push(json!({
}),
Err(_) => json!({
"name": name,
"comment": null,
"item_count": 0,
"table_size_bytes": 0,
"table_status": "UNKNOWN",
}));
}),
}
}
}
}))
.buffer_unordered(DESCRIBE_TABLE_CONCURRENCY)
.collect()
.await;
// buffer_unordered completes out of order; restore alphabetical
// ordering so the sidebar matches ListTables order.
results.sort_by(|a, b| {
a["name"]
.as_str()
.unwrap_or("")
.cmp(b["name"].as_str().unwrap_or(""))
});
ok_response(id, json!(results))
}
Err(err) => error_response(id, ErrorCode::InternalError, &err.message),
Expand Down