Archive for November, 2022

Grooming the backlog, planning, and starting December sprint

November 30, 2022

Today I groomed the backlog a bit for the sandbox project, closed the last sprint, and started a new one.

Previous Goals

The goals for the November sprint for the sandbox project were:

  • Persist procgen data in instance service
  • Draft payment service

I succeeded on the first objective, and made decent headway on the second.

New goals

The goals for the December sprint for the sandbox project will be:

  • Retrieve procgen data on join an instance
  • Complete draft of payment service

It is a short sprint this month, so keeping things tight and focused is the best way forward.

November retrospective

November 29, 2022

General morale

Generally optimistic. As I write this on the 29th of July 2022 things have been progressing well.

What went well?

This month I:

  • Converted the existing procgen pipeline to stream data efficiently using sensibly sized chunks,
  • Fixed an issue with one of the consumers hanging after processing each packet,
  • Persisted the procedurally generated data successfully in a new table within the instance service,
  • Made a start on the Payments service, and gave some thought as to how to build a performant distributed system leveraging ZeroMQ.

What didn’t go so well?

Things went pretty well actually.

What’s the outlook?

Sunny with a slight chance of a few showers.

My basic plan is:

  • Complete the draft of the Payments service by POC’ing a request to the Stripe API using said app.
  • Leverage the persisted procgen data by pulling it when a new client joins an existing instance with procgen objects.
  • Look into creating shareable links to instances.

The road ahead

Per the roadmap, to reiterate the points from here:

Near term (from now through to the end of the January 2023 sprint).

  • Implement procgen persistence. The lion’s share of this was done in the November sprint just gone. It was a fair bit of work, but the groundwork is in place now. Integration will still be a slog but hopefully not nearly so much of one as it was to stream and persist the data. (December)
  • Create and integrate payment service. Might be a tad ambitious considering in retrospect to finish by the close of January. I’ve actually punted completion of this to February. I guess will see how this goes. (February)
  • Implement shareable instance links functionality. I have a concrete plan of action here. Moderately complex, not sure if I’ll have capacity this month to do it. Certainly by the end of January though. (January)
  • Implement basic limits. Basic organisation limits (max organisation users, max concurrent users in instances including anonymous ones, max instances – nb, I might not want to limit overall instance count). (January)
  • Migrate client to Godot 3.5. (December)

Not so near term (from the February 2023 sprint onwards through to December 2023, i.e. likely Q4 ’22 -> Q2 ’23 in real time).

  • Parametrised procgen + improved procgen experience.
  • Deployment of all the things, leveraging terraform and other techniques.
  • Instance polish.
  • Can move / rotate / delete procedurally generated object groups.
  • Can recompute a procedurally generated object group with different parameters (maybe out of scope, to be decided).
  • Mouse over preview of where things will be moved / rotated to. This should be possible by judicious use of collision masks (because I don’t want avatared tokens to be blocked by pending clipboard pastes, if I end up networking the preview view), as well as using ray tracing to detect a collision as to where the token or object group will go – the same way I place these things currently, just not “locked in”.
  • UI / UX polish. I did a very small amount of this in the current sprint, adopting an approach where I treated different parts of the UI as different “pages”, but I’ll need to do a fair bit more work in the client before I have something that looks vaguely okay, and not the scrappy mess it currently is.
  • Test coverage for the client in place, and client coverage at 20% overall. n.b. the tech to measure gdscript test coverage leveraging GUT doesn’t currently exist, but it should hopefully by the time I get around to looking into it again.
  • Rudimentary test harnesses in place to focus on controllers within the instance service, the user service, the organisation service, and the payment service respectively. Test coverage hovering at about 10% for each of these services, and ideally pushing past 20%.
  • Basic content available to users in palette (users should have a reasonably varied range of options to choose from in order to populate their instances). Ideally I’d like to allow for users to upload their own assets / procgen algorithms etc, but this is waaaay beyond the scope of a first iteration / prototype. For the prototype I’ll manually add, wire up and configure a basic selection of canned assets, and try to architect things with an eye for future generalisation and extensibility.

Continuing with Payments

November 28, 2022

Today I:

  • Added my (test) credentials to the project using rails encryption,
  • Set the Stripe secret key as a before action in the main application controller.

I don’t really want to hook up the standard form of rails UI to the thing though – I’m just building an api only app! The UI is the client. Is there a straightforward walkthrough somewhere on the web?

It appears that there is! As of 2 days ago, this article. Quite auspicious timing indeed =D

Heading into December then I’ll aim to at least get a basic request to the Stripe api working following that article. Then I can focus on fetching the persisted procgen data from the instance service, and one or two other things.

Small progress on payments app

November 27, 2022

Today I:

  • Created the OrganisationSubscriptions table,
  • Created an OrganisationSubscription model,
  • Drafted the OrganisationSubscription controller.

I have opted to gate all actions on the controller by assuming that params to the app will take the form

{ 
  organisation_subscription: {
    ...paramsGoHere
  }, 
  session: { jwt: "<jwtGoesHere>" }
}

The jwt when decoded will have structure

{
  user_type: "<user_type_goes_here>",
  organisation_id: <organisation_id_goes_here>
}

If no session param, no jwt, if the user type is not organisation admin, or if the organisation_id is incorrect, then I’ll forbid the request. This might not be entirely right but it is a starting point. Indeed, I’ll likely need a salt as well, so that the jwt is encrypted with the salt by the organisation service – then the payment service first decrypts the string, and then jwt decodes it using this shared secret, the salt string. I can probably use bcrypt to facilitate this process.

Can you spot the salt?

The reason that it would be wise to take additional measures regarding locking down this controller is because it is related to payments, so I should make sure that I put in place at least reasonable patterns in regards to this sort of thing.

Basically I want a mechanism so that I know when an organisation admin for an organisation is making a request to the payments service. So roughly:

  • When a user logs into an account, they should have a secret assigned to their session (as a jwt) that indicates that they are an admin if true,
  • Should said user opt to purchase a subscription, this secret is forwarded to the payments service, and the payments service decodes it accordingly in order to determine if they have permissions for the given organisation.

So all of this is very interesting, but how do I actually create a subscription against Stripe? Presumably I need to get the api test environment token and set it somewhere, then use the stripe payments gem to make a request to Stripe. I’ll start poking around at that next.

Goals for payment app + findings, also zeroMQ

November 26, 2022

Recent findings

I discovered this haxe integration project for Godot recently: https://github.com/dazKind/hxgodot-cpp. This is of interest to me because the city generator code I’m interested in is also written in haxe: https://github.com/watabou/TownGeneratorOS.

… and asdf has a plugin for haxe here: https://github.com/asdf-community/asdf-haxe.

Of course one can bind other things to Godot, like haskell: https://github.com/SimulaVR/godot-haskell

Not that I’d want to choose python as a runtime language, and the code dates from 2016, but this tutorial on city generators is nevertheless very instructive (documentation, code).

Buildify looks pretty cool.

Soap Site

Turns out that the soap site wasn’t migrated, as Cloudflare was my domain registrar! So just as well I didn’t delete the old VM yet. I fixed the ssl cert for the new VM, but there is some data synchronisation I’ll need to action before I can establish reasonable confidence in the new machine.

Cloudfront would be a good idea to investigate eventually too so that I can use an AWS native CDN instead of Cloudflare, but it is not a burning concern at present, maybe that can wait another year. For this year’s calendar campaign running on a t4g.medium + using Argo should be good enough.

Payment app planning

In terms of my goals for this sprint in terms of drafting the new payment app:

  • Have a basic app that can interact with Stripe if one makes certain api calls to it
  • Configure Stripe in test-mode to be able to create a $5 USD per month subscription, or $50 USD per year annual subscription
  • POC an api call to create a monthly subscription and an annual subscription using the app + Stripe api keys configured in env vars
  • Figure out in principle how to integrate it with the rest of the system

I will be basing things loosely off this toptal tutorial from 2019, and this more recent webcrunch tutorial from 2022 (code, youtube). But evidently I am dispensing with the UI, since the client is the UI – and I’m not intending on hooking that up yet (via comms).

Payment app stream of consciousness

First things, Stripe. An api key I will need.

  • Created an organisation, and a premium product with a 5 USD/month and 50 USD/year subscription price points.
  • Added the Stripe gem.
  • Installed Stripe CLI
brew install stripe/stripe-cli/stripe
  • Looked into data model
class CreateSubscriptions < ActiveRecord::Migration[7.0]
  def change
    create_table :subscriptions do |t|
      t.string :plan
      t.string :customer_id
      t.string :subscription_id
      t.references :organisation, null: false, foreign_key: true
      t.string :status
      t.string :interval
      t.datetime :current_period_end
      t.datetime :current_period_start

      t.timestamps
    end
  end
end

Actually, looking at this, there is a question in my mind as to whether it makes sense to decouple Subscriptions for an Organisation from the Organisation service itself. i.e., do I need a separate Payments app? If I am decoupling it, instead of t.references I should use t.string :organisation_id, and pass the organisation_id from the client via comms when making a create subscription request.

I guess that works??

This comes down to a choice as to how to architect to manage potential future complexity, and questions of premature optimisation.

It also depends upon what I want each app to actually do. For the instance service, that is easy – I want it to be the workhorse powering the instance experience for users. Easy.

For the User service, I want this to be the service powering how users log in and configure their settings. Ideally I’d like a user to be able to be a member of more than one organisation. One way of managing this is graphql, admittedly – another way is to have a separate service.

A User service also would be useful for storing user-specific digital in-system assets, eg marketplace purchases (if and when there is a marketplace).

For the Organisation service, the purpose of this is to track for an organisation which users are in it, what plan it is on, and how it is currently configured.

One argument for decoupling Payments is that although it will be used for tracking Organisation subscriptions, it would also be intended to track User-specific marketplace purchases too, as well as reimbursing content creators who build for the Sandbox project (contributing procgen assets or tokens, for instance, to some form of shared marketplace). So basically Payments would cater to at least three personas: individual Users, Organisation (owners), and Asset Builders.

Therefore I guess it does make sense to have Payments decoupled. But then we still have another pressing concern. Performance.

If I am concerned about Performance, for intra-subsystem communication I can’t use Kafka like I have for a few proof of concept calls. That will need to be replaced with something better. I think ZeroMQ is the best option for this. There are patterns coupled ZeroMQ with the Outbox pattern that allow for reliable communication between microservices.

(For inter-subsystem communication I think Kafka is probably okay for the time being, but I will periodically revisit this aspect of the system architecture.)

But what about Performance?

ZeroMQ is fast: for a send process and receive process running on the same machine, if the send process sends 10,000 messages, then the time delta between first sent and last received facilitated by ZeroMQ is a staggeringly small 15 milliseconds (ref). I think that is fast enough, and certainly blows RabbitMQ out of the water, being almost 100 times faster.

For guaranteed performance, of course, one would need each individual microservice to run in cloud infrastructure sufficiently “close” to each other. Running on the same machine would be a blunt approach that is probably “good enough” as a first iteration, but evidently this doesn’t scale.

A proper model would have containers of each microservice running in a shared kubernetes computing environment.

The third aspect of network latency to consider of course is the packet trip from user to the cloud services and back again. That is where Edge Computing comes to the fore, which can be loosely seen as a generalisation of the idea of a CDN (content delivery network) from content to microservices running “close to the edge”. AWS seems to offer a few services of this nature … I guess it remains to be seen how critical this aspect will be.

In terms of how Edge Computing works, presumably if a cloud (eg AWS) offers a service of this nature, one deploys an app to AWS, and then depending on where the traffic is coming from ephemeral copies of the app with a replicated database are generated closer to the user – and, depending on demand, these are spun down or more copies are provisioned. If there is a conflict between two database transactions then the system facilitating edge computing would have a process to reconcile these, eg by giving precedence for one over the other as the system periodically moves to pull itself into sync.

So as to that data migration …

class CreateOrganisationSubscriptions < ActiveRecord::Migration[7.0]
  def change
    create_table :organisation_subscriptions do |t|
      t.string :plan
      t.string :customer_id
      t.string :subscription_id
      t.integer :organisation_id
      t.string :status
      t.string :interval
      t.datetime :current_period_end
      t.datetime :current_period_start

      t.timestamps
    end
  end
end

I think that this should suit my purposes.

Figured out Winery

November 25, 2022

I figured out Winery, it turns out that I needed to build a custom moltenvk dll for my macos version. That solved the problem, now I can run Windows games in steam on my mac.

Basically, I:

Rails new

November 24, 2022

Today I broke ground on what will become the payments service.

The idea of this service will be to manage payments for the sandbox system, so that the system can, in principle, generate income – so that I can, in principle, pay taxes – and eventually be able to register the hobby as a business. I intend to leverage Stripe over Paypal due to the increased flexibility for payments processing that Stripe provides.

Simple recurring plans at first, I think 5 USD monthly for the paid plan. This will unlock higher limits, something reasonable like 20 CCU for usage of the system (as opposed to 10 CCU free). Probably bigger instances too and a higher rate limit for use of procgen.

In terms of procgen, probably paid plan would allow for rate limit of 20 calls per minute (as opposed to 10), can configure accordingly if this is too small or too large.

In terms of bigger instances, potentially allow for 2 times larger in width / length / depth, i.e. 8-fold increase in overall potential size.

Wrestled with winery, asdf, + cloudfront

November 23, 2022

Today I wrestled with winery, because I didn’t have my windows machine on hand, and, well, there was a need for gaming apparently!

I made some progress based on this, but things crashed out on launching the desired steam game, possibly because I didn’t have the correct version of winery installed.

Anyway it was a process. Maybe I’ll manage to get it working eventually.

In asdf ruby version management related news, apparently 3.1.0 is a pre-requisite step to 3.1.2 (because reasons). Nope, 3.1.0 is having the breaking change with psych issue again. I think this is a case of needing to wait for things to be fixed before adopting this version – or maybe just leapfrogging it for the 3.2.x series when that comes out eventually. So I might stick with 3.0.4 for now.

I also found out that Cloudfront might be better for the soap site than Cloudflare, so might be worth some investigation at some point.

Psych outs with asdf ruby

November 22, 2022

Today I:

export CFLAGS="-Wno-error=implicit-function-declaration"
ASDF_RUBY_BUILD_VERSION=v20220630 MAKE_OPTS=-j1 RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1) --with-readline-dir=$(brew --prefix readline) --with-libyaml-dir=$(brew --prefix libyaml)" asdf install ruby 3.0.0
  • discovered that that didn’t work, ending up manually building openssl 1.1.1n following this guide pointed to by this comment
  • Ran ./config –prefix=$HOME/openssl11n –openssldir=$HOME/openssl11n no-ssl2
  • make to compile, then make test to check, then make install.
export CFLAGS="-Wno-error=implicit-function-declaration"
ASDF_RUBY_BUILD_VERSION=v20220630 MAKE_OPTS=-j1 RUBY_CONFIGURE_OPTS="--with-openssl-dir=$HOME/openssl11n --with-readline-dir=$(brew --prefix readline) --with-libyaml-dir=$(brew --prefix libyaml)" asdf install ruby 3.0.0
  • still doesn’t work!!! found this error searching ??
  • brew uninstall openssl@3
  • try the command again …
export CFLAGS="-Wno-error=implicit-function-declaration"
ASDF_RUBY_BUILD_VERSION=v20220630 MAKE_OPTS=-j1 RUBY_CONFIGURE_OPTS="--with-openssl-dir=$HOME/openssl11n --with-readline-dir=$(brew --prefix readline) --with-libyaml-dir=$(brew --prefix libyaml)" asdf install ruby 3.0.0
  • noooooo $HOME/.asdf/installs/ruby/3.0.0/lib/ruby/3.0.0/yaml.rb:3: warning: it seems your ruby installation is missing psych. To eliminate this warning, please install libyaml and reinstall your ruby.
export CFLAGS="-Wno-error=implicit-function-declaration"
ASDF_RUBY_BUILD_VERSION=v20220630 MAKE_OPTS=-j1 RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline) --with-libyaml-dir=$(brew --prefix libyaml)" asdf install ruby 3.0.0
  • nope. further down the rabbit hole we go!
  • okay, that installed ruby 3.0.0, but gem install bundler psychs out again. so no.
  • maybe this? tools shouldn’t be this hard to use … nope.
  • trying rabbits again but with a modification
#>cd /var/folders/pr/k2gg73sn7cl3j8m4sl8dzl9h0000gn/T/ruby-build.20220723071458.58803.7Nl7vU/ruby-3.0.0
#>./configure --prefix=$HOME/.asdf/installs/ruby/3.0.0 \
--with-openssl-dir=/usr/local/opt/openssl@1.1 \
--enable-shared \
--with-readline-dir=/usr/local/opt/readline --with-libyaml-dir=/usr/local/opt/libyaml

Configuration summary for ruby version 3.0.0

   * Installation prefix: $HOME/.asdf/installs/ruby/3.0.0
   * exec prefix:         ${prefix}
   * arch:                x86_64-darwin20
   * site arch:           ${arch}
   * RUBY_BASE_NAME:      ruby
   * enable shared:       yes
   * ruby lib prefix:     ${libdir}/${RUBY_BASE_NAME}
   * site libraries path: ${rubylibprefix}/${sitearch}
   * vendor path:         ${rubylibprefix}/vendor_ruby
   * target OS:           darwin20
   * compiler:            clang -fdeclspec
   * with pthread:        yes
   * with coroutine:      amd64
   * enable shared libs:  yes
   * dynamic library ext: bundle
   * CFLAGS:              ${optflags} ${debugflags} ${warnflags}
   * LDFLAGS:             -L. -fstack-protector-strong \
                          -L/usr/local/opt/readline/lib -L/usr/local/lib
   * DLDFLAGS:            -Wl,-undefined,dynamic_lookup \
                          -Wl,-multiply_defined,suppress
   * optflags:            -O3
   * debugflags:          -ggdb3
   * warnflags:           -Wall -Wextra -Wdeprecated-declarations \
                          -Wdivision-by-zero \
                          -Wimplicit-function-declaration -Wimplicit-int \
                          -Wmisleading-indentation -Wpointer-arith \
                          -Wshorten-64-to-32 -Wwrite-strings \
                          -Wmissing-noreturn -Wno-constant-logical-operand \
                          -Wno-long-long -Wno-missing-field-initializers \
                          -Wno-overlength-strings -Wno-parentheses-equality \
                          -Wno-self-assign -Wno-tautological-compare \
                          -Wno-unused-parameter -Wno-unused-value \
                          -Wunused-variable -Wextra-tokens
   * strip command:       strip -A -n
   * install doc:         rdoc
   * JIT support:         yes
   * man page type:       doc
   * BASERUBY -v:         ruby 2.6.3p62 (2019-04-16 revision 67580) \
                          [universal.x86_64-darwin20]

#>make && make install
#>asdf reshim ruby 3.0.0
#>echo "ruby 3.0.0" >> .tool-versions
#>gem install bundler
$HOME/.asdf/installs/ruby/3.0.0/lib/ruby/3.0.0/yaml.rb:3: warning: It seems your ruby installation is missing psych (for YAML output).
To eliminate this warning, please install libyaml and reinstall your ruby.

Maybe export RUBY_CFLAGS=”-w” as well? Or it could just be a rbenv/ruby-build master issue, I could pin the ruby-build brew package to an earlier commit sha and see if that fixes things…

No ruby cflags doesn’t fix it =(

Or … ASDF_RUBY_BUILD_VERSION=v20220610 or v20220426 ???? Probably not a great idea, the psych issue seems to predate ruby 3.0.0, certainly ruby 3.1.2!

Okay, I’m going to conclude that ruby-build is broken on MacOS 11.6.5 Intel x86, rbenv is broken, and asdf-ruby which uses ruby-build is broken.

Time to explore a different tool for ruby version management, maybe ruby-install? https://github.com/postmodern/ruby-install

No, still an issue, maybe GEM_HOME needs to be set back to the rvm version that I had before?

Okay even with rvm ruby is broken (gem install bundler doesn’t work), rvm reinstall ruby-2.6.6 time.

Hmm, getting the psych issue with rvm install 2.6.6, what is going on? Must be a ruby-build thing. Pinning time. https://github.com/Homebrew/homebrew-core/tree/6ab5d51d9aa82f3c24a07ecbcd4abbf53dce4cc1 .

brew uninstall --ignore-dependencies ruby-build
curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/6ab5d51d9aa82f3c24a07ecbcd4abbf53dce4cc1/Formula/ruby-build.rb && brew install ./ruby-build.rb
rvm uninstall 2.6.6
rvm install 2.6.6
gem install bundler
>$HOME/.rvm/rubies/ruby-2.6.6/lib/ruby/2.6.0/yaml.rb:3: warning: It seems your ruby installation is missing psych (for YAML output).
>To eliminate this warning, please install libyaml and reinstall your ruby.
>
>$HOME/.rvm/rubies/ruby-2.6.6/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require': incompatible library version - $HOME/.rvm/gems/ruby-2.6.6/gems/strscan-3.0.3/lib/strscan.bundle (LoadError)

Possibly I could pin libyaml, that might be another option … and what on earth is this strscan-3.0.3 nonsense? Maybe I need to delete the whole directory there.

brew uninstall --ignore-dependencies libyaml
curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/6ab5d51d9aa82f3c24a07ecbcd4abbf53dce4cc1/Formula/libyaml.rb && brew install ./libyaml.rb

Turns out all unnecessary, just needed to delete all gems in .rvm/gems/ruby-2.6.6/gems. After that I could install different ruby versions with asdf.

Okay, installed ruby 3.0.0 and ruby 2.6.6, only with asdf install ruby version, good good. But ruby 3.1.2 psych issues again, probably this: https://www.reddit.com/r/ruby/comments/ugnh32/ruby_31s_incompatible_changes_to_its_yaml_module/

Good old incompatible changes!

Performance considerations

November 21, 2022

One question I’m starting to think about is performance of the system I’m aiming to build. With the Soap Site I have been able to get decent performance by leaning on Cloudflare edge layer caching, but with the Sandbox project I won’t have that luxury.

One thing that I am considering for the Soap Site is to decouple the virtual machine from the database. One benefit of this is that I could then do geo-load balancing (still through Cloudflare) so that the few requests that do require communication with the physical server have less distance to travel.

In addition to this, I could have a replicated database. Fortunately AWS Aurora already supports this.

Those two things should potentially be able to half the load time of the Soap Site – which is already fairly decent. This would be exciting if possible!

A lower hanging fruit would be to enable Argo for the Soap Site, I might do that when I run the next calendar campaign. From Cloudflare’s pitch for it:

Argo is a service that uses optimized routes across the Cloudflare network to decrease loading times, increase reliability, and reduce bandwidth costs.

Enabling Argo activates Argo Smart Routing, reducing Internet latency by 30% and connection errors by 27% on average.

For 5 USD / month it seems fairly reasonable.

For the Sandbox Project, I could follow a similar approach – leverage replication for my database layer, and also do geo-load balancing for the service layer. This would obviously be a bit expensive and I probably wouldn’t look into doing this straight away. But it is good to know that it is possible.

For now I’ll probably aim just to co-locate all VMs on an AWS region close to home, then at least packets won’t have to travel too far.