https://pine32.be - © pine32.be 2024
Welcome! - 58 total posts. [RSS]
A funny little cycle. [LATEST]


Search 58 posts with 29 unique tags


#1728318672


[ dev | corap ]

Apart from a few minor glitches (which have been fixed) the scraper and scheduler runs fine. The frontend is also coming along nicely, needs a few more pages and then the CSS. And I also need to figure out how to load a dynamic amount of columns from a materialized view, shouldn’t be to hard but I want to make it fault tolerant. I don’t know what I want to do regarding design. But I know some body that maybe wants to help me, fingers crossed.

#1727195369


[ dev | corap ]

Python (scraper) rewrite is done. Almost no dependencies now. Reduced the Docker image from 1.2 GB to less then 100MB. Feels a lot better to update and modify to. Now time for the fronted webserver.

beautifulsoup4==4.12.3
requests==2.32.3
python-dotenv==1.0.1
psycopg==3.2.2
psycopg-binary==3.2.2

#1726927465


[ dev | corap | database ]

The amount of cursed SQL that I am writing just to keep it in pure SQL. It would be way faster to just make the query in Python. Anyway… Corap rewrite is coming along nicely.

DO $$
DECLARE
    cols text;
    query text;
BEGIN
    SELECT string_agg(quote_ident(name) || ' text', ', ')
    INTO cols
    FROM (
        SELECT name
        FROM (SELECT DISTINCT name, priority FROM device_analyses) AS o
        ORDER BY priority DESC
    ) AS o;
    
    BEGIN
        EXECUTE 'DROP MATERIALIZED VIEW IF EXISTS device_analysis_summary';
        query := format('
            CREATE MATERIALIZED VIEW device_analysis_summary AS
            SELECT *
            FROM crosstab(
                ''SELECT d.deveui, da.name, da.value
                FROM devices d
                LEFT JOIN device_analyses da ON d.deveui = da.device_id
                ORDER BY d.deveui, da.name'',
                ''SELECT name
                FROM (SELECT DISTINCT name, priority FROM device_analyses) AS o
                ORDER BY priority DESC''
            ) AS ct(deveui text, %s);
        ', cols);
        EXECUTE query;
    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE 'Error creating materialized view: %', SQLERRM;
            ROLLBACK;
            RETURN;
    END;
END $$;

#1726662585


[ dev | corap ]

Time to rewrite Corap finally, starting with the scheduler. The current docker images is more then 1 GB. Going to remove a lot of dependencies. Also going to rewrite the fronted, learned a lot about Golang sins starting that project.

#1722283789


[ mb_dev ]

So… this regex broke my site… Because it is a ‘Youtube URL’ but is has no ID so it breaks things and panics. I should really learn regex. But not now, hotfix for the win.

faulty regex screenshot

#1720948971


[ dev | rant ]

I am officially done with ORM’s. My latest experiment was ent, a code gen based ORM for Golang. Works fine, I like the API, and then you want to do something slightly complex and it just doesn’t work. I wanted a many to many with extra data in the join table, so for so good, this did work. Until I wanted to make it not unique. I needed this because I wanted to add one track multiple times to a playlist, in my current project. But this was not allowed, the codegen would not build. Other people have the same issue but no solution is known. So my solution is to rewrite my code again, this time with pgx. I also have tried and used sqlc in some projects but it won’t scale for my current project. But I do like it a lot for smaller projects, like this blog uses it for example.

I have tried a lot of ORM’s over the year but I am finally done, not a chance. They are cool great until they are not, then they are just a pain.

#1720389426


[ webdev ]

Spent the last 2 weeks scraping supermarkets for a vacation job and the average website is hot garbage. One website pulled the location data of all stores in all countries (1000+ stores) to show 1 marker on a small map. The other had over 50 deep HTML elements. Not to speak of all the badly formatted data that I had to parse (including invalid JSON). Just when you think you’ve seen it all they come up with some more BS. I am going to explode.

#1719158831


[ webdev | rant ]

Write your own auth!!

Everybody should write there own authentication at least once. I am currently writing a session based authentication system for one of my upcoming projects. It forced me to learn all parts of system. And once you’ve done it, authentication is no longer as scary and complicated as it might otherwise seem. Yes, it could be a vulnerability, but so can many things in your application.

#1718647739


[ webdev ]

Finally updated my personal website/blog, pine32.be. It is finally in a state that I don’t hate. I Rewrote it in Zola, before that is was written in Hugo. Both are static site generators but after trying both I preferer Zola. It is simpler but still feels more powerful, not to mention that it is written in Rust.

I am currently happy with both the layout and the content. I don’t think I will change the layout much in the future. I do want to add some more longer blogs. We’ll see if I actually do it.

#1715288892


[ mb_dev | webdev ]

Finally added cache busting to mb. I should have done this long before but I only learned this technique recently. For some reason I never wondered how bigger sites did this.

For those who don’t know what cache busting is. It’s a technique used to force a web browser to download the latest version of a static file (css, img, …) instead of using a cached version. This is done by making every version of a file have a different and unique name, so the browser won’t recognize the updated file and will fetch it from the server. I have done this by adding a hash of the file to the filename, for example main-ad5d104a4a86.css.

With every version being a unique file we can update the Cache-Control header.

Cache-Control: public, max-age=31536000, immutable

This the most aggressive caching that you can configure, it tells the browser that this file will never change and that it will never need to refetch it from the server. This could be a problem for normal files but this is perfect for this usecase because we have control of the clients cache from the server.

Normally these hashes would be added at build/bundle time. But Golang doesn’t have these options so I did it at start-up of the server. Luckily Golang is very fast so it didn’t add any time to the start up (1-2 ms). The implementation works better then expected.

The perfectionist within me did try it with code-gen and it did work, see commit. But it didn’t feel clean or maintainable and was drifting from the goal of simplicity of this project, so I decided to scrap it.

#1713557458


[ mb_dev ]

We do a little refactoring.

Showing 58 changed files with 406 additions and 352 deletions.

Project structure is now much better, less global var’s etc.

#1712417261


[ mb_dev ]

Just added metadata and updated the colour scheme for better contrast. With these two updates completed, I finally have a perfect Lighthouse score.

screenshot of perfect lighthouse score

#1712090133


[ webdev ]

The datalist element is pretty cool. You can make dropdown inputs that are still editable realy easy. Came in handy today.

<label>
	Choose a flavor:
	<input list="ice-cream-flavors" name="ice-cream-choice" />
</label>

<datalist id="ice-cream-flavors">
  <option value="Chocolate"></option>
  <option value="Coconut"></option>
  <option value="Mint"></option>
  <option value="Strawberry"></option>
  <option value="Vanilla"></option>
</datalist>

Source example: MDN Web Docs

#1711884999


[ mb_dev ]

Today is the first day of summer time and mb handled it correctly. I did not expect it to work correctly because I can’t recall implementing it.

I think the system will break if you change the time zone after the fact. Posts don’t store time zone data, only a Unix timestamp. Just don’t change the time zone I guess.

#1711279188


[ webdev | mb_dev ]

Finally added the RSS feed to mb. Wasn’t to difficult using the encoding/xml module of the standard lib and exactly 100 lines of code. It’s basic but it’s valid and it works. Maybe I will flesh it out if people actually use it.

[Valid RSS]

#1710948563


[ webdev ]

javascript fatigue:
longing for a hypertext
already in hand

- HTMX

#1710620087


[ mb_dev | photography ]

Mb has finally support for file uploads, only images for now. Video and audio is planned, soon™. The blog is more self sufficient with this feature added, not dependent on any other services.

blue bug

photo credit: ii*

#1710601100


[ webdev | mb_dev ]

Just found out a way to add hotkeys to a website without using JavaScript.

<a href="/auth" accesskey="a" aria-hidden="true" tabindex="-1"></a>
<a href="/media" accesskey="m" aria-hidden="true" tabindex="-1"></a>
<a href="/backup" accesskey="b" aria-hidden="true" tabindex="-1"></a>

aria-hidden="true" and tabindex="-1" are there to keep screen readers happy (and to keep my Google Lighthouse score high).

These hidden elements add alt + x hotkeys. It always surprises me how many things the HTML5 spec has integrated.

Shoutout to this article for the idea.

#1709333288


[ golang | docker | mb_dev ]

Docker vs Native binary

Tested the backend of this blog on small VM on my desktop with both a native binary and Docker to compare performance. My desktop could have a lot of variance so this test is not that accurate. The VM has 2GB of ram and 4 cores allocated. Memory is not limiting factor as the webserver uses 20MB at most. All 4 cores are utilised but the one core is always more used then the other, I assume that this is the case because the webserver is database limited for the most part. SQLite is in some ways single threaded.

  1  [|||||||||||||||||||||||||||||||   78.5%] 
  2  [||||||||||||||||||||||||||||||||  80.2%] 
  3  [||||||||||||||||||||||||||||||||  80.3%]
  4  [||||||||||||||||||||||||||||||||||95.7%]

I used the latest version at the time of writing.

I did 3 runs of each, alternating between the two version to limit variation. I used a tool called bombardier with 20 concurrent connections for every 10 second run. The requests are made from the host machine to give the webserver the whole VM to itself. This does add extra overhead but it is the same for both versions, so for sake of comparison this is fine.

These are the averaged results for both versions.

Binary

Statistics        Avg      Stdev        Max
  Reqs/sec       262.03     152.02     913.95
  Latency       75.22ms    53.44ms      0.93s
  Latency Distribution
     50%    71.05ms
     75%   104.18ms
     90%   127.14ms
     95%   143.13ms
     99%   188.12ms
  HTTP codes:
    1xx - 0, 2xx - 2643, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:     3.15MB/s

Docker

Statistics        Avg      Stdev        Max
  Reqs/sec       260.70     150.30     962.85
  Latency       76.59ms    53.97ms      0.95s
  Latency Distribution
     50%    70.67ms
     75%   107.55ms
     90%   135.69ms
     95%   151.88ms
     99%   197.22ms
  HTTP codes:
    1xx - 0, 2xx - 2622, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:     3.12MB/s

These results are really close with a very slight edge for the binary. Only 0.5% more requests per second and 1.78% lower latency on average. This is within the margin of error for this unstable setup. But even if we would take these results as 100% accurate I still think it is worth the small hit in performance. This small hit is probably even smaller when you are using a bigger server on more native hardware.

#1707727407


[ golang | webdev ]

SSE are cool.

This an example using the Echo framework in Golang. It sets up the connection, consumes a channel and sends that data to the user. It also exits the loop if the connection is broken, this is communicated via the request context.

func BuildingSSE(c echo.Context) error {
	c.Response().Header().Set(echo.HeaderCacheControl, "no-cache")
	c.Response().Header().Set(echo.HeaderConnection, "keep-alive")
	c.Response().Header().Set(echo.HeaderContentType, "text/event-stream")

	queue := queue.GetBuildQueue()

	ctx := c.Request().Context()
	for {
		select {
		case result := <-queue.BuildLogsChannel:
			fmt.Fprint(c.Response(), buildSSE("message", result))
			c.Response().Flush()
		case <-ctx.Done():
			return nil
		}
	}
}

func buildSSE(event, context string) string {
	var result string
	if len(event) != 0 {
		result = result + "event: " + event + "\n"
	}
	if len(context) != 0 {
		result = result + "data: " + context + "\n"
	}
	result = result + "\n"
	return result
}

#1707084791


[ mb_dev | music ]

Custom embeds :D

With this feature done, mb is now ready for its first deployment.

Players never doubt

YouTube Thumbnail

Cover Art