Banzai 파이프라인 및 파싱
GitLab v19.1GitLab Flavored Markdown을 HTML로 파싱하고 렌더링하는 작업에는 여러 컴포넌트가 관여합니다: Banzai 파이프라인과 다양한 필터들 백엔드에서 GLFM을 HTML로 변환하는 모든 처리를 담당합니다.
GitLab Flavored Markdown을 HTML로 파싱하고 렌더링하는 작업에는 여러 컴포넌트가 관여합니다:
-
Banzai 파이프라인과 다양한 필터들
-
Markdown 파서
백엔드에서 GLFM을 HTML로 변환하는 모든 처리를 담당합니다. 이를 통해 다음과 같은 이점을 얻을 수 있습니다:
-
보안: 알 수 없는 태그, 클래스, id를 제거하는 강력한 새니타이제이션(sanitization)을 수행합니다.
-
참조: 참조 구문은 이슈 등을 해석하기 위해 데이터베이스에 접근해야 하며, 사용자가 접근 권한이 없는 참조를 삭제하는 작업도 필요합니다.
-
일관성: 사용자에게 일관된 경험을 제공하고자 하며, 여기에는 GLFM 구문과 스타일링의 완전한 지원이 포함됩니다. 처리가 단일 위치에서 이루어지면 이를 보장할 수 있습니다.
-
캐싱: 이슈나 머지 리퀘스트 설명, 댓글 등 가능한 경우 HTML을 데이터베이스에 캐시합니다.
-
Quick actions: 전용 파이프라인을 사용하여 quick action을 처리하므로, Markdown 텍스트에서 더 효과적으로 감지할 수 있습니다.
프론트엔드는 다음과 같은 표시 관련 특정 측면을 처리합니다:
-
Math 블록
-
Mermaid 블록
-
과도한 수의 math 또는 mermaid 블록과 같은 특정 제한 사항 적용
Banzai 파이프라인#
하와이의 서핑 리프 브레이크의 이름을 따서 명명된 Banzai 파이프라인은 Markdown과 HTML이 파이프라인 방식으로 각 단계에서 변환되는 다양한 필터(lib/banzai/filters)로 구성됩니다. 다양한 파이프라인(lib/banzai/pipeline)이 정의되어 있으며, AsciiDocPipeline, EmailPipeline 등 각 파이프라인은 서로 다른 필터 시퀀스를 가집니다.
html-pipeline gem이 파이프라인/필터 메커니즘을 구현합니다.
기본 파이프라인은 FullPipeline이며, 이는 PlainMarkdownPipeline과 GfmPipeline의 조합입니다.
PlainMarkdownPipeline#
이 파이프라인에는 원시 Markdown을 HTML로 변환하는 필터가 포함되어 있으며, 주로 Filter::MarkdownFilter가 담당합니다.
Filter::MarkdownFilter#
이 필터는 실제 Markdown 파서와 인터페이스합니다. 파서는 comrak Rust 크레이트를 사용하는 gitlab-glfm-markdown Ruby gem을 사용합니다.
텍스트가 이 필터에 전달되면, 지정된 파서 엔진을 호출하여 해당하는 기본 HTML을 생성합니다.
GfmPipeline#
이 파이프라인에는 원시 HTML을 렌더링된 GLFM으로 변환하는 추가 필터들이 모두 포함되어 있습니다.
Nokogiri 문서가 이 필터들 각각에 전달되며, 다양한 변환을 수행합니다.
예를 들어 EmojiFitler, CommitTrailersFilter, SanitizationFilter 등이 있습니다.
초기 Markdown 파싱에서 처리할 수 없는 모든 것은 이 필터들이 처리합니다.
특히 주목할 것은 SanitizationFilter입니다. 이것은 악의적인 입력으로부터 안전한 HTML을 제공하기 위해 매우 중요합니다.
PostProcessPipeline#
FullPipeline의 출력은 데이터베이스에 캐시됩니다. 그러나 참조는 이미 해석된 상태입니다. 사용자의 권한에 따라 해당 참조를 볼 수 없을 수도 있습니다. PostProcessPipeline은 사용자 권한에 따라 기밀 정보를 삭제하는 역할을 담당합니다. 이러한 변경 사항은 표시될 때마다 다시 계산해야 하므로 캐시되지 않습니다.
SingleLinePipeline#
SingleLinePipeline은 issuable 제목과 같은 단일 행 텍스트 필드에 사용됩니다. Issuable concern에서
cache_markdown_field :title, pipeline: :single_line으로 구성됩니다.
FullPipeline과 달리 이 파이프라인은 Markdown 파서(MarkdownFilter)를 실행하지 않습니다. 최소한의 필터 세트를 통해 일반 텍스트를 처리합니다:
-
HtmlEntityFilter- 입력을 일반 텍스트로 처리하여 HTML 엔티티를 이스케이프합니다. -
EmojiFilter-:emoji:단축코드를 변환합니다. -
CustomEmojiFilter- 사용자 지정 이모지 단축코드를 변환합니다. -
AutolinkFilter- URL을 자동으로 링크합니다. -
ExternalLinkFilter- 외부 링크를 처리합니다. -
reference filters -
#123,@user,!456와 같은 GitLab 참조를 해석합니다.
이는 제목이 볼드, 이탤릭, 코드 스팬, Markdown 링크, 또는 기타 표준 Markdown 서식을 지원하지 않음을 의미합니다. 제목에서 사용 가능한 서식에 대한 자세한 내용은 work item 및 머지 리퀘스트 제목을 참조하세요.
성능#
필터를 가능한 한 빠르게 실행하는 것뿐만 아니라, 일반적으로 너무 오래 걸리지 않도록 하는 것도 중요합니다. 이를 위해 여러 기법을 사용합니다:
오래 걸릴 수 있는 특정 필터의 경우, TimeoutFilterHandler에서 Gitlab::RenderTimeout.timeout으로 Ruby 타임아웃을 사용합니다.
이를 통해 처리 시간이 너무 오래 걸리면 실제 처리를 중단할 수 있습니다.
일반적으로 Ruby timeout 사용은 안전하지 않은 것으로 간주됩니다.
따라서 타임아웃을 사용하는 대신 실제 성능 문제를 수정하는 것을 선호하며, 절대적으로 필요한 경우에만 사용합니다.
PipelineTimingCheck를 사용하면 파이프라인이 소요하는 누적 시간을 추적할 수 있습니다. 최대 시간에 도달하면 나머지 필터를 건너뛸 수 있습니다. 거의 모든 필터의 경우, 아무것도 표시하지 않는 것보다 사용자에게 무언가를 표시하기 위해 이와 같은 경우에 건너뛰는 것이 일반적으로 괜찮습니다.
그러나 이것이 바람직하지 않은 경우가 몇 가지 있습니다.
예를 들어 SanitizationFilter의 경우, 해당 필터가 완료되지 않으면 여전히 새니타이즈되지 않은 HTML이 있을 수 있으므로 사용자에게 HTML을 표시할 수 없습니다.
그런 경우에는 오류 메시지를 표시해야 합니다.
벤치마킹에 사용할 수 있는 rake 태스크도 있습니다. 성능 가이드라인을 참조하세요.
Markdown 파서#
comrak Rust 크레이트를 사용하는 gitlab-glfm-markdown Ruby gem을 사용합니다.
comrak은 GFM 및 CommonMark와 100% 호환성을 제공하면서 추가 확장을 추가할 수 있습니다. 예를 들어, 멀티라인 블록 인용구와 위키링크 구문을 comrak에서 직접 구현할 수 있었습니다. 목표는 더 많은 Ruby 필터를 (합리적인 경우) comrak으로 또는 gitlab-glfm-markdown으로 이전하는 것입니다.
comrak에 전달되는 다양한 옵션에 대한 자세한 내용은 glfm_markdown.rb를 참조하세요.
캐싱#
주요 파이프라인의 출력은 데이터베이스에 캐시되거나, 경우에 따라 Redis에 캐시됩니다. CacheMarkdownField는
적절한 _html 칼럼을 관리하는 데 사용됩니다. 예를 들어 description 칼럼이 있으면 description_html 칼럼이 관리됩니다. description_html이 비어 있으면 아직 description에 대해 계산되지 않은 것입니다. 비어 있지 않으면 description_html이 description의 렌더링된 버전임이 보장됩니다.
Markdown 필드를 포함하는 각 테이블에는 cached_markdown_version 칼럼도 포함됩니다. 이는 어떤
Markdown "버전"으로 렌더링되었는지를 나타냅니다. 이 값은 이미 캐시된 HTML을 다시 렌더링해야 하는지 여부를 제어합니다. 예를 들어 HTML 렌더링 방식이 변경되어 모든 캐시된 HTML을 다시 빌드해야 하는 경우에 발생할 수 있습니다.
이를 제어하는 두 가지 값이 있습니다. 하나는 기본 애플리케이션 버전인
Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION입니다. 파일에서 이 값이 변경되면 모든 설치에 걸쳐 모든 캐시된 HTML 필드가
다시 렌더링됩니다.
또한 관리자가 캐시를 무효화할 수 있는 애플리케이션 수준 설정 local_markdown_version이 있습니다.
이는 Markdown 캐시에 문서화되어 있습니다. 이것은
예를 들어 새 PlantUML 서버 사용과 같이 시스템 설정이 변경되어 관리자가 모든 필드에서 새 값을 사용하기를 원하는 경우에 필요할 수 있습니다. 문서에는 프로젝트만 재설정하는 방법 등도 언급되어 있습니다.
`Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION` 또는 애플리케이션 설정을 변경하면 모든
캐시된 Markdown 필드가 다시 렌더링됩니다. 대규모 설치의 경우 캐시된 Markdown이 있는 모든 행을 업데이트해야 하므로
데이터베이스에 큰 부하가 걸립니다. 따라서 렌더러 출력의 변경이 새 기능이나 사소한 버그 수정인 경우에는 Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION을 변경하지 마세요. 극단적인 상황에서만 변경해야 합니다.
자세한 내용은 이슈 330313을 참조하세요.
디버깅#
다양한 파이프라인과 필터를 디버깅하는 가장 쉬운 방법은 일반적으로 Rails 콘솔에서 실행하는 것입니다. 이렇게 하면 필터에 binding.pry를 설정하고 코드를 단계별로 실행할 수 있습니다.
TimeoutFilterHandler와 PipelineTimingCheck 때문에 필터 디버깅이 어려울 수 있습니다. 특수 환경 변수 GITLAB_DISABLE_MARKDOWN_TIMEOUT이 있으며, 설정하면 필터의 타임아웃 검사가 비활성화됩니다. 이는 GitLab Self-Managed 인스턴스가 해당 검사를 우회하려는 드문 경우에 고객도 사용할 수 있습니다.
text = 'Some test **Markdown**'
html = Banzai.render(text, project: nil)
이것은 프로젝트와 관계없이 Markdown을 렌더링합니다. 또는 프로젝트 컨텍스트에서 렌더링할 수도 있습니다:
project = Project.first
text = 'Some test **Markdown**'
html = Banzai.render(text, project: project)
render 메서드는 text와 렌더링을 위한 다양한 옵션을 제공하는 context 해시를 받습니다. 예를 들어 pipeline: :ascii_doc를 사용하여 AsciiDocPipeline을 실행할 수 있습니다. FullPipeline이 기본값입니다.
debug_timing: true를 지정하면 필터 목록과 각각 소요되는 시간을 확인할 수 있습니다.
Banzai.render(text, project: nil, debug_timing: true)
D, [2024-12-20T13:35:24.246463 #34584] DEBUG -- : 0.000012_s (0.000012_s): NormalizeSourceFilter [PreProcessPipeline]
D, [2024-12-20T13:35:24.246543 #34584] DEBUG -- : 0.000007_s (0.000019_s): TruncateSourceFilter [PreProcessPipeline]
D, [2024-12-20T13:35:24.246589 #34584] DEBUG -- : 0.000028_s (0.000047_s): FrontMatterFilter [PreProcessPipeline]
D, [2024-12-20T13:35:24.246662 #34584] DEBUG -- : 0.000005_s (0.000005_s): IncludeFilter [FullPipeline]
D, [2024-12-20T13:35:24.246816 #34584] DEBUG -- : 0.000088_s (0.000101_s): MarkdownFilter [FullPipeline]
...
D, [2024-12-20T13:35:24.252338 #34584] DEBUG -- : 0.000013_s (0.004394_s): CustomEmojiFilter [FullPipeline]
D, [2024-12-20T13:35:24.252504 #34584] DEBUG -- : 0.000095_s (0.004489_s): TaskListFilter [FullPipeline]
D, [2024-12-20T13:35:24.252558 #34584] DEBUG -- : 0.000028_s (0.004517_s): SetDirectionFilter [FullPipeline]
D, [2024-12-20T13:35:24.252623 #34584] DEBUG -- : 0.000045_s (0.004562_s): SyntaxHighlightFilter [FullPipeline]
더 자세한 필터별 정보를 보려면 debug: true를 사용하세요.