Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui] Fix endless log loading #26446

Merged
merged 1 commit into from
Dec 16, 2024
Merged

[ui] Fix endless log loading #26446

merged 1 commit into from
Dec 16, 2024

Conversation

hellendag
Copy link
Member

@hellendag hellendag commented Dec 12, 2024

Summary & Motivation

We received a bug report from an org with some very, very long log output on some of their runs. Specifically, hundreds of thousands of large logs for individual runs. I noticed two issues during testing:

  • When a chunk of logs loads and indicates that there are more older logs left to stream, we consider that a "loading" state and simply don't show any logs in the logs table at all.
    • This is unnecessary, since we're streaming logs in. Instead, just show the loading state if there are no logs available yet at all.
  • First, we're using the spread-to-concat antipattern in the subscription handler reducer, which means iterating over the entire existing list of nodes before adding the newly received logs.
    • To resolve this, I'm changing the state array to track chunks of logs instead of flattened logs. This means we can more efficiently push chunks into state without mutating state or taking the time to copy entire chunks, then flatten them as needed for filtering/rendering.
    • I used [].concat(...chunks) as the flattening approach, based on benchmarking. it seems to be faster than flat().

How I Tested These Changes

View run with neverending log loading. Verify that logs appear and stream in.

Use Chrome profiler to determine that the chunking and flattening behavior appears to be performant at scale. The filterLogs function remains expensive, but I think in this case it's because the logs themselves are enormous and require a ton of JSON stringification to support text searching.

Changelog

[ui] Fix run log streaming for runs with a large volume of logs.

Copy link
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link

github-actions bot commented Dec 12, 2024

Deploy preview for dagit-core-storybook ready!

✅ Preview
https://dagit-core-storybook-j1hezvw9k-elementl.vercel.app
https://dish-logs-loading-forever.core-storybook.dagster-docs.io

Built with commit c174946.
This pull request is being automatically deployed with vercel-action

@hellendag hellendag force-pushed the dish/logs-loading-forever branch from 60ac241 to b306940 Compare December 12, 2024 17:14
@hellendag hellendag marked this pull request as ready for review December 12, 2024 17:34
@@ -81,12 +81,10 @@ export const LogsScrollingTable = (props: Props) => {
}, [totalHeight, virtualizer]);

const content = () => {
if (logs.loading) {
if (logs.allNodeChunks.length === 0 && logs.loading) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the specific bug report.

<NonIdealState icon="spinner" title="Fetching logs..." />
</ListEmptyState>
<Box margin={{top: 32}} flex={{direction: 'column', alignItems: 'center'}}>
<SpinnerWithText label="Loading run logs…" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweaking the loading state to better match our loading states elsewhere.

const nodes = [...state.nodes, ...queuedNodes];

const copy = state.nodeChunks.slice();
copy.push(queuedNodes);
Copy link
Member Author

@hellendag hellendag Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid copying the entire list of individual logs by using chunks instead.

@hellendag hellendag force-pushed the dish/logs-loading-forever branch from b306940 to d0ed589 Compare December 12, 2024 17:36
@hellendag hellendag force-pushed the dish/logs-loading-forever branch 2 times, most recently from 88e37b7 to dbcc916 Compare December 12, 2024 17:54
@@ -113,9 +114,12 @@ export const StepLogsModalContent = ({
const [logType, setComputeLogType] = useState<LogType>(LogType.structured);
const [computeLogUrl, setComputeLogUrl] = React.useState<string | null>(null);

const firstLogForStep = logs.allNodes.find(
const flatLogs = useMemo(() => flattenNodeChunks(logs.allNodeChunks), [logs]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could try using useThrottledMemo in case the websocket gets spammed very quickly then we can batch multiple updates to lead to just 1 flattenNodeChunks call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import React, { useState, useEffect } from 'react';

function withThrottle(WrappedComponent, delay = 1000) {
  return function ThrottledComponent(props) {
    const [throttledProps, setThrottledProps] = useState(props);

    useEffect(() => {
      const handler = setTimeout(() => setThrottledProps(props), delay);
      return () => clearTimeout(handler);
    }, [props, delay]);

    return <WrappedComponent {...throttledProps} />;
  };
}

export default withThrottle;

Or maybe we could use something like the above to throttle the number of re-renders for the logs component.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is throttling behavior in LogsProvider to guard against this. It looks like it is working as intended, though it's pretty old code and could maybe be revisited at some point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I added that throttling. Thinking back on it though I think throttling at the component level would be nicer than just throttling the websocket updates.

@hellendag hellendag force-pushed the dish/logs-loading-forever branch from dbcc916 to c174946 Compare December 16, 2024 19:16
Copy link
Member Author

hellendag commented Dec 16, 2024

Merge activity

  • Dec 16, 1:35 PM CST: A user started a stack merge that includes this pull request via Graphite.
  • Dec 16, 1:35 PM CST: A user merged this pull request with Graphite.

@hellendag hellendag merged commit 279d44a into master Dec 16, 2024
2 checks passed
@hellendag hellendag deleted the dish/logs-loading-forever branch December 16, 2024 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants