diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml index 96314c864a54e..81f1fc30d2008 100644 --- a/.github/workflows/build-check.yml +++ b/.github/workflows/build-check.yml @@ -196,6 +196,7 @@ jobs: yarn cache clean export NODE_OPTIONS=--max-old-space-size=8192 yarn + yarn key-features:generate echo "Building with DOCS_VERSIONS=${DOCS_VERSIONS:-all}" PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js yarn docusaurus build ${{ steps.detect.outputs.locale_args }} diff --git a/.github/workflows/cron-deploy-website.yml b/.github/workflows/cron-deploy-website.yml index 6fe7c3390db15..486ee65fe8867 100644 --- a/.github/workflows/cron-deploy-website.yml +++ b/.github/workflows/cron-deploy-website.yml @@ -39,6 +39,7 @@ jobs: export NODE_OPTIONS=--max-old-space-size=8192 yarn node ./scripts/update_github_info.js + yarn key-features:generate PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js yarn docusaurus build --locale en --locale zh-CN if [ ! -d "./ja-build" ]; then echo "ja-build directory not found, aborting to avoid publishing incorrect ja content." diff --git a/.github/workflows/docs-next-build.yml b/.github/workflows/docs-next-build.yml index 87d35ec5eadfe..06731855b803f 100644 --- a/.github/workflows/docs-next-build.yml +++ b/.github/workflows/docs-next-build.yml @@ -75,6 +75,7 @@ jobs: DOCS_VERSIONS: '4.x' run: | export NODE_OPTIONS=--max-old-space-size=8192 + yarn key-features:generate yarn docusaurus build --locale en --locale zh-CN - name: Verify docs-next artifacts diff --git a/.github/workflows/manual-deploy-website.yml b/.github/workflows/manual-deploy-website.yml index b8a8b985b009e..abcb2502f34f9 100644 --- a/.github/workflows/manual-deploy-website.yml +++ b/.github/workflows/manual-deploy-website.yml @@ -42,6 +42,7 @@ jobs: yarn cache clean export NODE_OPTIONS=--max-old-space-size=8192 yarn + yarn key-features:generate PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js yarn docusaurus build --locale en --locale zh-CN if [ ! -d "./ja-build" ]; then echo "ja-build directory not found, aborting to avoid publishing incorrect ja content." diff --git a/.gitignore b/.gitignore index 2b3044e867373..61baea771f4ef 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Generated files .docusaurus .cache-loader +/src/generated/key-features.ts # Misc .DS_Store diff --git a/docs-next/key-features/compute-storage-decoupled.mdx b/docs-next/key-features/compute-storage-decoupled.mdx new file mode 100644 index 0000000000000..19cf6ebbe440c --- /dev/null +++ b/docs-next/key-features/compute-storage-decoupled.mdx @@ -0,0 +1,11 @@ +--- +title: Compute-Storage Decoupled +description: Scale compute and storage independently with a decoupled Doris architecture. +slug: /key-features/compute-storage-decoupled +featureCard: + tags: + - cloud + - elastic + href: /docs-next/dev/compute-storage-decoupled/intro +--- + diff --git a/docs-next/key-features/high-availability.mdx b/docs-next/key-features/high-availability.mdx new file mode 100644 index 0000000000000..64f11c1f5875c --- /dev/null +++ b/docs-next/key-features/high-availability.mdx @@ -0,0 +1,21 @@ +--- +title: High Availability +description: Keep Doris available through replication, failover, and fault tolerance. +slug: /key-features/high-availability +featureCard: + tags: + - fault-tolerance + - replica +--- + +High availability is the set of mechanisms that help Doris stay online when nodes fail or need maintenance. Replication and failover are central to that behavior. + +## When to use + +Use this feature whenever the cluster must tolerate node loss or maintenance events without significant service interruption. + +## Key points + +- Replication protects data availability. +- Operational procedures matter as much as system design. +- Failover planning should be part of deployment planning. diff --git a/docs-next/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md b/docs-next/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md index 15e9255c2c695..eda822ccae2de 100644 --- a/docs-next/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md +++ b/docs-next/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md @@ -1,6 +1,7 @@ --- { "title": "DML Plan Tuning: Locating Load and Query Performance Bottlenecks", + "sidebar_label": "DML Plan Tuning", "language": "en", "description": "How to tune Doris DML plans? This article explains how to distinguish between load and query bottlenecks, and provides entry points to best practices for both load and query tuning.", "keywords": ["Doris DML tuning", "load performance bottleneck", "query performance bottleneck", "DML plan tuning", "Doris load best practices"] diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-next/current/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md b/i18n/zh-CN/docusaurus-plugin-content-docs-next/current/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md index b71caa1680c1a..b8138d70254d9 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs-next/current/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-next/current/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md @@ -1,6 +1,7 @@ --- { "title": "DML 计划调优:定位导入与查询性能瓶颈", + "sidebar_label": "DML 计划调优", "language": "zh-CN", "description": "如何对 Doris DML 计划进行调优?本文介绍如何区分导入与查询瓶颈,并提供导入与查询调优的最佳实践入口。", "keywords": ["Doris DML 调优", "导入性能瓶颈", "查询性能瓶颈", "DML 计划调优", "Doris 导入最佳实践"] diff --git a/package.json b/package.json index e133faa642ca6..3a7437e93d015 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,12 @@ "start:zh-CN": "docusaurus start --locale zh-CN", "start:ja": "docusaurus start --locale ja", "postinstall": "node scripts/patch-search-tokenize.js", + "prestart": "yarn key-features:generate", "build": "PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js docusaurus build", + "prebuild": "yarn key-features:generate", "build:version": "docusaurus set-versions && docusaurus build", + "prebuild:version": "yarn key-features:generate", + "pretypecheck": "yarn key-features:generate", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", @@ -37,6 +41,7 @@ "docs:lint": "node scripts/docs-governance/report.js --format github", "docs:lint:changed": "node scripts/docs-governance/report.js --changed --format github", "docs:report": "node scripts/docs-governance/report.js --format json --output website-quality-governance/generated/docs-governance-report.json", + "key-features:generate": "node scripts/key-features/generate.js", "docs-governance:test": "node --test scripts/docs-governance/__tests__/*.test.js", "typecheck": "tsc" }, diff --git a/scripts/key-features/generate.js b/scripts/key-features/generate.js new file mode 100644 index 0000000000000..90c4df86f45dd --- /dev/null +++ b/scripts/key-features/generate.js @@ -0,0 +1,142 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const matter = require('gray-matter'); +const { + ensureDirForFile, + normalizePath, + parseArgs, + stripMarkdownExtension, + walkMarkdownFiles, +} = require('../docs-governance/lib'); + +const DOCS_DIR = 'docs-next/key-features'; +const DOCS_ROUTE_BASE = '/docs-next/dev'; +const OUTPUT_FILE = 'src/generated/key-features.ts'; + +function slugToRoute(slug, fallbackSlug) { + const raw = typeof slug === 'string' && slug.trim() ? slug.trim() : fallbackSlug; + const normalized = raw.replace(/^\/+/, '/').replace(/\/+$/, ''); + return normalized === '/' ? '/' : normalized; +} + +function resolveHref(frontMatter, docsRelativePath) { + const cardHref = frontMatter?.featureCard?.href; + if (typeof cardHref === 'string' && cardHref.trim()) { + return cardHref.trim(); + } + + const fallbackSlug = `/${stripMarkdownExtension(docsRelativePath)}`; + const routeSlug = slugToRoute(frontMatter.slug, fallbackSlug); + return `${DOCS_ROUTE_BASE}${routeSlug}`; +} + +function normalizeTags(value, sourcePath) { + if (!Array.isArray(value) || value.length === 0) { + throw new Error(`Missing featureCard.tags in ${sourcePath}`); + } + return value.map((tag) => { + if (typeof tag !== 'string' || !tag.trim()) { + throw new Error(`Invalid featureCard.tags entry in ${sourcePath}`); + } + return tag.trim(); + }); +} + +function parseFeatureDoc(rootDir, absPath, index) { + const raw = fs.readFileSync(absPath, 'utf8'); + const parsed = matter(raw); + const data = parsed.data || {}; + const featureCard = data.featureCard || {}; + const relativePath = normalizePath(path.relative(rootDir, absPath)); + const docsRelativePath = normalizePath(path.relative(path.join(rootDir, 'docs-next'), absPath)); + const sourcePath = relativePath; + const title = typeof data.title === 'string' ? data.title.trim() : ''; + const description = typeof data.description === 'string' ? data.description.trim() : ''; + + if (!title) { + throw new Error(`Missing required front matter field "title" in ${sourcePath}`); + } + if (!description) { + throw new Error(`Missing required front matter field "description" in ${sourcePath}`); + } + + const href = resolveHref(data, docsRelativePath); + const badge = + typeof featureCard.badge === 'string' && featureCard.badge.trim() + ? featureCard.badge.trim() + : typeof featureCard.href === 'string' && featureCard.href.trim() + ? 'doc' + : undefined; + const id = stripMarkdownExtension(docsRelativePath).replace(/\//g, '-'); + return { + id, + title, + description, + tags: normalizeTags(featureCard.tags, sourcePath), + href, + ...(badge ? { badge } : {}), + sourcePath, + }; +} + +function generate({ rootDir = process.cwd(), output = OUTPUT_FILE } = {}) { + const absDocsDir = path.join(rootDir, DOCS_DIR); + if (!fs.existsSync(absDocsDir)) { + throw new Error(`Missing docs directory: ${DOCS_DIR}`); + } + + const files = walkMarkdownFiles(rootDir, [DOCS_DIR]); + const cards = files.map((absPath, index) => parseFeatureDoc(rootDir, absPath, index)); + cards.sort((a, b) => a.title.localeCompare(b.title) || a.href.localeCompare(b.href)); + + const seenIds = new Set(); + const seenHref = new Set(); + for (const card of cards) { + if (seenIds.has(card.id)) { + throw new Error(`Duplicate key-features id: ${card.id}`); + } + if (seenHref.has(card.href)) { + throw new Error(`Duplicate key-features href: ${card.href}`); + } + seenIds.add(card.id); + seenHref.add(card.href); + } + + const outputPath = path.resolve(rootDir, output); + ensureDirForFile(outputPath); + const file = `/* AUTO-GENERATED FILE. DO NOT EDIT DIRECTLY. */ +export type KeyFeatureCard = { + id: string; + title: string; + description: string; + tags: string[]; + href: string; + badge?: string; +}; + +export const keyFeatureCards: KeyFeatureCard[] = ${JSON.stringify( + cards.map(({ sourcePath, ...card }) => card), + null, + 2, + )}; +`; + fs.writeFileSync(outputPath, `${file}\n`, 'utf8'); + return cards; +} + +function runCli() { + const args = parseArgs(process.argv.slice(2)); + const rootDir = args.root ? path.resolve(process.cwd(), args.root) : process.cwd(); + const output = args.output || OUTPUT_FILE; + generate({ rootDir, output }); +} + +if (require.main === module) { + runCli(); +} + +module.exports = { + generate, +}; diff --git a/src/components/home-next/NavbarNext.tsx b/src/components/home-next/NavbarNext.tsx index 8d758908c6357..881c1b81b964e 100644 --- a/src/components/home-next/NavbarNext.tsx +++ b/src/components/home-next/NavbarNext.tsx @@ -35,9 +35,9 @@ function buildNavItems(docsHref: string, releasesHref: string): NavItem[] { { label: 'Why Doris', items: [ - { label: 'Key Features (coming soon)', href: '#' }, { label: 'Doris vs. Others', href: '/why-doris/compare' }, { label: 'Benchmarks (coming soon)', href: '#' }, + { label: 'Key Features', href: '/why-doris/key-features' }, { label: 'User Stories (coming soon)', href: '#' }, ], }, diff --git a/src/components/use-cases-next/AIAnalyticsNext.tsx b/src/components/use-cases-next/AIAnalyticsNext.tsx index a699f6ad0c435..f96d67bc811ca 100644 --- a/src/components/use-cases-next/AIAnalyticsNext.tsx +++ b/src/components/use-cases-next/AIAnalyticsNext.tsx @@ -486,13 +486,6 @@ const capabilities: Capability[] = [ poweredLabel: 'Powered by', poweredBy: [ 'VARIANT data type', - 'JSON analytics', - 'Dynamic schema support', - 'Nested field access', - 'Flexible ingestion', - 'Semi-structured analytics', - 'Auto-columnar storage for hot fields', - 'SQL exploration over dynamic data', ], }, { @@ -509,15 +502,12 @@ const capabilities: Capability[] = [ 'Apache Doris brings SQL filtering, full-text search, BM25 relevance scoring, and vector search into one analytical engine for RAG, enterprise search, AI copilots, agent memory, and AI observability. Teams can retrieve the right context from documents, logs, prompts, responses, feedback, and embeddings while applying business filters such as tenant, user, time range, permissions, model version, and workflow status.', poweredLabel: 'Powered by', poweredBy: [ - 'Inverted index', - 'Tokenized full-text search', - 'BM25 relevance scoring', - 'Vector index (HNSW)', - 'Vector similarity search', - 'Structured SQL filters', - 'JSON and metadata filters', - 'Hybrid search in SQL', - 'RAG-ready retrieval', + 'Inverted Index', + 'Full-text Search', + 'BM25', + 'Vector Index', + 'Embedding', + 'Reciprocal Rank Fusion', ], }, { @@ -535,15 +525,8 @@ const capabilities: Capability[] = [ poweredLabel: 'Powered by', poweredBy: [ 'LLM SQL functions', - 'Text summarization in SQL', - 'Sentiment analysis in SQL', - 'Classification and extraction', - 'MCP server', - 'SQL interface for agents and tools', - 'API and connector integration', - 'Agent framework integration', - 'LLMOps and observability integration', - 'Unified analytics for AI signals', + 'MCP Server', + 'Semantic Layer(Coming Soon)', ], }, ]; @@ -750,7 +733,7 @@ function CtaSection(): JSX.Element { with Apache Doris.